Эх сурвалжийг харах

feat(ad): history

adjust api
Casper Dai 3 жил өмнө
parent
commit
c22fa1b037

+ 5 - 2
src/components/table/Table/Column.vue

@@ -1,5 +1,8 @@
 <script>
-import { getThumbnailUrl } from '@/api/asset'
+import {
+  getAssetUrl,
+  getThumbnailUrl
+} from '@/api/asset'
 import {
   State,
   AssetType
@@ -79,7 +82,7 @@ export default {
         return this.$createElement('auto-image', {
           staticClass: `o-thumbnail${on ? ' u-pointer' : ''}`,
           props: {
-            src: getThumbnailUrl(value.thumbnail, '64,fit'),
+            src: value.origin ? getAssetUrl(value.thumbnail) : getThumbnailUrl(value.thumbnail, '64,fit'),
             broken: value.type === AssetType.VIDEO ? 'video-broken' : 'image-broken'
           },
           nativeOn: on && { click: $event => {

+ 6 - 0
src/router/index.js

@@ -357,6 +357,12 @@ export const asyncRoutes = [
         path: 'order/review',
         component: () => import('@/views/ad/review-order/index'),
         meta: { title: '订单审核' }
+      },
+      {
+        name: 'ad-order-history',
+        path: 'order/history',
+        component: () => import('@/views/ad/history/index'),
+        meta: { title: '审核记录' }
       }
     ]
   },

+ 5 - 6
src/views/ad/api.js

@@ -1,19 +1,18 @@
-import request, { tenantRequest } from '@/utils/request'
+import request from '@/utils/request'
 import {
   resolve,
-  reject,
-  addTenant
+  reject
 } from '@/api/base'
 
 export function getOrders (query, options) {
   const { pageNum: pageIndex, pageSize, ...params } = query
-  return tenantRequest({
+  return request({
     url: '/ad/tenant/order/list',
     method: 'GET',
-    params: addTenant({
+    params: {
       pageIndex, pageSize,
       ...params
-    }),
+    },
     ...options
   })
 }

+ 141 - 0
src/views/ad/history/index.vue

@@ -0,0 +1,141 @@
+<template>
+  <wrapper
+    fill
+    margin
+    padding
+    background
+  >
+    <schema-table
+      ref="table"
+      :schema="schema"
+    />
+    <table-dialog
+      ref="adDialog"
+      title="广告内容"
+      :schema="adSchema"
+    />
+    <preview-dialog ref="previewDialog" />
+  </wrapper>
+</template>
+
+<script>
+import {
+  State,
+  AssetType
+} from '@/constant'
+import {
+  parseByte,
+  parseDuration
+} from '@/utils'
+import { getOrders } from '../api'
+
+export default {
+  name: 'AdOrderHistory',
+  data () {
+    return {
+      schema: {
+        condition: { status: State.RESOLVED },
+        list: getOrders,
+        transform: this.transform,
+        filters: [
+          { key: 'status', type: 'select', options: [
+            { value: State.RESOLVED, label: '通过' },
+            { value: State.REJECTED, label: '驳回' }
+          ] }
+        ],
+        cols: [
+          { type: 'refresh' },
+          { prop: 'deviceName', label: '设备', 'min-width': 100 },
+          { prop: 'startDate', label: '起始日期', align: 'right', 'min-width': 100 },
+          { prop: 'range', label: '时段', align: 'right', 'min-width': 100 },
+          { prop: 'freq', label: '频率', align: 'right', 'min-width': 140 },
+          { prop: 'price', label: '总价(元)', 'min-width': 100, align: 'right' },
+          { prop: 'createTime', label: '提交时间', 'min-width': 140, align: 'right' },
+          { prop: 'auditTime', label: '审核时间', 'min-width': 140, align: 'right' },
+          { prop: 'remark', label: '备注', 'min-width': 140, align: 'right' },
+          { type: 'invoke', render: [
+            { label: '内容', on: this.onView }
+          ] }
+        ]
+      },
+      adSchema: {
+        list: this.getSources,
+        cols: [
+          { prop: 'file', label: '缩略图', type: 'asset', on: this.onViewAsset },
+          { prop: 'adDuration', label: '播放时长', align: 'left' },
+          { prop: 'fileType', label: '文件类型', align: 'center' },
+          { prop: 'size', label: '文件大小' },
+          { prop: 'ratio', label: '分辨率' },
+          { prop: 'remark', label: '其他' },
+          { type: 'invoke', render: [
+            { label: '查看', render ({ file }) { return !!file }, on: this.onViewSource }
+          ] }
+        ]
+      }
+    }
+  },
+  methods: {
+    transform ({ id, price, status, createTime, auditTime, expand, orders, assets }) {
+      const { startDate, range, day, duration, count } = orders[0]
+      return {
+        id,
+        deviceName: orders[0].name,
+        startDate,
+        range,
+        price: (price / 100).toFixed(2),
+        freq: `${day}天 x ${duration}秒 x ${count}次`,
+        remark: status === State.REJECTED ? expand : '-',
+        createTime,
+        auditTime,
+        assets
+      }
+    },
+    onView (order) {
+      this.$order = order
+      this.$refs.adDialog.show()
+    },
+    getSources () {
+      const sources = this.$order.assets
+      return Promise.resolve({
+        data: sources.map(this.transformSource),
+        totalCount: sources.length
+      })
+    },
+    transformSource ({ keyName, type, adDuration, duration, size, width, height, thumb, status = 3, reason = 'xxxx' }) {
+      const isImage = type === AssetType.IMAGE
+      return type
+        ? {
+          file: {
+            type,
+            url: keyName,
+            thumbnail: isImage ? keyName : thumb || null,
+            origin: !isImage
+          },
+          fileType: isImage ? '图片' : '视频',
+          adDuration: parseDuration(adDuration || duration),
+          ratio: width && height ? `${width}x${height}` : '-',
+          size: parseByte(size),
+          remark: duration ? parseDuration(duration) : '-',
+          statusTag: {
+            type: ['', 'primay', 'success', 'danger'][status],
+            label: ['-', '待审核', '已审核', '驳回'][status],
+            msg: status === State.REJECTED ? reason : ''
+          }
+        }
+        : {
+          fileType: '-',
+          adDuration: parseDuration(adDuration || duration),
+          ratio: '-',
+          size: '-',
+          remark: '素材已删除'
+        }
+    },
+    onViewSource ({ file }) {
+      this.onViewAsset(file)
+    },
+    onViewAsset (asset) {
+      this.$refs.previewDialog.show(asset)
+    }
+  }
+}
+</script>

+ 5 - 3
src/views/ad/review-asset/index.vue

@@ -66,7 +66,7 @@ export default {
   data () {
     return {
       schema: {
-        condition: { status: State.READY },
+        condition: { status: State.SUBMITTED },
         list: getAssets,
         transform: this.transform,
         cols: [
@@ -96,14 +96,16 @@ export default {
   },
   methods: {
     transform ({ keyName, type, size, duration, width, height, thumb, createTime }) {
+      const isImage = type === AssetType.IMAGE
       return {
         keyName,
         file: {
           type,
           url: keyName,
-          thumbnail: type === AssetType.IMAGE ? keyName : thumb || null
+          thumbnail: isImage ? keyName : thumb || null,
+          origin: !isImage
         },
-        fileType: type === AssetType.IMAGE ? '图片' : '视频',
+        fileType: isImage ? '图片' : '视频',
         ratio: width && height ? `${width}x${height}` : '-',
         size: parseByte(size),
         remark: duration ? parseDuration(duration) : '-',

+ 56 - 37
src/views/ad/review-order/index.vue

@@ -70,18 +70,17 @@ export default {
   data () {
     return {
       schema: {
-        condition: { status: State.READY },
+        condition: { status: State.SUBMITTED },
         list: getOrders,
         transform: this.transform,
         cols: [
           { type: 'refresh' },
-          { prop: 'device', label: '设备' },
-          { prop: 'address', label: '位置' },
-          { prop: 'date', label: '起始日期', 'min-width': 100 },
-          { prop: 'day', label: '持续(天)', 'min-width': 90, align: 'right' },
-          { prop: 'range', label: '时段', 'min-width': 100, align: 'right' },
-          { prop: 'count', label: '每日(次)', 'min-width': 90, align: 'right' },
-          { prop: 'price', label: '价格(元)', 'min-width': 100, align: 'right' },
+          { prop: 'deviceName', label: '设备', 'min-width': 100 },
+          { prop: 'startDate', label: '起始日期', align: 'right', 'min-width': 100 },
+          { prop: 'range', label: '时段', align: 'right', 'min-width': 100 },
+          { prop: 'freq', label: '频率', align: 'right', 'min-width': 140 },
+          { prop: 'price', label: '总价(元)', 'min-width': 100, align: 'right' },
+          { prop: 'createTime', label: '提交时间', 'min-width': 140, align: 'right' },
           { type: 'invoke', width: 160, render: [
             { label: '内容', on: this.onView },
             { label: '通过', on: this.onResolve },
@@ -101,32 +100,33 @@ export default {
       adSchema: {
         list: this.getSources,
         cols: [
-          { prop: 'file', type: 'asset', on: this.onViewAsset },
-          { prop: 'filename', label: '', 'min-width': 100 },
-          { prop: 'duration', label: '播放时长', align: 'right' },
-          { prop: 'size', label: '大小', align: 'right' },
+          { prop: 'file', label: '缩略图', type: 'asset', on: this.onViewAsset },
+          { prop: 'adDuration', label: '播放时长', align: 'left' },
+          { prop: 'fileType', label: '文件类型', align: 'center' },
+          { prop: 'size', label: '文件大小' },
+          { prop: 'ratio', label: '分辨率' },
+          { prop: 'remark', label: '其他' },
+          { prop: 'statusTag', type: 'tag' },
           { type: 'invoke', render: [
-            { label: '查看', on: this.onViewSource }
+            { label: '查看', render ({ file }) { return !!file }, on: this.onViewSource }
           ] }
         ]
       }
     }
   },
   methods: {
-    transform ({ order, devices, assets }) {
-      const slot = order.scheduleType === 1 ? order.slot : {}
+    transform ({ id, price, status, createTime, auditTime, expand, orders, assets }) {
+      const { startDate, range, day, duration, count } = orders[0]
       return {
-        id: order.id,
-        device: devices[0].name,
-        address: devices[0].address,
-        date: slot.startDate,
-        day: slot.day,
-        range: slot.range,
-        count: slot.count,
-        duration: parseDuration(order.duration),
-        price: (order.price / 100).toFixed(2),
-        status: order.status,
-        expand: order.expand,
+        id,
+        deviceName: orders[0].name,
+        startDate,
+        range,
+        price: (price / 100).toFixed(2),
+        freq: `${day}天 x ${duration}秒 x ${count}次`,
+        remark: status === State.REJECTED ? expand : '-',
+        createTime,
+        auditTime,
         assets
       }
     },
@@ -168,17 +168,36 @@ export default {
         totalCount: sources.length
       })
     },
-    transformSource ({ filename, type, duration, size, keyName }) {
-      return {
-        file: {
-          type,
-          url: keyName,
-          thumbnail: type === AssetType.IMAGE ? keyName : ''
-        },
-        filename,
-        duration: parseDuration(duration),
-        size: parseByte(size)
-      }
+    transformSource ({ keyName, type, adDuration, duration, size, width, height, thumb, status, reason }) {
+      const isImage = type === AssetType.IMAGE
+      return type
+        ? {
+          file: {
+            type,
+            url: keyName,
+            thumbnail: isImage ? keyName : thumb || null,
+            origin: !isImage
+          },
+          fileType: isImage ? '图片' : '视频',
+          adDuration: parseDuration(adDuration || duration),
+          ratio: width && height ? `${width}x${height}` : '-',
+          size: parseByte(size),
+          remark: duration ? parseDuration(duration) : '-',
+          status,
+          statusTag: {
+            type: ['', 'primay', 'success', 'danger'][status],
+            label: ['-', '待审核', '已审核', '驳回'][status],
+            msg: status === State.REJECTED ? reason : ''
+          }
+        }
+        : {
+          fileType: '-',
+          adDuration: parseDuration(adDuration || duration),
+          ratio: '-',
+          size: '-',
+          remark: '素材已删除',
+          status
+        }
     },
     onViewSource ({ file }) {
       this.onViewAsset(file)

+ 2 - 2
src/views/broadcast/history/components/BroadcastHistoryTable.vue

@@ -53,7 +53,7 @@ export default {
           { prop: 'endTime', label: '失效时间', render ({ endTime }) {
             return endTime || '-'
           } },
-          { label: '状态', type: 'tag', width: 100, render ({ releaseStatus }) {
+          { type: 'tag', width: 100, render ({ releaseStatus }) {
             return {
               type: ['warning', 'success'][releaseStatus],
               label: ['失效', '正常'][releaseStatus]
@@ -77,7 +77,7 @@ export default {
         list: getDeviceReleases,
         cols: [
           { prop: 'deviceId', label: '设备id' },
-          { label: '状态', type: 'tag', render ({ publishStatus }) {
+          { type: 'tag', render ({ publishStatus }) {
             return {
               type: ['warning', 'success'][publishStatus],
               label: ['失效', '正常'][publishStatus]

+ 11 - 0
src/views/realm/device/settings/components/AdConfigDialog.vue

@@ -5,6 +5,16 @@
     @confirm="onSave"
   >
     <div class="c-grid-form medium u-align-self--center">
+      <span class="c-grid-form__label">开启</span>
+      <div class="l-flex--row c-grid-form__option">
+        <el-switch
+          v-model="attributes.enable"
+          active-color="#13ce66"
+          inactive-color="#ff4949"
+          :active-value="1"
+          :inactive-value="0"
+        />
+      </div>
       <span class="c-grid-form__label required">开机时间</span>
       <el-time-picker
         v-model="attributes.openTime"
@@ -85,6 +95,7 @@ export default {
       getAdAttributes(id).then(({ data }) => {
         this.isAdd = !data
         this.attributes = {
+          enable: 0,
           openTime: '07:00', // 开机时间
           closeTime: '22:00', // 关机时间
           minDuration: 5, // 最小投放广告时长(单位秒)

+ 130 - 25
src/views/realm/device/settings/components/AttributeConfigDialog.vue

@@ -49,6 +49,48 @@
           :value="option.value"
         />
       </el-select>
+      <span class="c-grid-form__label">水平偏移</span>
+      <div
+        class="l-flex--row c-grid-form__option c-grid-form__info"
+        :data-info="maxPaddingHDesc"
+      >
+        <el-input-number
+          v-model="paddingH"
+          :min="0"
+          :max="maxPaddingH"
+          step-strictly
+        />
+      </div>
+      <span class="c-grid-form__label">垂直偏移</span>
+      <div
+        class="l-flex--row c-grid-form__option c-grid-form__info"
+        :data-info="maxPaddingVDesc"
+      >
+        <el-input-number
+          v-model="paddingV"
+          :min="0"
+          :max="maxPaddingV"
+          step-strictly
+        />
+      </div>
+      <span class="c-grid-form__label">二维码提示</span>
+      <el-input
+        v-model.trim="attributes.orderQrTip"
+        class="c-grid-form__option"
+        clearable
+      />
+      <span class="c-grid-form__label">字体大小</span>
+      <div
+        class="l-flex--row c-grid-form__option c-grid-form__info"
+        :data-info="maxFontSizeDesc"
+      >
+        <el-input-number
+          v-model="attributes.orderQrTipFontSize"
+          :min="12"
+          :max="maxFontSize"
+          step-strictly
+        />
+      </div>
     </div>
   </confirm-dialog>
 </template>
@@ -69,10 +111,10 @@ const attributeSet = [
     return null
   }, type: 'string' },
   { key: 'orderQr', type: 'boolean' },
-  { key: 'orderQrSize', type: 'string', defaults ({ wide, high }) {
+  { key: 'orderQrSize', defaults ({ wide, high }) {
     return Math.max(64, Math.min(wide, high) / 20 | 0)
   } },
-  { key: 'orderQrContent', type: 'string', defaults ({ id }) {
+  { key: 'orderQrContent', defaults ({ id }) {
     return `${process.env.VUE_APP_AD_ORDER_QR}?device=${id}`
   }, valid (val, map) {
     if (!val && map.orderQr === '1') {
@@ -80,11 +122,13 @@ const attributeSet = [
     }
     return null
   } },
-  { key: 'orderQrTop', type: 'string', defaults () {
-    return '-3'
+  { key: 'orderQrTop', type: 'string' },
+  { key: 'orderQrLeft', type: 'string' },
+  { key: 'orderQrTip', defaults () {
+    return '扫码投广告'
   } },
-  { key: 'orderQrLeft', type: 'string', defaults () {
-    return '-3'
+  { key: 'orderQrTipFontSize', defaults () {
+    return 36
   } }
 ]
 
@@ -98,6 +142,9 @@ export default {
   data () {
     return {
       maxSize: 64,
+      maxFontSize: 36,
+      maxPaddingH: 0,
+      maxPaddingV: 0,
       position: 'rb',
       positionOptions: [
         { value: 'lt', label: '左上' },
@@ -105,12 +152,23 @@ export default {
         { value: 'rt', label: '右上' },
         { value: 'rb', label: '右下' }
       ],
+      paddingH: 0,
+      paddingV: 0,
       attributes: {}
     }
   },
   computed: {
     maxSizeDesc () {
       return `范围:64~${this.maxSize}`
+    },
+    maxPaddingHDesc () {
+      return `范围:0~${this.maxPaddingH}`
+    },
+    maxPaddingVDesc () {
+      return `范围:0~${this.maxPaddingV}`
+    },
+    maxFontSizeDesc () {
+      return `范围:12~${this.maxFontSize},占高比1.4`
     }
   },
   methods: {
@@ -122,44 +180,91 @@ export default {
         this.$deviceId = id
         this.$extra = { tenant, width: wide, height: high }
         this.maxSize = Math.min(wide, high) / 2 | 0
+        this.maxFontSize = Math.min(wide, high) / 20 | 0
+        this.maxPaddingH = wide / 4 | 0
+        this.maxPaddingV = high / 4 | 0
         const attributes = {}
         attributeSet.forEach(({ key, type, defaults }) => {
           attributes[key] = data[key] || (defaults ? defaults(device) : defaultValue[type])
           attributes[`_${key}`] = data[key]
         })
+        this.initPositionAttributes(this.$extra, data)
         this.attributes = attributes
-        this.position = `${attributes.orderQrLeft === '-1' ? 'l' : 'r'}${attributes.orderQrTop === '-1' ? 't' : 'b'}`
         this.$refs.configDialog.show()
       }).finally(() => {
         this.$closeLoading(loading)
       })
     },
-    onSave (done) {
-      const attributes = this.attributes
-      const attributeMap = {}
-      const deviceAttributes = {
-        ...this.$extra
+    initPositionAttributes ({ width, height }, attributes) {
+      if (!attributes.orderQrLeft) {
+        this.position = 'rb'
+        this.paddingH = 0
+        this.paddingV = 0
+        return
       }
-      switch (this.position) {
-        case 'lt':
-          attributes.orderQrTop = '-1'
-          attributes.orderQrLeft = '-1'
+      const orderQrSize = Number(attributes.orderQrSize)
+      let position = ''
+      switch (attributes.orderQrLeft) {
+        case '-1':
+          position = 'l'
+          this.paddingH = 0
           break
-        case 'lb':
-          attributes.orderQrTop = '-3'
-          attributes.orderQrLeft = '-1'
+        case '-3':
+          position = 'r'
+          this.paddingH = 0
           break
-        case 'rt':
-          attributes.orderQrTop = '-1'
-          attributes.orderQrLeft = '-3'
+        default:
+          this.paddingH = Number(attributes.orderQrLeft) <= width / 2 ? Number(attributes.orderQrLeft) : width - Number(attributes.orderQrLeft) - orderQrSize
+          position = Number(attributes.orderQrLeft) <= width / 2 ? 'l' : 'r'
+          break
+      }
+      switch (attributes.orderQrTop) {
+        case '-1':
+          position += 't'
+          this.paddingV = 0
           break
-        case 'rb':
-          attributes.orderQrTop = '-3'
-          attributes.orderQrLeft = '-3'
+        case '-3':
+          position += 'b'
+          this.paddingV = 0
           break
         default:
+          this.paddingV = Number(attributes.orderQrTop) <= height / 2 ? Number(attributes.orderQrTop) : height - Number(attributes.orderQrTop) - orderQrSize - (attributes.orderQrTip ? Math.ceil(attributes.orderQrTipFontSize * 1.4) : 0)
+          position += Number(attributes.orderQrTop) <= height / 2 ? 't' : 'b'
           break
       }
+      this.position = position
+    },
+    onSave (done) {
+      const attributes = { ...this.attributes }
+      const attributeMap = {}
+      const deviceAttributes = {
+        ...this.$extra
+      }
+      if (this.paddingH === 0 && this.paddingV === 0) {
+        switch (this.position) {
+          case 'lt':
+            attributes.orderQrTop = '-1'
+            attributes.orderQrLeft = '-1'
+            break
+          case 'lb':
+            attributes.orderQrTop = '-3'
+            attributes.orderQrLeft = '-1'
+            break
+          case 'rt':
+            attributes.orderQrTop = '-1'
+            attributes.orderQrLeft = '-3'
+            break
+          case 'rb':
+            attributes.orderQrTop = '-3'
+            attributes.orderQrLeft = '-3'
+            break
+          default:
+            break
+        }
+      } else {
+        attributes.orderQrTop = this.position === 'lt' || this.position === 'rt' ? this.paddingV : this.$extra.height - attributes.orderQrSize - this.paddingV - (attributes.orderQrTip ? Math.ceil(attributes.orderQrTipFontSize * 1.4) : 0)
+        attributes.orderQrLeft = this.position === 'lt' || this.position === 'lb' ? this.paddingH : this.$extra.width - attributes.orderQrSize - this.paddingH
+      }
       let needUpdate = false
       for (let i = 0; i < attributeSet.length; i++) {
         const { key, valid } = attributeSet[i]

+ 1 - 1
src/views/review/history/index.vue

@@ -49,7 +49,7 @@ export default {
           { prop: 'name', label: '', 'min-width': 100 },
           { prop: 'createBy', label: '发布人' },
           { prop: 'createTime', label: '发布时间' },
-          { label: '状态', type: 'tag', render ({ status }) {
+          { type: 'tag', render ({ status }) {
             if (status === State.CANCEL) {
               return {
                 type: 'danger',

+ 1 - 1
src/views/review/workflow/mine/index.vue

@@ -107,7 +107,7 @@ export default {
           { type: 'expand', render: this.renderReason },
           { prop: 'handledBy', label: '处理人', width: 100 },
           { prop: 'createTime', label: '时间' },
-          { prop: 'tag', label: '状态', type: 'tag' },
+          { prop: 'tag', type: 'tag' },
           { prop: 'reason', label: '备注' }
         ]
       }