liyongli 2 years ago
parent
commit
5927df93a4
3 changed files with 725 additions and 0 deletions
  1. 12 0
      src/api/index.js
  2. 7 0
      src/router/index.js
  3. 706 0
      src/views/Keep/activeUser.vue

+ 12 - 0
src/api/index.js

@@ -947,6 +947,18 @@ export function getKeepPage(data) {
     data,
   });
 }
+/**
+ * 获取大数据平台活跃用户留存分析 表格;
+ * @return {AxjxPromise}
+ */
+export function getActivePage(data) {
+  return ajax({
+    urlType: "leverAudience",
+    url: "/cxzx-program/new-media/user/active-keep",
+    method: "POST",
+    data,
+  });
+}
 /**
  * 获取大数据平台新用户留存分析 表格;
  * @return {AxjxPromise}

+ 7 - 0
src/router/index.js

@@ -142,6 +142,13 @@ const routes = [
     component: () =>
       import(/* webpackChunkName: "Keep" */ "../views/Keep/index.vue"),
   },
+  //   活跃用户留存
+  {
+    path: "/activeUser",
+    name: "ActiveUser",
+    component: () =>
+      import(/* webpackChunkName: "ActiveKeep" */ "../views/Keep/activeUser.vue"),
+  },
   //   搜索分析
   {
     path: "/search",

+ 706 - 0
src/views/Keep/activeUser.vue

@@ -0,0 +1,706 @@
+<template>
+  <div class="activeUser">
+    <el-breadcrumb separator-class="el-icon-arrow-right">
+      <el-breadcrumb-item>新媒体</el-breadcrumb-item>
+      <el-breadcrumb-item>留存分析</el-breadcrumb-item>
+      <el-breadcrumb-item>活跃用户留存</el-breadcrumb-item>
+    </el-breadcrumb>
+    <el-card class="box-card">
+      <el-form
+        ref="form"
+        :model="form"
+        size="small"
+        :inline="true"
+        label-width="120px"
+        class="demo-form-inline"
+      >
+        <el-form-item label="日期">
+          <el-date-picker
+            v-if="form.date.length"
+            v-model="form.date"
+            type="daterange"
+            :disabled-date="time => disabledDate(time)"
+            range-separator="-"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+            :clearable="false"
+          >
+          </el-date-picker>
+        </el-form-item>
+        <el-form-item label="应用">
+          <el-select
+            v-model="form.app"
+            placeholder="请选择时段"
+            @change="change"
+          >
+            <el-option
+              v-for="item in cycle"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+              :disabled="item.disabled"
+            >
+            </el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="版本">
+          <el-select
+            filterable
+            multiple
+            collapse-tags
+            clearable
+            v-model="form.version"
+            placeholder="请选择版本"
+            @change="changeversion"
+          >
+            <el-option
+              v-for="item in version"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+              :disabled="item.disabled"
+            >
+            </el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="渠道">
+          <el-select
+            filterable
+            multiple
+            collapse-tags
+            clearable
+            v-model="form.channel"
+            @change="changechannel"
+            placeholder="请选择渠道"
+          >
+            <el-option
+              v-for="item in channel"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+              :disabled="item.disabled"
+            >
+            </el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item style="float: right">
+          <el-button type="primary" @click="onSubmit">查询</el-button>
+        </el-form-item>
+      </el-form>
+    </el-card>
+    <br />
+    <el-card class="box-card">
+      <div style="text-align: right">
+        <el-button-group>
+          <el-button
+            @click="() => keepTypeChange(2)"
+            size="small"
+            :type="form.keepType === 2 ? 'primary' : 'plain'"
+          >
+            留存率
+          </el-button>
+          <el-button
+            @click="() => keepTypeChange(1)"
+            size="small"
+            :type="form.keepType === 1 ? 'primary' : 'plain'"
+          >
+            用户数
+          </el-button>
+        </el-button-group>
+        <el-button
+          size="small"
+          style="margin-left: 0.5em"
+          type="primary"
+          @click="onExport"
+          >导出</el-button
+        >
+      </div>
+      <br />
+      <el-table
+        v-if="oriData.length"
+        :data="oriData"
+        style="width: 100%"
+        :header-cell-style="{ backgroundColor: '#f4f5f7', color: '#606266' }"
+      >
+        <el-table-column
+          header-align="center"
+          align="center"
+          prop="dt"
+          label="日期"
+        />
+        <el-table-column
+          header-align="center"
+          align="center"
+          prop="r0"
+          label="活跃用户数"
+        >
+          <template #default="scope">
+            <countTo
+              :startVal="scope.row.r0"
+              :endVal="scope.row.r0"
+              :duration="100"
+            ></countTo>
+          </template>
+        </el-table-column>
+        <el-table-column
+          header-align="center"
+          align="center"
+          prop="r1"
+          label="第1天"
+        >
+          <template #default="scope">
+            <div
+              class="keepNmuber"
+              :style="
+                scope.row.r1
+                  ? 'background-color: #69c0ff;color: #fff'
+                  : 'background-color: #ebf0ff;color: #ddd'
+              "
+            >
+              <countTo
+                v-if="form.keepType === 1"
+                :startVal="scope.row.r1"
+                :endVal="scope.row.r1"
+                :duration="100"
+              ></countTo>
+              <span v-else>{{ (scope.row.r1 * 100).toFixed(2) }}%</span>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column
+          header-align="center"
+          align="center"
+          prop="r2"
+          label="第2天"
+        >
+          <template #default="scope">
+            <div
+              class="keepNmuber"
+              :style="
+                scope.row.r2
+                  ? 'background-color: #69c0ff;color: #fff'
+                  : 'background-color: #ebf0ff;color: #ddd'
+              "
+            >
+              <countTo
+                v-if="form.keepType === 1"
+                :startVal="scope.row.r2"
+                :endVal="scope.row.r2"
+                :duration="100"
+              ></countTo>
+              <span v-else>{{ (scope.row.r2 * 100).toFixed(2) }}%</span>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column
+          header-align="center"
+          align="center"
+          prop="r3"
+          label="第3天"
+        >
+          <template #default="scope">
+            <div
+              class="keepNmuber"
+              :style="
+                scope.row.r3
+                  ? 'background-color: #69c0ff;color: #fff'
+                  : 'background-color: #ebf0ff;color: #ddd'
+              "
+            >
+              <countTo
+                v-if="form.keepType === 1"
+                :startVal="scope.row.r3"
+                :endVal="scope.row.r3"
+                :duration="100"
+              ></countTo>
+              <span v-else>{{ (scope.row.r3 * 100).toFixed(2) }}%</span>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column
+          header-align="center"
+          align="center"
+          prop="r4"
+          label="第4天"
+        >
+          <template #default="scope">
+            <div
+              class="keepNmuber"
+              :style="
+                scope.row.r4
+                  ? 'background-color: #69c0ff;color: #fff'
+                  : 'background-color: #ebf0ff;color: #ddd'
+              "
+            >
+              <countTo
+                v-if="form.keepType === 1"
+                :startVal="scope.row.r4"
+                :endVal="scope.row.r4"
+                :duration="100"
+              ></countTo>
+              <span v-else>{{ (scope.row.r4 * 100).toFixed(2) }}%</span>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column
+          header-align="center"
+          align="center"
+          prop="r5"
+          label="第5天"
+        >
+          <template #default="scope">
+            <div
+              class="keepNmuber"
+              :style="
+                scope.row.r5
+                  ? 'background-color: #69c0ff;color: #fff'
+                  : 'background-color: #ebf0ff;color: #ddd'
+              "
+            >
+              <countTo
+                v-if="form.keepType === 1"
+                :startVal="scope.row.r5"
+                :endVal="scope.row.r5"
+                :duration="100"
+              ></countTo>
+              <span v-else>{{ (scope.row.r5 * 100).toFixed(2) }}%</span>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column
+          header-align="center"
+          align="center"
+          prop="r6"
+          label="第6天"
+        >
+          <template #default="scope">
+            <div
+              class="keepNmuber"
+              :style="
+                scope.row.r6
+                  ? 'background-color: #69c0ff;color: #fff'
+                  : 'background-color: #ebf0ff;color: #ddd'
+              "
+            >
+              <countTo
+                v-if="form.keepType === 1"
+                :startVal="scope.row.r6"
+                :endVal="scope.row.r6"
+                :duration="100"
+              ></countTo>
+              <span v-else>{{ (scope.row.r6 * 100).toFixed(2) }}%</span>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column
+          header-align="center"
+          align="center"
+          prop="r7"
+          label="第7天"
+        >
+          <template #default="scope">
+            <div
+              class="keepNmuber"
+              :style="
+                scope.row.r7
+                  ? 'background-color: #69c0ff;color: #fff'
+                  : 'background-color: #ebf0ff;color: #ddd'
+              "
+            >
+              <countTo
+                v-if="form.keepType === 1"
+                :startVal="scope.row.r7"
+                :endVal="scope.row.r7"
+                :duration="100"
+              ></countTo>
+              <span v-else>{{ (scope.row.r7 * 100).toFixed(2) }}%</span>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column
+          header-align="center"
+          align="center"
+          prop="r15"
+          label="第15天"
+        >
+          <template #default="scope">
+            <div
+              class="keepNmuber"
+              :style="
+                scope.row.r15
+                  ? 'background-color: #69c0ff;color: #fff'
+                  : 'background-color: #ebf0ff;color: #ddd'
+              "
+            >
+              <countTo
+                v-if="form.keepType === 1"
+                :startVal="scope.row.r15"
+                :endVal="scope.row.r15"
+                :duration="100"
+              ></countTo>
+              <span v-else>{{ (scope.row.r15 * 100).toFixed(2) }}%</span>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column
+          header-align="center"
+          align="center"
+          prop="r30"
+          label="第30天"
+        >
+          <template #default="scope">
+            <div
+              class="keepNmuber"
+              :style="
+                scope.row.r30
+                  ? 'background-color: #69c0ff;color: #fff'
+                  : 'background-color: #ebf0ff;color: #ddd'
+              "
+            >
+              <countTo
+                v-if="form.keepType === 1"
+                :startVal="scope.row.r30"
+                :endVal="scope.row.r30"
+                :duration="100"
+              ></countTo>
+              <span v-else>{{ (scope.row.r30 * 100).toFixed(2) }}%</span>
+            </div>
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-card>
+  </div>
+</template>
+
+<script>
+// @ is an alias to /src
+import { getRule, getAppList, getActivePage, getSearchData } from "@/api/index";
+
+import countTo from "@/components/counto/vue-countTo.vue";
+
+// import config from "@/config/index";
+export default {
+  name: "activeUser",
+  data() {
+    return {
+      type: "",
+      lastParams: {},
+      form: {
+        app: "",
+        keepType: 1,
+        version: [],
+        channel: [],
+        date: [],
+      },
+      cycle: [],
+      oriData: [],
+      version: [],
+      channel: [],
+    };
+  },
+  async mounted() {
+    const { source, appV, appC, appli, appCLi, appVLi } =
+      await this.getAppListFunc();
+    const keys = {
+      value: "mname",
+      label: "mname",
+    };
+    this.cycle = this.verifyList(appli, source, keys, false);
+    this.channel = this.verifyList(appCLi, appC, keys, true);
+    this.version = this.verifyList(appVLi, appV, keys, true);
+    this.form = {
+      //   app: (this.cycle[0] || { value: "" }).value,
+      app: "起点新闻",
+      keepType: 1,
+      version: [(this.version[0] || { value: "" }).value],
+      channel: [(this.channel[0] || { value: "" }).value],
+      date: [new Date(Date.now() - 604800000), new Date(Date.now() - 86400000)],
+    };
+    this.onSubmit();
+  },
+  computed: {},
+  methods: {
+    keepTypeChange(type) {
+      this.form.keepType = type;
+      this.oriData = [];
+      this.onSubmit();
+    },
+    verifyList(list, verify, obj, more) {
+      if (!obj) return;
+      let li = list || [];
+      const out = [];
+      more && out.push({ value: -1, label: "不限" });
+      for (let i = 0; i < li.length; i++) {
+        const v = li[i];
+        if (verify.length !== 0 && !verify[v.mcode]) continue;
+        out.push({
+          value: v[obj.value],
+          label: v[obj.label],
+        });
+      }
+      return out;
+    },
+    onSubmit() {
+      this.lastParams = {
+        app: this.form.app,
+        keepType: this.form.keepType,
+        start: this.FormData(this.form.date[0]),
+        end: this.FormData(this.form.date[1]),
+        manufacturer: this.form.channel == -1 ? undefined : this.form.channel,
+        version: this.form.version == -1 ? undefined : this.form.version,
+      };
+      getActivePage(this.lastParams)
+        .then(r => {
+          let oriData = r || [];
+          this.oriData = oriData;
+        })
+        .catch(() => {
+          this.oriData = [];
+        });
+    },
+    async getAppListFunc() {
+      const { r, li, appVersion, channel } = await this.getAppListOri();
+      let source = { length: 0 },
+        appli = [];
+      let appV = { length: 0 },
+        appVLi = [];
+      let appC = { length: 0 },
+        appCLi = [];
+      let clentV = { length: 0 },
+        clentli = [];
+      let prvList = r.output.data.prvRolectrl || [];
+      for (let i = 0; i < prvList.length; i++) {
+        const v = prvList[i];
+        if (v.controlid == "RMT_SOURCE")
+          (source[v.detid] = true), (source.length = source.length + 1);
+        if (v.controlid == "APP_VERSION")
+          (appV[v.detid] = true), (appV.length = appV.length + 1);
+        if (v.controlid == "CHANNEL")
+          (appC[v.detid] = true), (appC.length = appC.length + 1);
+      }
+      if (li.status === "0") appli = li.output.data || [];
+      if (appVersion.length) appVLi = appVersion || [];
+      if (channel.length) appCLi = channel || [];
+      return {
+        source,
+        appV,
+        appC,
+        appli,
+        clentV,
+        appVLi,
+        appCLi,
+        clentli,
+      };
+    },
+    async getAppListOri() {
+      const roleid = JSON.parse(
+        window.parent.localStorage.userinfo || "{}"
+      ).roleid;
+      const r = await getRule({
+        db: "authplat",
+        exportMark: "0",
+        menuid: 399,
+        roleid,
+      });
+      //   应用列表
+
+      const li = await getAppList({
+        exportMark: "0",
+        gcode: "SOURCE",
+        pageid: 1,
+        pagesize: 1000,
+      });
+      const defaultAppName = "起点新闻";
+      //   应用版本列表
+      const appVersion = await getSearchData({
+        gcode: "APP_VERSION",
+        source: defaultAppName,
+      });
+      //   应用渠道列表
+      const channel = await getSearchData({
+        gcode: "CHANNEL",
+        source: defaultAppName,
+      });
+      return { r, li, appVersion, channel };
+    },
+    disabledDate(time) {
+      const first = new Date("2021-06-21 00:00:00");
+      return (
+        time.getTime() > Date.now() - 86400000 ||
+        time.getTime() < first.getTime()
+      );
+    },
+    FormData(date) {
+      const d = new Date(date || Date.now() - 86400000);
+      const year = d.getFullYear();
+      const month =
+        d.getMonth() <= 8 ? "0" + (d.getMonth() + 1) : d.getMonth() + 1;
+      const day = d.getDate() <= 9 ? "0" + d.getDate() : d.getDate();
+      return [year, month, day].join("-");
+    },
+    timeFormat(t) {
+      const Time = t || 0;
+      const mH = Time % 3600;
+      let hour = (Time - mH) / 3600;
+      let min = (mH - (mH % 60)) / 60;
+      let son = Number(mH % 60).toFixed(0);
+      hour = hour <= 9 ? "0" + hour : hour;
+      min = min <= 9 ? "0" + min : min;
+      son = son <= 9 ? "0" + son : son;
+      let out = [];
+      if (hour * 1 > 0) out.push(hour);
+      out.push(...[min, son]);
+      return out.join(":");
+    },
+    change() {
+      const roleid = JSON.parse(
+        window.parent.localStorage.userinfo || "{}"
+      ).roleid;
+      const appV = {
+        length: 0,
+      };
+      getRule({
+        db: "authplat",
+        exportMark: "0",
+        menuid: 399,
+        roleid,
+      }).then(rule => {
+        let prvList = rule.output.data.prvRolectrl || [];
+        for (let i = 0; i < prvList.length; i++) {
+          const v = prvList[i];
+          if (v.controlid == "APP_VERSION")
+            (appV[v.detid] = true), (appV.length = appV.length + 1);
+        }
+        getSearchData({
+          gcode: "APP_VERSION",
+          source: this.form.app,
+        }).then(r => {
+          let version = [
+            {
+              label: "不限",
+              value: -1,
+            },
+          ];
+          r.map(v => {
+            if ((appV.length && appV[v.mcode]) || appV.length === 0)
+              version.push({
+                value: v.mname,
+                label: v.mname,
+              });
+          });
+          this.version = version;
+          this.form = {
+            ...this.form,
+            version: [-1],
+          };
+        });
+      });
+    },
+    changeversion(v) {
+      if (!v.length) return (this.form.version = [-1]);
+      const last = v[v.length - 1];
+      if (last == -1) return (this.form.version = [-1]);
+      let ver = [];
+      for (let i = 0; i < v.length; i++) {
+        const element = v[i];
+        if (element == -1) continue;
+        ver.push(element);
+      }
+      this.form.version = ver;
+    },
+    changechannel(v) {
+      if (!v.length) return (this.form.channel = [-1]);
+      const last = v[v.length - 1];
+      if (last == -1) return (this.form.channel = [-1]);
+      let ver = [];
+      for (let i = 0; i < v.length; i++) {
+        const element = v[i];
+        if (element == -1) continue;
+        ver.push(element);
+      }
+      this.form.channel = ver;
+    },
+    onExport() {
+      const S = this.form.date[0]
+        ? this.FormData(this.form.date[0])
+        : undefined;
+      const E = this.form.date[1]
+        ? this.FormData(this.form.date[1])
+        : undefined;
+      let p = this.lastParams.app
+        ? this.lastParams
+        : {
+            app: this.form.app,
+            start: S,
+            end: E,
+            manufacturer:
+              this.form.channel == -1 ? undefined : this.form.channel,
+            version: this.form.version == -1 ? undefined : this.form.version,
+          };
+      getActivePage(p).then(r => {
+        // 生成数据
+        let strcsv =
+          "data:text/csv;charset=utf-8,\uFEFF日期,活跃用户数,第1天,第2天,第3天,第4天,第5天,第6天,第7天,第15天,第30天\r\n";
+        (r || []).map(v => {
+          strcsv += [
+            v.dt,
+            v.r0,
+            v.r1,
+            v.r2,
+            v.r3,
+            v.r4,
+            v.r5,
+            v.r6,
+            v.r7,
+            v.r15,
+            v.r30,
+            "\r\n",
+          ].join(",");
+        });
+        // 导出
+        let link = document.createElement("a");
+        link.id = "download-csv";
+        link.setAttribute("href", encodeURI(strcsv));
+        link.setAttribute(
+          "download",
+          p.app + "活跃用户留存" + S + "_" + E + ".csv"
+        );
+        // document.body.appendChild(link);
+        link.click();
+        link = undefined;
+      });
+    },
+  },
+  components: {
+    countTo,
+  },
+};
+</script>
+
+<style>
+.activeUser {
+  margin: 10px 15px;
+}
+.activeUser .has-seconds .el-time-spinner__wrapper:last-child {
+  display: none;
+}
+.head {
+  display: flex;
+  font-weight: 500;
+}
+.head .head-item {
+  flex: 1;
+  text-align: center;
+  font-size: 0.8em;
+  border-top: 3px solid #fff;
+  padding-top: 10px;
+}
+.head .value {
+  margin: 15px 0;
+  color: #396fff;
+  font-size: 25px;
+}
+.keepNmuber {
+  padding: 3px 0;
+}
+</style>