Bladeren bron

app月度报表统计,发货权限配置,公开课流量统计

wangxy 8 uur geleden
bovenliggende
commit
ec58f0400a

+ 9 - 1
src/api/course/courseTrafficLog.js

@@ -50,4 +50,12 @@ export function exportCourseTrafficLog(query) {
     method: 'get',
     params: query
   })
-}
+}
+
+// 获取公开课列表
+export function commonCourseList() {
+  return request({
+    url: '/course/courseRedPacketLog/publicCourseList',
+    method: 'get'
+  })
+}

+ 14 - 4
src/views/course/courseTrafficLog/index.vue

@@ -30,8 +30,8 @@
         </el-select>
       </el-form-item>
       <el-form-item label="公开课" v-if="activeTab === 'all' || activeTab === 'common'">
-        <el-select v-model="queryParams.courseId" placeholder="请选择课" filterable clearable size="small">
-          <el-option v-for="dict in courseLists" :key="dict.dictValue" :label="dict.dictLabel" :value="dict.dictValue" />
+        <el-select v-model="queryParams.courseId" placeholder="请选择公开课" filterable clearable size="small">
+          <el-option v-for="dict in commonCourseLists" :key="dict.dictValue" :label="dict.dictLabel" :value="dict.dictValue" />
         </el-select>
       </el-form-item>
 
@@ -75,7 +75,7 @@
     <el-table border v-loading="loading" :data="courseTrafficLogList" @selection-change="handleSelectionChange" show-summary :summary-method="getSummaries">
       <el-table-column type="selection" width="55" align="center" />
       <!-- 公司列 -->
-      <el-table-column label="公司" align="center" prop="companyName" />
+      <el-table-column label="公司" align="center" prop="companyName" v-if="activeTab !== 'common'" />
       <!-- 项目列 -->
       <el-table-column label="项目" align="center" prop="projectName" v-if="activeTab === 'all' || activeTab === 'project'" />
       <!-- 课程列 -->
@@ -100,7 +100,7 @@
 </template>
 
 <script>
-import { listCourseTrafficLog, exportCourseTrafficLog } from "@/api/course/courseTrafficLog";
+import { listCourseTrafficLog, exportCourseTrafficLog, commonCourseList } from "@/api/course/courseTrafficLog";
 import { courseList } from "@/api/course/courseRedPacketLog";
 import { allList } from "@/api/company/company";
 
@@ -152,6 +152,7 @@ export default {
         ]
       },
       courseLists: [],
+      commonCourseLists: [],
       loading: false,
       exportLoading: false,
       showSearch: true,
