Browse Source

开发ing utm和分享

zhoujump 1 week ago
parent
commit
10e2690d70

+ 1 - 0
package.json

@@ -23,6 +23,7 @@
     "element-ui": "2.13.2",
     "hugerte": "^1.0.9",
     "js-cookie": "2.2.0",
+    "lz-string": "^1.5.0",
     "normalize.css": "7.0.0",
     "script-loader": "0.7.2",
     "sortablejs": "1.8.4",

File diff suppressed because it is too large
+ 132 - 0
public/shareModule/index.html


File diff suppressed because it is too large
+ 325 - 0
public/shareModule/initShare.js


+ 1 - 1
src/App.vue

@@ -110,7 +110,7 @@ export default {
 </script>
 <style lang="scss">
  body,html,#app{
-   margin: 0;
+   margin: 0 !important;
    height: 100%;
    width: 100%;
    overflow: hidden;

+ 80 - 0
src/api/expo.js

@@ -125,3 +125,83 @@ export function setExpoStatus(id,status) {
     }
   })
 }
+
+/**
+ * 获取UTM列表接口
+ * @param {number} page 页码
+ * @param {number} page_size 每页数量
+ * @param {string} campaign_name 推广计划名称
+ * @param {string} campaign_id 推广计划ID
+ * @returns {*}
+ */
+export function getUtmList(page, page_size, campaign_name, campaign_id) {
+  return request({
+    url: '/api/utm/list',
+    method: 'get',
+    params: {
+      page,
+      page_size,
+      campaign_name,
+      campaign_id
+    }
+  })
+}
+
+/**
+ * 获取自己UTM详情接口
+ * @param id UTM ID
+ * @returns {*}
+ */
+export function getUtmDetail(id) {
+  return request({
+    url: '/api/utm/info',
+    method: 'get',
+    params: {
+      id
+    }
+  })
+}
+
+/**
+ * 保存UTM详情接口
+ * @param {number} id
+ * @param {string} website_url
+ * @param {string} campaign_name
+ * @param {string} campaign_id
+ * @param {string} campaign_source
+ * @param {string} campaign_term
+ * @param {string} campaign_medium
+ * @param {string} campaign_content
+ * @returns {*}
+ */
+export function  saveUtmDetail(id,website_url,campaign_name,campaign_id,campaign_source,campaign_term,campaign_medium,campaign_content) {
+  return request({
+    url: '/api/utm/save-info',
+    method: 'post',
+    data:{
+      id,
+      website_url,
+      campaign_name,
+      campaign_id,
+      campaign_source,
+      campaign_term,
+      campaign_medium,
+      campaign_content
+    }
+  })
+}
+
+/**
+ * 删除UTM接口
+ * @param id UTM ID
+ * @returns {*}
+ */
+export function deleteUtm(id) {
+  return request({
+    url: '/api/utm/info',
+    method: 'delete',
+    data:{
+      id
+    }
+  })
+}

+ 14 - 3
src/layout/index.vue

@@ -9,7 +9,7 @@
         <el-menu :unique-opened="true" class="layout-menu-inner" :collapse="isCollapse" :default-active="menuActive+''">
           <template v-for="(route,index) in menuRouter">
             <template v-if="!route.meta.hidden">
-              <el-submenu v-if="route.children" :key="route.path" :index="index+''">
+              <el-submenu :class="'menu-'+route.name" v-if="route.children" :key="route.path" :index="index+''">
                 <template slot="title">
                   <i :class="[route.meta.icon,'icon']" />
                   <span slot="title">{{ route.meta.title }}</span>
@@ -21,7 +21,7 @@
                   </el-menu-item>
                 </template>
               </el-submenu>
-              <el-menu-item v-else :key="route.path" :index="index+''" @click="goto(route)">
+              <el-menu-item v-else :class="'menu-'+route.name" :key="route.path" :index="index+''" @click="goto(route)">
                 <i :class="[route.meta.icon,'icon']" />
                 <span slot="title">{{ route.meta.title }}</span>
               </el-menu-item>
@@ -283,9 +283,20 @@ export default {
                 padding-bottom: 6px;
               }
             }