@@ -171,8 +172,17 @@ export default {
     };
   },
   created() {
+    const today = new Date();
+    const year = today.getFullYear();
+    const month = String(today.getMonth() + 1).padStart(2, '0');
+    const day = String(today.getDate()).padStart(2, '0');
+    const todayStr = `${year}-${month}-${day}`;
+    this.timeRange = [todayStr, todayStr];
+    this.queryParams.startDate = todayStr;
+    this.queryParams.endDate = todayStr;
     this.queryParams.tabType = "project";
     courseList().then(res => this.courseLists = res.list);
+    commonCourseList().then(res => this.commonCourseLists = res.list || res.data || []);
     this.getDicts("sys_course_project").then(res => this.projectOptions = res.data);
     this.getAllCompany();
     this.getList();

+ 215 - 0
src/views/his/statistics/appOperationReport.vue

@@ -0,0 +1,215 @@
+<template>
+  <div class="app-container">
+    <div class="period-switch">
+      <el-radio-group v-model="periodType" @change="handlePeriodChange">
+        <el-radio-button label="current">本月</el-radio-button>
+        <el-radio-button label="last">上月</el-radio-button>
+      </el-radio-group>
+      <span class="period-label">{{ periodLabel }}</span>
+    </div>
+
+    <el-row :gutter="20" class="card-row">
+      <el-col :span="6">
+        <el-card shadow="hover" class="metric-card">
+          <div class="metric-icon" style="background: #409EFF;">
+            <i class="el-icon-user"></i>
+          </div>
+          <div class="metric-content">
+            <div class="metric-value">{{ reportData.newUsers || 0 }}</div>
+            <div class="metric-label">新增用户数</div>
+          </div>
+        </el-card>
+      </el-col>
+      <el-col :span="6">
+        <el-card shadow="hover" class="metric-card">
+          <div class="metric-icon" style="background: #67C23A;">
+            <i class="el-icon-s-custom"></i>
+          </div>
+          <div class="metric-content">
+            <div class="metric-value">{{ reportData.totalUsers || 0 }}</div>
+            <div class="metric-label">累计注册用户</div>
+          </div>
+        </el-card>
+      </el-col>
+      <el-col :span="6">
+        <el-card shadow="hover" class="metric-card">
+          <div class="metric-icon" style="background: #E6A23C;">
+            <i class="el-icon-s-data"></i>
+          </div>
+          <div class="metric-content">
+            <div class="metric-value">{{ reportData.activeUsers || 0 }}</div>
+            <div class="metric-label">活跃用户数</div>
+          </div>
+        </el-card>
+      </el-col>
+      <el-col :span="6">
+        <el-card shadow="hover" class="metric-card">
+          <div class="metric-icon" style="background: #F56C6C;">
+            <i class="el-icon-connection"></i>
+          </div>
+          <div class="metric-content">
+            <div class="metric-value">{{ reportData.retentionRate != null ? reportData.retentionRate + '%' : '-' }}</div>
+            <div class="metric-label">留存率</div>
+          </div>
+        </el-card>
+      </el-col>
+    </el-row>
+
+    <el-row :gutter="20" class="card-row">
+      <el-col :span="6">
+        <el-card shadow="hover" class="metric-card">
+          <div class="metric-icon" style="background: #909399;">
+            <i class="el-icon-time"></i>
+          </div>
+          <div class="metric-content">
+            <div class="metric-value">{{ reportData.avgUseDuration != null ? reportData.avgUseDuration + ' 分钟' : '-' }}</div>
+            <div class="metric-label">用户平均使用时长</div>
+          </div>
+        </el-card>
+      </el-col>
+      <el-col :span="6">
+        <el-card shadow="hover" class="metric-card">
+          <div class="metric-icon" style="background: #9C27B0;">
+            <i class="el-icon-coin"></i>
+          </div>
+          <div class="metric-content">
+            <div class="metric-value">{{ reportData.ltv != null ? '¥' + reportData.ltv : '-' }}</div>
+            <div class="metric-label">用户生命周期价值(LTV)</div>
+          </div>
+        </el-card>
+      </el-col>
+      <el-col :span="6">
+        <el-card shadow="hover" class="metric-card">
+          <div class="metric-icon" style="background: #FF9800;">
+            <i class="el-icon-money"></i>
+          </div>
+          <div class="metric-content">
+            <div class="metric-value">{{ reportData.cac != null ? '¥' + reportData.cac : '-' }}</div>
+            <div class="metric-label">用户获取成本(CAC)</div>
+          </div>
+        </el-card>
+      </el-col>
+      <el-col :span="6">
+        <el-card shadow="hover" class="metric-card">
+          <div class="metric-icon" style="background: #795548;">
+            <i class="el-icon-warning-outline"></i>
+          </div>
+          <div class="metric-content">
+            <div class="metric-value">{{ reportData.monthlyChurnRate != null ? reportData.monthlyChurnRate + '%' : '-' }}</div>
+            <div class="metric-label">月流失率</div>
+          </div>
+        </el-card>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import request from '@/utils/request'
+
+export default {
+  name: "AppOperationReport",
+  data() {
+    return {
+      periodType: "current",
+      reportData: {},
+      year: null,
+      month: null
+    };
+  },
+  computed: {
+    periodLabel() {
+      return this.year + '年' + this.month + '月';
+    }
+  },
+  created() {
+    this.initPeriod();
+    this.getData();
+  },
+  methods: {
+    initPeriod() {
+      const now = new Date();
+      if (this.periodType === 'current') {
+        this.year = now.getFullYear();
+        this.month = now.getMonth() + 1;
+      } else {
+        const lastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1);
+        this.year = lastMonth.getFullYear();
+        this.month = lastMonth.getMonth() + 1;
+      }
+    },
+    handlePeriodChange() {
+      this.initPeriod();
+      this.getData();
+    },
+    getData() {
+      request({
+        url: '/his/appOperationReport/monthReport',
+        method: 'get',
+        params: {
+          year: this.year,
+          month: this.month
+        }
+      }).then(response => {
+        this.reportData = response.data || {};
+      });
+    }
+  }
+};
+</script>
+
+<style scoped>
+.period-switch {
+  margin-bottom: 20px;
+  display: flex;
+  align-items: center;
+}
+.period-label {
+  margin-left: 20px;
+  font-size: 16px;
+  font-weight: bold;
+  color: #303133;
+}
+.card-row {
+  margin-bottom: 20px;
+}
+.metric-card {
+  display: flex;
+  align-items: center;
+  padding: 20px;
+}
+.metric-card >>> .el-card__body {
+  display: flex;
+  align-items: center;
+  width: 100%;
+  padding: 20px;
+}
+.metric-icon {
+  width: 60px;
+  height: 60px;
+  border-radius: 12px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-shrink: 0;
+}
+.metric-icon i {
+  font-size: 28px;
+  color: #fff;
+}
+.metric-content {
+  margin-left: 20px;
+  flex: 1;
+}
+.metric-value {
+  font-size: 24px;
+  font-weight: bold;
+  color: #303133;
+  line-height: 1.2;
+}
+.metric-label {
+  font-size: 13px;
+  color: #909399;
+  margin-top: 6px;
+}
+</style>