-            &.hover{
+            &:hover{
               background: $menuHover;
             }
+            &.menu-inviteRebate{
+              &.is-active{
+                color: #543317;
+                background: linear-gradient(60deg, #f3e7dc, #ddbeab);
+                border-right: 4px solid #543317;
+              }
+              &:hover{
+                color: #543317;
+                background: linear-gradient(60deg, #f3e7dc, #ddbeab);
+              }
+            }
           }
           &:not(.el-menu--collapse) {
             width: 300px;

File diff suppressed because it is too large
+ 1 - 0
src/lib/lz-string.min.js


File diff suppressed because it is too large
+ 1 - 1
src/permission.js


+ 10 - 0
src/router/index.js

@@ -514,6 +514,16 @@ export const constantRoutes = [
         ]
       },
       {
+        path: 'inviteRebate',
+        component: () => import('@/views/inviteRebate/index'),
+        name: 'inviteRebate',
+        meta: {
+          title: '邀请返佣',
+          icon: 'el-icon-money',
+          roles: 'inviteRebate'
+        }
+      },
+      {
         path: '404',
         component: () => import('@/views/errorPage/404'),
         name: '404',

+ 1 - 1
src/views/dashboard/index.vue

@@ -14,7 +14,7 @@
                 <div class="el-icon-user button-icon"></div>
                 <div class="info-box">
                   <div class="text">已预约观众</div>
-                  <div class="value">{{currentExpo.form_count}}</div>
+                  <div class="value">{{currentExpo.form_count || 0}}</div>
                 </div>
               </router-link>
               <router-link to="/invitation" class="card-button">

+ 1 - 1
src/views/exhibitorManage/exhibitorList.vue

@@ -112,7 +112,7 @@ export default Vue.extend({
       this.$router.push({ path: '/exhibitor/add' })
     },
     gotoShare(row) {
-      this.$router.push({ path: '/share&utm/share?id=' + row.id })
+      this.$router.push({ path: '/share&utm/share?expoid=' + row.id })
     }
   }
 })

+ 4 - 1
src/views/exhibitorManage/exhibitorSetting.vue

@@ -128,6 +128,7 @@ export default Vue.extend({
           'undo redo | bold italic underline | fontsize',
           'forecolor backcolor | alignleft aligncenter alignright | insertImage'
         ],
+        font_size_formats: '0.7em 0.8em 0.9em 1em 1.1em 1.2em 1.3em 1.4em 1.5em 1.6em 1.7em 1.8em',
         menubar: false,
         height: '100%',
         width: '100%',
@@ -469,6 +470,9 @@ export default Vue.extend({
       height: 100%;
       width: 100%;
       position: relative;
+      #expoManangeEditor{
+        outline: 2px dashed lightgray;
+      }
       .scroll{
         padding: 8px;
         position: absolute;
@@ -481,7 +485,6 @@ export default Vue.extend({
       }
       .text-area{
         max-height: 120px;
-        outline: 2px dashed lightgray;
         width: 100%;
         margin-top: 24px;
       }

+ 15 - 0
src/views/inviteRebate/index.vue

@@ -0,0 +1,15 @@
+<script lang="ts">
+import Vue from 'vue'
+
+export default Vue.extend({
+  name: "index"
+})
+</script>
+
+<template>
+
+</template>
+
+<style scoped>
+
+</style>

File diff suppressed because it is too large
+ 889 - 101
src/views/shareUtm/share.vue


+ 582 - 3
src/views/shareUtm/utm.vue

@@ -1,15 +1,594 @@
 <script lang="ts">
 import Vue from 'vue'
-
+import * as echarts from 'echarts'
+import { getUtmList, getUtmDetail, saveUtmDetail, deleteUtm, getExpoList, getMyExpoInfo } from '@/api/expo'
 export default Vue.extend({
-  name: "utm"
+  name: 'Utm',
+  data() {
+    return {
+      showAdd: false,
+      isAdd: true,
+      expoList: [],
+      page: 1,
+      total: 0,
+      currentExpo: '',
+      loading: false,
+      searchData: {
+        campaign_name: '',
+        campaign_id: ''
+      },
+      addData: {
+        id: '',
+        website_url: '',
+        campaign_name: '',
+        campaign_id: '',
+        campaign_source: '',
+        campaign_term: '',
+        campaign_medium: '',
+        campaign_content: ''
+      },
+      adList: [],
+      currentAd: {},
+      echartsData: {
+        tooltip: { trigger: 'item' },
+        graphic: [
+          {
+            type: 'text',
+            left: 'center',
+            top: '38%',
+            style: {
+              text: '3147',
+              fontSize: 36,
+              fontWeight: 'bold',
+              fill: '#666',
+              textAlign: 'center'
+            }
+          },
+          {
+            type: 'text',
+            left: 'center',
+            top: '56%',
+            style: {
+              text: '人均pv',
+              fontSize: 16,
+              fill: '#111111',
+              textAlign: 'center'
+            }
+          }
+        ],
+        series: [
+          {
+            type: 'pie',
+            radius: ['60%', '100%'],
+            avoidLabelOverlap: false,
+            padAngle: 5,
+            itemStyle: { borderRadius: 10, color: function(params) {
+              const colorList = ['#72cdcd', '#d8ba87']
+              return colorList[params.dataIndex]
+            } },
+            label: { show: false },
+            left: 8, right: 8, top: 8, bottom: 8,
+            data: [
+              { value: 0, name: '访客数' },
+              { value: 0, name: '浏览量' }
+            ]
+          }
+        ]
+      }
+    }
+  },
+  mounted() {
+    this.getExpoListData()
+    this.getUtmData()
+  },
+  methods: {
+    openAdd() {
+      this.addData = {
+        id: '',
+        website_url: '',
+        campaign_name: '',
+        campaign_id: '',
+        campaign_source: '',
+        campaign_term: '',
+        campaign_medium: '',
+        campaign_content: ''
+      }
+      this.currentExpo = ''
+      this.isAdd = true
+      this.showAdd = true
+    },
+    getUtmData() {
+      if (this.loading) {
+        return
+      }
+      this.loading = true
+      getUtmList(this.page, 20, this.searchData.campaign_name, this.searchData.campaign_id)
+        .then(res => {
+          this.adList = res.data.data
+          this.total = res.data.total
+          this.page = res.data.current_page
+          this.loading = false
+        })
+    },
+    inputSearch() {
+      if (this.timer) {
+        clearTimeout(this.timer)
+      }
+      this.timer = setTimeout(() => {
+        this.getUtmData()
+      }, 500)
+    },
+    changePage(page) {
+      this.page = page
+      this.getUtmData()
+    },
+    editAd() {
+      this.addData = Object.assign({}, this.currentAd)
+      this.isAdd = false
+      this.showAdd = true
+    },
+    addAd() {
+      if (this.addData.campaign_name === '') {
+        this.$notify({
+          title: '提示',
+          message: '请填写广告系列名称',
+          type: 'warning'
+        })
+        return
+      }
+      if (this.addData.website_url === '') {
+        this.$notify({
+          title: '提示',
+          message: '请选择展会',
+          type: 'warning'
+        })
+        return
+      }
+      if (this.loading) {
+        return
+      }
+      saveUtmDetail(this.addData.id, this.addData.website_url, this.addData.campaign_name, this.addData.campaign_id, this.addData.campaign_source, this.addData.campaign_term, this.addData.campaign_medium, this.addData.campaign_content)
+        .then(res => {
+          this.$notify({
+            title: '提示',
+            message: '添加成功',
+            type: 'success'
+          })
+          this.getUtmData()
+          this.loading = false
+        })
+        .catch(err => {
+          this.$notify({
+            title: '提示',
+            message: '添加失败:' + err,
+            type: 'error'
+          })
+          this.loading = false
+        })
+      this.showAdd = false
+    },
+    getExpoListData() {
+      getExpoList(1, 100, '').then(res => {
+        this.expoList = res.data.data
+      })
+    },
+    getExpoDetail() {
+      this.loading = true
+      getMyExpoInfo(this.currentExpo).then(res => {
+        const host = window.location.protocol + '//' + window.location.host + '/register/'
+        this.addData.website_url = host + res.data.urla
+        this.loading = false
+      }).catch(err => {
+        this.$notify({
+          title: '提示',
+          message: '获取展会信息失败:' + err,
+          type: 'error'
+        })
+        this.loading = false
+      })
+    },
+    rowClick(row) {
+      console.log(row)
+      this.currentAd = {}
+      this.echartsData.series[0].data[1].value = row.pv
+      this.echartsData.series[0].data[0].value = row.uv
+      this.echartsData.graphic[0].style.text = ((row.pv / row.uv) || 0).toFixed(2)
+      if (this.myChart) {
+        this.myChart.setOption(this.echartsData)
+      } else {
+        const dom = document.getElementById('form')
+        this.myChart = echarts.init(dom)
+        this.myChart.setOption(this.echartsData)
+      }
+      setTimeout(() => {
+        this.currentAd = row
+      }, 0)
+    },
+    shareAd(id) {
+      this.$router.push({ path: '/share&utm/share?utmid=' + id })
+    },
+    deleteAd(id) {
+      this.$confirm('确定要删除吗?', '提示', {
+        callback: (action) => {
+          if (action === 'confirm') {
+            deleteUtm(id).then(res => {
+              this.$notify({
+                title: '提示',
+                message: '删除成功',
+                type: 'success'
+              })
+              this.currentAd = {}
+              this.getUtmData()
+            }).catch(err => {
+              this.$notify({
+                title: '提示',
+                message: '删除失败:' + err,
+                type: 'error'
+              })
+            })
+          }
+        }
+      })
+    }
+  }
 })
 </script>
 
 <template>
+  <div class="utm">
+    <div class="list">
+      <div class="head">
+        <el-input v-model="searchData.campaign_name" class="search" placeholder="活动名称搜索" @input="inputSearch()" />
+        <el-input v-model="searchData.campaign_id" class="search" placeholder="系列ID搜索" @input="inputSearch()" />
+        <el-button class="add" icon="el-icon-plus" type="primary" @click="openAdd()">新建广告</el-button>
+      </div>
+      <div v-loading="loading" class="body">
+        <el-table :data="adList" height="100%" class="table" @row-click="rowClick">
+          <el-table-column label="ID" prop="id" width="60" />
+          <el-table-column label="广告系列" prop="campaign_name" width="200" />
+          <el-table-column label="浏览量" prop="pv" width="80" />
+          <el-table-column label="访客数" prop="uv" width="80" />
+          <el-table-column label="广告系列ID" prop="campaign_id" width="200" />
+          <el-table-column label="广告来源" prop="campaign_source" width="200" />
+          <el-table-column label="广告媒介" prop="campaign_medium" width="200" />
+          <el-table-column label="广告内容" prop="campaign_content" width="200" />
+          <el-table-column label="广告字段" prop="campaign_term" width="200" />
+          <el-table-column label="更新时间" prop="update_time" width="200" />
+        </el-table>
 
+      </div>
+      <div class="foot">
+        <el-pagination
+          background
+          :page-size="10"
+          layout="total"
+          :total="total"
+        />
+        <el-pagination
+          v-permission="'exhibitor.handel'"
+          background
+          :page-size="10"
+          layout="prev, pager, next"
+          :total="total"
+          @current-change="changePage"
+        />
+      </div>
+    </div>
+    <div class="view-cont">
+      <div class="view">
+        <div class="head">
+          <div class="name">{{ currentAd.campaign_name || '请选择广告' }}</div>
+        </div>
+        <div :class="['ad-info',currentAd.id?'show':'hide']">
+          <el-button v-show="currentAd.id" class="edit" icon="el-icon-edit-outline" circle @click="editAd()" />
+          <div>广告信息</div>
+          <div class="info-list">
+            <div class="item">
+              <div class="label">ID:</div>
+              <div class="value">{{ currentAd.id }}</div>
+            </div>
+            <div class="item">
+              <div class="label">浏览量:</div>
+              <div class="value">{{ currentAd.pv }}</div>
+            </div>
+            <div class="item">
+              <div class="label">访客数:</div>
+              <div class="value">{{ currentAd.uv }}</div>
+            </div>
+            <div class="item">
+              <div class="label">原链接:</div>
+              <div class="value">{{ currentAd.website_url }}</div>
+            </div>
+            <div class="item">
+              <div class="label">短链接:</div>
+              <div class="value">{{ currentAd.short_url }}</div>
+            </div>
+            <div class="item">
+              <div class="label">系列ID:</div>
+              <div class="value">{{ currentAd.campaign_id }}</div>
+            </div>
+            <div class="item">
+              <div class="label">广告来源:</div>
+              <div class="value">{{ currentAd.campaign_source }}</div>
+            </div>
+            <div class="item">
+              <div class="label">广告媒介:</div>
+              <div class="value">{{ currentAd.campaign_medium }}</div>
+            </div>
+            <div class="item">
+              <div class="label">广告内容:</div>
+              <div class="value">{{ currentAd.campaign_content }}</div>
+            </div>
+            <div class="item">
+              <div class="label">广告字段:</div>
+              <div class="value">{{ currentAd.campaign_term }}</div>
+            </div>
+            <div class="item">
+              <div class="label">更新时间:</div>
+              <div class="value">{{ currentAd.update_time }}</div>
+            </div>
+          </div>
+        </div>
+        <div :class="['info-card',currentAd.id?'show':'hide']">
+          <div>人均浏览量</div>
+          <div id="form" class="charts" />
+          <div class="desc">
+            <span v-if="currentAd.pv/currentAd.uv < 1.5">
+              大部分访客仅浏览一个页面后就离开了,也许您需要优化展会的宣传内容。
+            </span>
+            <span v-else-if="currentAd.pv/currentAd.uv > 1.5 && currentAd.pv/currentAd.uv < 3">
+              部分访客进入网站后又浏览了其它页面,看来访客对您的展会有兴趣!
+            </span>
+            <span v-else-if="currentAd.pv/currentAd.uv > 3">
+              进入网站的访客平均浏览了三个以上的页面,访客对您的展会非常有兴趣!
+            </span>
+            <span v-else>
+              网站暂时还没有访客数据,请耐心等待。
+            </span>
+          </div>
+        </div>
+        <div :class="['handel-info',currentAd.id?'show':'hide']">
+          <div>操作面板</div>
+          <div class="button-list">
+            <el-button icon="el-icon-share" type="primary" @click="shareAd(currentAd.id)">分享广告</el-button>
+            <el-button icon="el-icon-edit-outline" type="primary" @click="editAd()">编辑广告</el-button>
+            <el-button icon="el-icon-delete" type="danger" @click="deleteAd(currentAd.id)">删除广告</el-button>
+          </div>
+        </div>
+      </div>
+    </div>
+    <el-dialog :title="isAdd?'添加广告':'编辑广告'" custom-class="utm-dialog" :close-on-click-modal="false" :append-to-body="true" :visible.sync="showAdd">
+      <div class="form">
+        <div v-show="isAdd" class="item">
+          <span class="label">展会(必选)</span>
+          <el-select v-model="currentExpo" @change="getExpoDetail()">
+            <el-option
+              v-for="item in expoList"
+              :key="item.id"
+              :label="item.expo_name"
+              :value="item.id"
+            />
+          </el-select>
+        </div>
+        <div v-show="isAdd" class="item">
+          <span class="label">展会网址(自动填写)</span>
+          <el-input :value="addData.website_url" />
+        </div>
+        <div class="item">
+          <span class="label">广告系列名称(必填)</span>
+          <el-input v-model="addData.campaign_name" />
+        </div>
+        <div class="item">
+          <span class="label">广告来源</span>
+          <el-input v-model="addData.campaign_source" />
+        </div>
+        <div class="item">
+          <span class="label">宣传媒介</span>
+          <el-input v-model="addData.campaign_medium" />
+        </div>
+        <div class="item">
+          <span class="label">广告内容</span>
+          <el-input v-model="addData.campaign_content" />
+        </div>
+        <div class="item">
+          <span class="label">广告系列ID</span>
+          <el-input v-model="addData.campaign_id" />
+        </div>
+        <div class="item">
+          <span class="label">广告字段</span>
+          <el-input v-model="addData.campaign_term" />
+        </div>
+      </div>
+      <span slot="footer" class="dialog-footer">
+        <el-button @click="showAdd=false">取 消</el-button>
+        <el-button v-loading="loading" type="primary" @click="addAd()">确 定</el-button>
+      </span>
+    </el-dialog>
+  </div>
 </template>
 
 <style scoped>
-
+  .utm{
+    overflow: hidden;
+    padding: 0 !important;
+    display: grid;
+    width: 100%;
+    height: 100%;
+    grid-template-columns: 1fr 360px;
+    .list{
+      display: grid;
+      grid-gap: 16px;
+      grid-template-rows: auto 1fr auto;
+      z-index: 1;
+      position: relative;
+      padding: 16px;
+      box-shadow: 0 1px 4px 0 #00000022;
+      .head{
+        gap: 16px;
+        display: flex;
+        .search{
+          width: 30%;
+        }
+        .add{
+          margin-left: auto;
+        }
+      }
+      .body{
+        height: 100%;
+        position: relative;
+        .table{
+          width: 100%;
+          height: 100%;
+          position: absolute;
+          top: 0;
+          left: 0;
+        }
+      }
+      .foot{
+        display: flex;
+        justify-content: space-between;
+      }
+    }
+    .view-cont{
+      position: relative;
+      width: 100%;
+      height: 100%;
+    }
+    .view{
+      padding: 16px;
+      border-radius: 0 16px 16px 0;
+      position: absolute;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+      overflow: hidden;
+      overflow-y: auto;
+      background-color: #F9FAFB;
+      .ad-info{
+        position: relative;
+        margin-top: 16px;
+        background-color: #FFFFFF;
+        box-shadow: 0 1px 4px 0 #00000011;
+        border-radius: 16px;
+        padding: 16px;
+        transition-duration: 300ms;
+        &.hide{
+          transition-duration: 0ms;
+          opacity: 0;
+          transform: translateY(10px);
+        }
+        .edit{
+          position: absolute;
+          right: 8px;
+          top: 8px;
+        }
+        .info-list{
+          color:  grey;
+          margin-top: 8px;
+          .item{
+            display: flex;
+            font-size: 14px;
+            .label{
+              width: 80px;
+              text-align: right;
+            }
+            .value{
+              width: 215px;
+              overflow: hidden;
+            }
+          }
+        }
+      }
+      .handel-info{
+        background-color: #FFFFFF;
+        box-shadow: 0 1px 4px 0 #00000011;
+        border-radius: 16px;
+        padding: 16px;
+        position: relative;
+        margin-top: 16px;
+        width: 100%;
+        transition-delay: 200ms;
+        transition-duration: 300ms;
+        .button-list{
+          display: flex;
+          flex-direction: column;
+          gap: 8px;
+          margin-top: 8px;
+          .el-button{
+            margin: 0;
+          }
+        }
+        &.hide{
+          transition-delay: 0ms;
+          transition-duration: 0ms;
+          opacity: 0;
+          transform: translateY(10px);
+        }
+      }
+      .info-card{
+        background-color: #FFFFFF;
+        box-shadow: 0 1px 4px 0 #00000011;
+        border-radius: 16px;
+        padding: 16px;
+        position: relative;
+        margin-top: 16px;
+        width: 100%;
+        transition-delay: 100ms;
+        transition-duration: 300ms;
+        &.hide{
+          transition-delay: 0ms;
+          transition-duration: 0ms;
+          opacity: 0;
+          transform: translateY(10px);
+        }
+        .charts{
+          position: relative;
+          width: 100%;
+          height: 260px;
+        }
+        .desc{
+          margin-top: 16px;
+          font-size: 14px;
+          color: grey;
+        }
+      }
+      .head{
+        display: flex;
+        align-items: center;
+        .name{
+          font-size: 18px;
+          font-weight: bold;
+          color: #111111;
+          overflow: hidden;
+          flex: 1;
+          margin-right: 16px;
+          text-overflow: ellipsis;
+          white-space: nowrap;
+          display: -webkit-box;
+          -webkit-box-orient: vertical;
+          -webkit-line-clamp: 1;
+        }
+      }
+    }
+  }
+</style>
+<style>
+  .utm-dialog{
+    .form{
+      display: flex;
+      flex-direction: column;
+      gap: 12px;
+      .item{
+        display: flex;
+        align-items: center;
+        .label{
+          width: 160px;
+        }
+        .el-select{
+          width: 100%;
+        }
+      }
+    }
+  }
 </style>