+ 27 - 2
src/views/his/store/index.vue

@@ -338,6 +338,15 @@
           </el-radio-group>
         </el-form-item>
 
+        <el-form-item label="发货连锁品牌" prop="chainBrand">
+          <el-select v-model="form.chainBrand" multiple placeholder="请选择发货连锁品牌" size="small" style="width: 100%">
+            <el-option label="红德堂" value="红德堂" />
+            <el-option label="优身" value="优身" />
+            <el-option label="乘济" value="乘济" />
+            <el-option label="诚质" value="诚质" />
+          </el-select>
+        </el-form-item>
+
           <el-form-item label="登录帐号" prop="account" v-if="title != '修改店铺管理'">
             <el-input v-model="form.account" placeholder="请输入登录帐号" />
           </el-form-item>
@@ -468,8 +477,17 @@ shippingType:[],
       ],
       phone:  [
           {
-            pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/,
-            message: "请输入正确的手机号码",
+            validator: (rule, value, callback) => {
+              if (!value) {
+                callback();
+              } else if (value.includes('*')) {
+                callback();
+              } else if (!/^1[3|4|5|6|7|8|9][0-9]\d{8}$/.test(value)) {
+                callback(new Error("请输入正确的手机号码"));
+              } else {
+                callback();
+              }
+            },
             trigger: "blur"
           }
         ],
@@ -638,6 +656,10 @@ shippingType:[],
         if(this.form.deliveryType!=null ){
           this.form.deliveryType=JSON.stringify(this.form.deliveryType)
         }
+        if (this.form.chainBrands) {
+          this.form.chainBrand = this.form.chainBrands.split(',');
+        }
+        this.handleDeliveryTypeChange(this.form.deliveryType);
 
 
 
@@ -651,6 +673,9 @@ shippingType:[],
         if (valid) {
           this.form.cityIds=(this.form.cityIds).toString()
           this.form.shippingType=(this.form.shippingType).toString()
+          if (this.form.chainBrand && Array.isArray(this.form.chainBrand)) {
+            this.form.chainBrands = this.form.chainBrand.join(',');
+          }
           if (this.form.storeId != null) {
             updateStore(this.form).then(response => {
               this.msgSuccess("修改成功");

+ 30 - 2
src/views/his/storeProduct/index.vue

@@ -276,11 +276,39 @@
               </el-form-item>
             </el-col>
             <el-col :span="12">
-              <el-form-item label="单位名" prop="unitName">
-                <el-input v-model="form.unitName" placeholder="请输入单位名" />
+              <el-form-item label="商品来源" prop="productSourceType">
+                <el-select v-model="form.productSourceType" placeholder="请选择商品来源" clearable size="small" style="width: 100%">
+                  <el-option label="大包品" :value="1" />
+                  <el-option label="自库品" :value="2" />
+                  <el-option label="免费品" :value="3" />
+                </el-select>
               </el-form-item>
             </el-col>
             </el-row>
+            <el-row v-if="form.productSourceType === 1">
+              <el-col :span="12">
+                <el-form-item label="连锁品牌" prop="chainBrand">
+                  <el-select v-model="form.chainBrand" placeholder="请选择连锁品牌" clearable size="small" style="width: 100%">
+                    <el-option label="红德堂" value="红德堂" />
+                    <el-option label="优身" value="优身" />
+                    <el-option label="乘济" value="乘济" />
+                    <el-option label="诚质" value="诚质" />
+                  </el-select>
+                </el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="单位名" prop="unitName">
+                  <el-input v-model="form.unitName" placeholder="请输入单位名" />
+                </el-form-item>
+              </el-col>
+            </el-row>
+            <el-row v-if="form.productSourceType !== 1">
+              <el-col :span="12">
+                <el-form-item label="单位名" prop="unitName">
+                  <el-input v-model="form.unitName" placeholder="请输入单位名" />
+                </el-form-item>
+              </el-col>
+            </el-row>
             <el-row>
               <el-col :span="12">
              <el-form-item label="状态" prop="isShow">

+ 1 - 1
src/views/his/user/index.vue

@@ -40,7 +40,7 @@
       <el-form-item label="app来源" prop="source">
         <el-input
           v-model="queryParams.source"
-          placeholder="请输入手机号码"
+          placeholder="请输入app来源"
           clearable
           size="small"
           @keyup.enter.native="handleQuery"