Bläddra i källkod

公域课数据统计 营期收藏 点播接口

yuhongqi 4 dagar sedan
förälder
incheckning
1583860ab3

+ 9 - 0
src/api/course/courseWatchComment.js

@@ -9,6 +9,15 @@ export function listCourseWatchComment(query) {
   })
 }
 
+// 查询公域视频下的看课评论列表
+export function listPublicCourseVideoWatchComment(query) {
+  return request({
+    url: '/course/userCourseComment/publicCourseVideoWatchComment/list',
+    method: 'get',
+    params: query
+  })
+}
+
 // // 查询看课评论详细
 // export function getCourseWatchComment(commentId) {
 //   return request({

+ 8 - 0
src/api/course/publicCourseWatchStat.js

@@ -31,3 +31,11 @@ export function exportPublicCourseWatchStatCatalog(query) {
     params: query
   })
 }
+
+export function listCatalogUserStudy(query) {
+  return request({
+    url: '/course/publicCourseWatchStat/catalog/userStudy',
+    method: 'get',
+    params: query
+  })
+}

+ 9 - 0
src/api/course/userCourse.js

@@ -9,6 +9,15 @@ export function listUserCourse(query) {
   })
 }
 
+// 查询课程列表
+export function publicListUserCourse(query) {
+  return request({
+    url: '/course/userCourse/publicList',
+    method: 'get',
+    params: query
+  })
+}
+
 // 查询课程详细
 export function getUserCourse(courseId) {
   return request({

+ 9 - 0
src/api/course/userCourseCategory.js

@@ -9,6 +9,15 @@ export function listUserCourseCategory(query) {
   })
 }
 
+// 查询课堂分类列表
+export function publicListUserCourseCategory(query) {
+  return request({
+    url: '/course/userCourseCategory/publicList',
+    method: 'get',
+    params: query
+  })
+}
+
 // 查询课堂分类详细
 export function getUserCourseCategory(cateId) {
   return request({

+ 36 - 0
src/api/his/userIntegral.js

@@ -0,0 +1,36 @@
+import request from '@/utils/request'
+
+// 查询积分记录列表
+export function listUserIntegralLogs(query) {
+  return request({
+    url: '/his/userIntegralLogs/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 校验用户是否存在
+export function checkUser(userId) {
+  return request({
+    url: '/user/integral/checkUser/' + userId,
+    method: 'get'
+  })
+}
+
+// 增加积分
+export function addIntegral(data) {
+  return request({
+    url: '/user/integral/add',
+    method: 'post',
+    data: data
+  })
+}
+
+// 扣除积分
+export function deductIntegral(data) {
+  return request({
+    url: '/user/integral/deduct',
+    method: 'post',
+    data: data
+  })
+}

+ 8 - 0
src/api/hisStore/storeProductPackage.js

@@ -69,3 +69,11 @@ export function packageBySearch(query) {
     params: query
   })
 }
+
+// 一键复制商品组合套餐
+export function copyStoreProductPackage(packageId) {
+  return request({
+    url: '/store/store/storeProductPackage/copy/' + packageId,
+    method: 'post'
+  })
+}

+ 2 - 2
src/views/components/course/courseWatchComment.vue

@@ -221,8 +221,8 @@ export default {
     getList() {
       this.loading = true;
       listCourseWatchComment(this.queryParams).then(response => {
-        this.courseWatchCommentList = response.rows.list;
-        this.total = response.rows.total;
+        this.courseWatchCommentList = response.rows;
+        this.total = response.total;
         this.loading = false;
       });
     },

+ 7 - 6
src/views/course/publiccourse/courseQuestionBank/index.vue

@@ -346,12 +346,13 @@ export default {
     },
     /** 点播配置:不校验答案时隐藏列表/表单中的答案相关项(与 validateAnswerWhenWatch 为 false / "0" 一致) */
     hideAnswerFields() {
-      const cfg = this.courseConfig;
-      if (!cfg || !Object.prototype.hasOwnProperty.call(cfg, 'validateAnswerWhenWatch')) {
-        return false;
-      }
-      const v = cfg.validateAnswerWhenWatch;
-      return v === false || v === '0' || v === 0 || v === 'false';
+      // const cfg = this.courseConfig;
+      // if (!cfg || !Object.prototype.hasOwnProperty.call(cfg, 'validateAnswerWhenWatch')) {
+      //   return false;
+      // }
+      // const v = cfg.validateAnswerWhenWatch;
+      // return v === false || v === '0' || v === 0 || v === 'false';
+      return false;
     },
     dynamicRules() {
       const r = { ...this.rules };

+ 8 - 7
src/views/course/publiccourse/courseWatchComment/index.vue

@@ -90,7 +90,7 @@
       <!--      <el-table-column label="父评论id" align="center" prop="parentId" />-->
       <el-table-column label="评论内容" align="center" prop="content" />
       <el-table-column label="所属课程" align="center" prop="courseName" />
-      <el-table-column label="所属小节" align="center" prop="title" />
+<!--      <el-table-column label="所属小节" align="center" prop="title" />-->
       <!--      <el-table-column label="弹幕状态" align="center" prop="status">-->
       <!--        <template slot-scope="scope">-->
       <!--          <el-tag :type="getStatusTagType(scope.row.status)">-->
@@ -230,7 +230,8 @@
 </template>
 
 <script>
-import { listCourseWatchComment, delCourseWatchComment, exportCourseWatchComment, addBlack,clearBlack,updateBarrageStatus } from "@/api/course/courseWatchComment";
+import { listPublicCourseVideoWatchComment, delCourseWatchComment, exportCourseWatchComment, addBlack,clearBlack,updateBarrageStatus } from "@/api/course/courseWatchComment";
+import { delUserCourseComment,exportUserCourseComment } from "@/api/course/userCourseComment";
 import { addKeyword } from "@/api/system/keyword";
 
 export default {
@@ -307,9 +308,9 @@ export default {
     getList() {
       this.loading = true;
       this.queryParams.cateType = this.pageCateType;
-      listCourseWatchComment(this.queryParams).then(response => {
-        this.courseWatchCommentList = response.rows.list;
-        this.total = response.rows.total;
+      listPublicCourseVideoWatchComment(this.queryParams).then(response => {
+        this.courseWatchCommentList = response.rows;
+        this.total = response.total;
         this.loading = false;
       });
     },
@@ -482,7 +483,7 @@ export default {
         cancelButtonText: "取消",
         type: "warning"
       }).then(function() {
-        return delCourseWatchComment(commentIds);
+        return delUserCourseComment(commentIds);
       }).then(() => {
         this.getList();
         this.msgSuccess("删除成功");
@@ -497,7 +498,7 @@ export default {
         type: "warning"
       }).then(() => {
         this.exportLoading = true;
-        return exportCourseWatchComment(queryParams);
+        return exportUserCourseComment(queryParams);
       }).then(response => {
         this.download(response.msg);
         this.exportLoading = false;

+ 94 - 4
src/views/course/publiccourse/courseWatchLog/index.vue

@@ -160,6 +160,53 @@
         />
       </el-tab-pane>
     </el-tabs>
+
+    <!-- 用户数据抽屉 -->
+    <el-drawer
+      :title="'用户数据 - ' + (drawerRow.catalogName || '')"
+      :visible.sync="drawerVisible"
+      direction="rtl"
+      size="720px"
+      :before-close="handleDrawerClose"
+    >
+      <el-form :inline="true" :model="drawerQuery" size="small" style="margin-bottom:12px">
+        <el-form-item label="微信昵称">
+          <el-input v-model="drawerQuery.nickName" placeholder="请输入" clearable style="width:140px" />
+        </el-form-item>
+        <el-form-item label="手机号">
+          <el-input v-model="drawerQuery.phone" placeholder="请输入" clearable style="width:140px" />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" icon="el-icon-search" @click="getDrawerList">搜索</el-button>
+        </el-form-item>
+      </el-form>
+      <el-table v-loading="drawerLoading" border :data="drawerList" size="small">
+        <el-table-column label="用户ID" align="center" prop="userId" width="80" />
+        <el-table-column label="微信昵称" align="center" prop="nickName" min-width="100" show-overflow-tooltip />
+        <el-table-column label="手机号" align="center" prop="phone" width="120" />
+        <el-table-column label="累计学习时长" align="center" width="110">
+          <template slot-scope="scope">
+            <span>{{ formatSeconds(scope.row.totalDuration) }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="首次看课时间" align="center" prop="firstWatchTime" width="160" />
+        <el-table-column label="最近看课时间" align="center" prop="lastWatchTime" width="160" />
+        <el-table-column label="是否领取积分" align="center" prop="integralClaimed" width="110">
+          <template slot-scope="scope">
+            <el-tag :type="scope.row.integralClaimed === '是' ? 'success' : 'info'" size="small">
+              {{ scope.row.integralClaimed }}
+            </el-tag>
+          </template>
+        </el-table-column>
+      </el-table>
+      <pagination
+        v-show="drawerTotal > 0"
+        :total="drawerTotal"
+        :page.sync="drawerQuery.pageNum"
+        :limit.sync="drawerQuery.pageSize"
+        @pagination="getDrawerList"
+      />
+    </el-drawer>
   </div>
 </template>
 
@@ -168,7 +215,8 @@ import {
   listPublicCourseWatchStatCourseDay,
   listPublicCourseWatchStatCatalog,
   exportPublicCourseWatchStatCourseDay,
-  exportPublicCourseWatchStatCatalog
+  exportPublicCourseWatchStatCatalog,
+  listCatalogUserStudy
 } from '@/api/course/publicCourseWatchStat'
 import { listUserCourseCategory } from '@/api/course/userCourseCategory'
 
@@ -195,7 +243,20 @@ export default {
       courseTotal: 0,
       catalogLoading: false,
       catalogList: [],
-      catalogTotal: 0
+      catalogTotal: 0,
+      // 用户数据抽屉
+      drawerVisible: false,
+      drawerLoading: false,
+      drawerRow: {},
+      drawerList: [],
+      drawerTotal: 0,
+      drawerQuery: {
+        pageNum: 1,
+        pageSize: 10,
+        videoId: null,
+        nickName: null,
+        phone: null
+      }
     }
   },
   computed: {
@@ -328,8 +389,37 @@ export default {
         this.exportLoading = false
       })
     },
-    handleUserData() {
-      this.$modal.msg('用户数据功能开发中')
+    handleUserData(row) {
+      this.drawerRow = row
+      this.drawerQuery.videoId = row.videoId
+      this.drawerQuery.nickName = null
+      this.drawerQuery.phone = null
+      this.drawerQuery.pageNum = 1
+      this.drawerVisible = true
+      this.getDrawerList()
+    },
+    getDrawerList() {
+      this.drawerLoading = true
+      listCatalogUserStudy(this.drawerQuery).then(res => {
+        this.drawerList = res.rows
+        this.drawerTotal = res.total
+        this.drawerLoading = false
+      }).catch(() => {
+        this.drawerLoading = false
+      })
+    },
+    handleDrawerClose(done) {
+      this.drawerVisible = false
+      done()
+    },
+    formatSeconds(sec) {
+      if (!sec || sec <= 0) return '0秒'
+      const h = Math.floor(sec / 3600)
+      const m = Math.floor((sec % 3600) / 60)
+      const s = sec % 60
+      if (h > 0) return h + '时' + m + '分' + s + '秒'
+      if (m > 0) return m + '分' + s + '秒'
+      return s + '秒'
     }
   }
 }

+ 49 - 41
src/views/course/publiccourse/userCourseCategory/index.vue

@@ -10,8 +10,8 @@
           @keyup.enter.native="handleQuery"
         />
       </el-form-item>
-      <el-form-item label="是否推荐" prop="isShow">
-        <el-select v-model="queryParams.isShow" placeholder="是否推荐" clearable size="small">
+      <el-form-item label="状态" prop="isShow">
+        <el-select v-model="queryParams.isShow" placeholder="状态" clearable size="small">
           <el-option
             v-for="dict in orOptions"
             :key="dict.dictValue"
@@ -59,39 +59,39 @@
           v-hasPermi="['course:userCourseCategory:remove']"
         >删除</el-button>
       </el-col>
-      <el-col :span="1.5">
-        <el-button
-          type="warning"
-          plain
-          icon="el-icon-download"
-          size="mini"
-          :loading="exportLoading"
-          @click="handleExport"
-          v-hasPermi="['course:userCourseCategory:export']"
-        >导出</el-button>
-      </el-col>
-      <el-col :span="1.5">
-        <el-button
-          plain
-          type="info"
-          icon="el-icon-upload2"
-          size="mini"
-          @click="handleImport"
-          v-hasPermi="['course:userCourseCategory:importData']"
-        >导入</el-button>
-      </el-col>
+<!--      <el-col :span="1.5">-->
+<!--        <el-button-->
+<!--          type="warning"-->
+<!--          plain-->
+<!--          icon="el-icon-download"-->
+<!--          size="mini"-->
+<!--          :loading="exportLoading"-->
+<!--          @click="handleExport"-->
+<!--          v-hasPermi="['course:userCourseCategory:export']"-->
+<!--        >导出</el-button>-->
+<!--      </el-col>-->
+<!--      <el-col :span="1.5">-->
+<!--        <el-button-->
+<!--          plain-->
+<!--          type="info"-->
+<!--          icon="el-icon-upload2"-->
+<!--          size="mini"-->
+<!--          @click="handleImport"-->
+<!--          v-hasPermi="['course:userCourseCategory:importData']"-->
+<!--        >导入</el-button>-->
+<!--      </el-col>-->
 
-      <el-col :span="1.5">
-        <el-button
-          type="warning"
-          plain
-          icon="el-icon-download"
-          size="mini"
-          :loading="exportLoading"
-          @click="handleFansExport"
-          v-hasPermi="['course:userCourseCategory:fansExport']"
-        >重粉导出</el-button>
-      </el-col>
+<!--      <el-col :span="1.5">-->
+<!--        <el-button-->
+<!--          type="warning"-->
+<!--          plain-->
+<!--          icon="el-icon-download"-->
+<!--          size="mini"-->
+<!--          :loading="exportLoading"-->
+<!--          @click="handleFansExport"-->
+<!--          v-hasPermi="['course:userCourseCategory:fansExport']"-->
+<!--        >重粉导出</el-button>-->
+<!--      </el-col>-->
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
 
@@ -206,7 +206,18 @@
 </template>
 
 <script>
-import { listUserCourseCategory, getUserCourseCategory, delUserCourseCategory, addUserCourseCategory, updateUserCourseCategory, exportUserCourseCategory, exportFans,importTemplate, exportFail } from "@/api/course/userCourseCategory";
+import {
+  listUserCourseCategory,
+  getUserCourseCategory,
+  delUserCourseCategory,
+  addUserCourseCategory,
+  updateUserCourseCategory,
+  exportUserCourseCategory,
+  exportFans,
+  importTemplate,
+  exportFail,
+  publicListUserCourseCategory
+} from "@/api/course/userCourseCategory";
 import Treeselect from "@riophae/vue-treeselect";
 import "@riophae/vue-treeselect/dist/vue-treeselect.css";
 import { getToken } from '@/utils/auth'
@@ -217,7 +228,7 @@ export default {
   },
   data() {
     return {
-      orOptions:[],
+      orOptions:['显示','隐藏'],
       categoryOptions:[],
       // 遮罩层
       loading: true,
@@ -282,15 +293,12 @@ export default {
   },
   created() {
     this.getList();
-    this.getDicts("sys_company_or").then(response => {
-      this.orOptions = response.data;
-    });
   },
   methods: {
     /** 查询课堂分类列表 */
     getList() {
       this.loading = true;
-      listUserCourseCategory(this.queryParams).then(response => {
+      publicListUserCourseCategory(this.queryParams).then(response => {
         this.userCourseCategoryList = this.handleTree(response.data, "cateId", "pid");
         this.total = response.total;
         this.loading = false;
@@ -308,7 +316,7 @@ export default {
       };
     },
     getTreeselect() {
-      listUserCourseCategory({ cateType: 1 }).then(response => {
+      publicListUserCourseCategory({ cateType: 1 }).then(response => {
         this.categoryOptions = [];
         const data = { cateId: 0, cateName: '顶级目录', children: [] };
         data.children = response.data.filter(item => item.pid == 0)

+ 20 - 8
src/views/course/publiccourse/usercourse/index.vue

@@ -253,6 +253,13 @@
             </el-form-item>
           </el-col>
         </el-row>
+        <el-row>
+          <el-col :span="24">
+            <el-form-item label="观看数" prop="views">
+              <el-input-number v-model="form.views"  :min="0"  label="观看数"></el-input-number>
+            </el-form-item>
+          </el-col>
+        </el-row>
         <el-row>
           <el-col :span="24">
             <el-form-item label="课堂简介" prop="description">
@@ -280,10 +287,10 @@
             <el-form-item :label="rec.label">
               <div class="rec-inline">
                 <el-checkbox v-model="form[rec.enabledKey]" :true-label="1" :false-label="0">启用</el-checkbox>
-                <el-select v-model="form[rec.modeKey]" placeholder="方式" clearable size="small" style="width: 120px; margin-left: 12px;">
-                  <el-option label="插入" :value="1"/>
-                  <el-option label="替换" :value="2"/>
-                </el-select>
+<!--                <el-select v-model="form[rec.modeKey]" placeholder="方式" clearable size="small" style="width: 120px; margin-left: 12px;">-->
+<!--                  <el-option label="插入" :value="1"/>-->
+<!--                  <el-option label="替换" :value="2"/>-->
+<!--                </el-select>-->
                 <el-input-number v-model="form[rec.sortKey]" :min="0" :controls="true" placeholder="位置序号" size="small" style="width: 160px; margin-left: 12px;"/>
               </div>
             </el-form-item>
@@ -483,7 +490,7 @@ import {
   copyUserCourse,
   putOn,
   pullOff, updateUserCourseRedPage,
-  editConfig
+  editConfig, publicListUserCourse
 } from '@/api/course/userCourse'
 
 import {getSelectableRange} from "@/api/qw/sopTemp";
@@ -493,7 +500,12 @@ import Editor from '@/components/Editor/wang';
 import ImageUpload from '@/components/ImageUpload/index';
 import {listBySearch} from "@/api/course/userTalent";
 import userCourseCatalogDetails from '@/views/components/course/userCourseCatalogDetails.vue';
-import {getAllCourseCategoryList, getCatePidList, getCateListByPid} from "@/api/course/userCourseCategory";
+import {
+  getAllCourseCategoryList,
+  getCatePidList,
+  getCateListByPid,
+  getPublicCatePidList
+} from "@/api/course/userCourseCategory";
 import {allList} from "@/api/company/company";
 import VideoUpload from '@/components/VideoUpload/index.vue'
 import { getConfigByKey } from '@/api/system/config'
@@ -717,7 +729,7 @@ export default {
     }).catch(res=>{
 
     })
-    getCatePidList().then(response => {
+    getPublicCatePidList().then(response => {
       this.categoryOptions = response.data;
     });
 
@@ -818,7 +830,7 @@ export default {
     getList() {
       this.loading = true;
       this.queryParams.isPrivate = this.pageIsPrivate;
-      listUserCourse(this.queryParams).then(response => {
+      publicListUserCourse(this.queryParams).then(response => {
         this.userCourseList = response.rows;
         this.total = response.total;
         this.loading = false;

+ 2 - 2
src/views/course/publiccourse/videoResource/index.vue

@@ -827,7 +827,7 @@ import {
   listVideoResource,
   updateVideoResource,
   batchAddVideoResource,
-  batchUpdateVideoResource
+  batchUpdateVideoResource, listPublicVideoResource
 } from '@/api/course/videoResource'
 import {listUserCourseCategory,getPublicCatePidList,getPublicCateListByPid} from '@/api/course/userCourseCategory'
 import {getByIds, listCourseQuestionBank} from '@/api/course/courseQuestionBank'
@@ -1050,7 +1050,7 @@ export default {
     getList() {
       this.loading = true;
       this.queryParams.videoType = this.pageVideoType;
-      listVideoResource(this.queryParams).then(response => {
+      listPublicVideoResource(this.queryParams).then(response => {
         this.resourceList = response.rows;
         this.total = response.total;
         this.loading = false;

+ 280 - 0
src/views/his/userIntegral/index.vue

@@ -0,0 +1,280 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="90px">
+      <el-form-item label="用户id" prop="userId">
+        <el-input
+          v-model="queryParams.userId"
+          placeholder="请输入用户id"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="用户电话" prop="phone">
+        <el-input
+          v-model="queryParams.phone"
+          placeholder="请输入用户电话"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="类别" prop="logType">
+        <el-select v-model="queryParams.logType" placeholder="请选择类别" clearable size="small">
+          <el-option
+            v-for="dict in integralLogTypeOptions"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="dict.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="时间" prop="createTime">
+        <el-date-picker v-model="createTime" size="small" style="width: 220px" value-format="yyyy-MM-dd" type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" @change="changeTime" />
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          type="success"
+          plain
+          icon="el-icon-plus"
+          size="mini"
+          @click="handleAddIntegral"
+          v-hasPermi="['user:integral:add']"
+        >增加积分</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="danger"
+          plain
+          icon="el-icon-minus"
+          size="mini"
+          @click="handleDeductIntegral"
+          v-hasPermi="['user:integral:deduct']"
+        >扣除积分</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table v-loading="loading" border :data="integralLogsList">
+      <el-table-column label="用户id" align="center" prop="userId" />
+      <el-table-column label="用户昵称" align="center" prop="nickName" />
+      <el-table-column label="用户电话" align="center" prop="phone" />
+      <el-table-column label="类别" align="center" prop="logType">
+        <template slot-scope="scope">
+          <dict-tag :options="integralLogTypeOptions" :value="scope.row.logType"/>
+        </template>
+      </el-table-column>
+      <el-table-column label="积分" align="center" prop="integral" />
+      <el-table-column label="积分余额" align="center" prop="balance" />
+      <el-table-column label="订单关联id" align="center" prop="businessId" />
+      <el-table-column label="时间" align="center" prop="createTime" width="160" />
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+    <!-- 增加/扣除积分弹窗 -->
+    <el-dialog :title="integralDialogTitle" :visible.sync="integralDialogOpen" width="500px" append-to-body>
+      <el-form ref="integralForm" :model="integralForm" :rules="integralRules" label-width="100px">
+        <el-form-item label="用户ID" prop="userId">
+          <el-input v-model="integralForm.userId" placeholder="请输入用户ID" @blur="handleCheckUser" />
+        </el-form-item>
+        <el-form-item label="用户信息" v-if="checkedUserInfo">
+          <span>{{ checkedUserInfo.nickName }} / {{ checkedUserInfo.phone }} / 当前积分:{{ checkedUserInfo.integral }}</span>
+        </el-form-item>
+        <el-form-item label="类别">
+          <span>{{ integralForm.type === 'add' ? '管理员后台添加' : '管理员后台删减' }}</span>
+        </el-form-item>
+        <el-form-item :label="integralForm.type === 'add' ? '增加积分' : '扣除积分'" prop="integral">
+          <el-input-number v-model="integralForm.integral" :min="1" :step="1" placeholder="请输入积分数量" style="width: 100%;" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitIntegralForm" :disabled="!checkedUserInfo">确 定</el-button>
+        <el-button @click="cancelIntegralDialog">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { listUserIntegralLogs, checkUser, addIntegral, deductIntegral } from "@/api/his/userIntegral";
+
+export default {
+  name: "UserIntegral",
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 积分类别字典
+      integralLogTypeOptions: [],
+
+      // 总条数
+      total: 0,
+      // 积分记录表格数据
+      integralLogsList: [],
+      // 时间范围
+      createTime: null,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        userId: null,
+        phone: null,
+        logType: null,
+        sTime: null,
+        eTime: null
+      },
+      // 增加/扣除积分弹窗
+      integralDialogOpen: false,
+      integralDialogTitle: "",
+      checkedUserInfo: null,
+      integralForm: {
+        userId: null,
+        logType: null,
+        integral: null,
+        type: "add"
+      },
+      integralRules: {
+        userId: [
+          { required: true, message: "请输入用户ID", trigger: "blur" }
+        ],
+
+        integral: [
+          { required: true, message: "请输入积分数量", trigger: "blur" }
+        ]
+      }
+    };
+  },
+  created() {
+    this.getList();
+    this.getDicts("sys_integral_log_type").then(response => {
+      this.integralLogTypeOptions = response.data;
+    });
+  },
+  methods: {
+    /** 查询积分记录列表 */
+    getList() {
+      this.loading = true;
+      listUserIntegralLogs(this.queryParams).then(response => {
+        this.integralLogsList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.createTime = null;
+      this.queryParams.sTime = null;
+      this.queryParams.eTime = null;
+      this.handleQuery();
+    },
+    /** 时间范围变化 */
+    changeTime() {
+      if (this.createTime != null) {
+        this.queryParams.sTime = this.createTime[0];
+        this.queryParams.eTime = this.createTime[1];
+      } else {
+        this.queryParams.sTime = null;
+        this.queryParams.eTime = null;
+      }
+    },
+    /** 校验用户 */
+    handleCheckUser() {
+      if (!this.integralForm.userId) {
+        this.checkedUserInfo = null;
+        return;
+      }
+      checkUser(this.integralForm.userId).then(response => {
+        if (response.code === 200) {
+          this.checkedUserInfo = {
+            nickName: response.nickName,
+            phone: response.phone,
+            integral: response.integral
+          };
+        } else {
+          this.checkedUserInfo = null;
+          this.$message.error("用户不存在");
+        }
+      }).catch(() => {
+        this.checkedUserInfo = null;
+      });
+    },
+
+    /** 增加积分按钮操作 */
+    handleAddIntegral() {
+      this.integralForm = { userId: null, logType: 23, integral: null, type: "add" };
+      this.checkedUserInfo = null;
+      this.integralDialogTitle = "增加积分";
+      this.integralDialogOpen = true;
+      this.$nextTick(() => {
+        this.$refs.integralForm && this.$refs.integralForm.clearValidate();
+      });
+    },
+    /** 扣除积分按钮操作 */
+    handleDeductIntegral() {
+      this.integralForm = { userId: null, logType: 28, integral: null, type: "deduct" };
+      this.checkedUserInfo = null;
+      this.integralDialogTitle = "扣除积分";
+      this.integralDialogOpen = true;
+      this.$nextTick(() => {
+        this.$refs.integralForm && this.$refs.integralForm.clearValidate();
+      });
+    },
+    /** 提交积分表单 */
+    submitIntegralForm() {
+      this.$refs["integralForm"].validate(valid => {
+        if (valid) {
+          const data = {
+            userId: this.integralForm.userId,
+            logType: this.integralForm.logType,
+            integral: this.integralForm.integral
+          };
+          if (this.integralForm.type === "add") {
+            addIntegral(data).then(response => {
+              if (response.code === 200) {
+                this.msgSuccess("增加积分成功");
+                this.integralDialogOpen = false;
+                this.getList();
+              }
+            });
+          } else {
+            deductIntegral(data).then(response => {
+              if (response.code === 200) {
+                this.msgSuccess("扣除积分成功");
+                this.integralDialogOpen = false;
+                this.getList();
+              }
+            });
+          }
+        }
+      });
+    },
+    /** 取消积分弹窗 */
+    cancelIntegralDialog() {
+      this.integralDialogOpen = false;
+      this.checkedUserInfo = null;
+    }
+  }
+};
+</script>

+ 2 - 1
src/views/hisStore/storeAfterSales/index.vue

@@ -183,7 +183,8 @@
       <el-table-column label="会员手机号" align="center" prop="userPhone" />
       <el-table-column label="产品名称" align="center" prop="productName" :show-overflow-tooltip="true" />
       <el-table-column label="退款金额" align="center" prop="refundAmount" />
-       <el-table-column label="退款类型" align="center" prop="serviceType" >
+      <el-table-column label="退款类型" align="center" prop="refundType" />
+       <el-table-column label="服务类型" align="center" prop="serviceType" >
           <template slot-scope="scope">
               <div prop="serviceType" v-for="(item, index) in serviceTypeOptions"    v-if="scope.row.serviceType==item.dictValue">{{item.dictLabel}}</div>
           </template>

+ 83 - 0
src/views/hisStore/storeProduct/indexZm.vue

@@ -683,6 +683,8 @@
               <!-- 多规格表格-->
               <el-col :span="24">
                 <el-form-item label="商品属性:" class="labeltop">
+                  <el-button type="primary" size="mini" @click="batchSetOpen = true" style="margin-right: 10px;">批量设置</el-button>
+                  <el-button type="warning" size="mini" @click="handleBatchClearStock">批量清空库存</el-button>
 
                   <el-table :data="manyFormValidate" size="small" style="width: 90%;" border>
                     <el-table-column type="myindex" v-for="(item,index) in form.header" :key="index"  :width="item.minWidth" :label="item.title" :property="item.slot" align="center">
@@ -1059,6 +1061,34 @@
       </div>
     </el-dialog>
 
+    <!-- 批量设置弹窗 -->
+    <el-dialog title="批量设置" :visible.sync="batchSetOpen" width="400px" append-to-body>
+      <el-form :model="batchSetForm" label-width="100px">
+        <el-form-item label="价格">
+          <el-input v-model="batchSetForm.price" placeholder="不填则不修改" />
+        </el-form-item>
+        <el-form-item label="增减库存">
+          <el-input v-model="batchSetForm.stock" placeholder="不填则不修改" />
+        </el-form-item>
+        <el-form-item label="成本价">
+          <el-input v-model="batchSetForm.cost" placeholder="不填则不修改" />
+        </el-form-item>
+        <el-form-item label="规格编码">
+          <el-input v-model="batchSetForm.barCode" placeholder="不填则不修改" />
+        </el-form-item>
+        <el-form-item label="重量(kg)">
+          <el-input v-model="batchSetForm.weight" placeholder="不填则不修改" />
+        </el-form-item>
+        <el-form-item label="体积(m³)">
+          <el-input v-model="batchSetForm.volume" placeholder="不填则不修改" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="handleBatchSet">确 定</el-button>
+        <el-button @click="batchSetOpen = false">取 消</el-button>
+      </div>
+    </el-dialog>
+
   </div>
 </template>
 
@@ -1161,6 +1191,16 @@ export default {
       ruleList:[],
       // 多规格表格data
       manyFormValidate: [],
+      // 批量设置弹窗
+      batchSetOpen: false,
+      batchSetForm: {
+        price: '',
+        stock: '',
+        cost: '',
+        barCode: '',
+        weight: '',
+        volume: ''
+      },
       // 单规格表格data
       oneFormValidate: [
         {
@@ -1472,6 +1512,49 @@ export default {
     delAttrTable (index) {
       this.manyFormValidate.splice(index, 1);
     },
+    // 批量设置
+    handleBatchSet() {
+      const form = this.batchSetForm;
+      const fields = ['price', 'stock', 'cost', 'barCode', 'weight', 'volume'];
+      let hasValue = false;
+      for (const key of fields) {
+        if (form[key] !== '' && form[key] !== null && form[key] !== undefined) {
+          hasValue = true;
+          break;
+        }
+      }
+      if (!hasValue) {
+        this.$message.warning('请至少填写一项数据');
+        return;
+      }
+      this.manyFormValidate.forEach(row => {
+        fields.forEach(key => {
+          if (form[key] !== '' && form[key] !== null && form[key] !== undefined) {
+            this.$set(row, key, form[key]);
+          }
+        });
+      });
+      this.batchSetOpen = false;
+      this.batchSetForm = { price: '', stock: '', cost: '', barCode: '', weight: '', volume: '' };
+      this.$message.success('批量设置成功');
+    },
+    // 批量清空库存
+    handleBatchClearStock() {
+      if (this.manyFormValidate.length === 0) {
+        this.$message.warning('没有可操作的规格数据');
+        return;
+      }
+      this.$confirm('是否确认将所有规格的库存清零?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        this.manyFormValidate.forEach(row => {
+          this.$set(row, 'stock', 0);
+        });
+        this.$message.success('库存已全部清零');
+      }).catch(() => {});
+    },
     addBtn () {
       this.clearAttr();
       this.createBnt = false;

+ 24 - 2
src/views/hisStore/storeProductPackage/index.vue

@@ -141,7 +141,7 @@
           </template>
       </el-table-column>
 
-      <el-table-column label="操作" align="center" fixed="right" width="120px" class-name="small-padding fixed-width">
+      <el-table-column label="操作" align="center" fixed="right" width="180px" class-name="small-padding fixed-width">
         <template slot-scope="scope">
           <el-button
             size="mini"
@@ -157,6 +157,13 @@
             @click="handleDelete(scope.row)"
             v-hasPermi="['store:storeProductPackage:remove']"
           >删除</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-document-copy"
+            @click="handleCopy(scope.row)"
+            v-hasPermi="['store:storeProductPackage:add']"
+          >复制</el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -334,7 +341,7 @@
 </template>
 
 <script>
-import { listStoreProductPackage, getStoreProductPackage, delStoreProductPackage, addStoreProductPackage, updateStoreProductPackage, exportStoreProductPackage,modifyStoreProductPackages } from "@/api/hisStore/storeProductPackage";
+import { listStoreProductPackage, getStoreProductPackage, delStoreProductPackage, addStoreProductPackage, updateStoreProductPackage, exportStoreProductPackage,modifyStoreProductPackages,copyStoreProductPackage } from "@/api/hisStore/storeProductPackage";
 import Editor from '@/components/Editor/wang';
 import productAttrValueSelect from "../components/productAttrValueSelect";
 import Material from '@/components/Material'
@@ -678,6 +685,21 @@ export default {
           this.msgSuccess("删除成功");
         }).catch(function() {});
     },
+    /** 一键复制按钮操作 */
+    handleCopy(row) {
+      this.$confirm('是否复制套餐ID:' + row.packageId + ',套餐标题为:' + row.title + ' 的套餐?', "提示", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          return copyStoreProductPackage(row.packageId);
+        }).then((response) => {
+          if (response.code === 200) {
+            this.msgSuccess("复制成功");
+            this.getList();
+          }
+        }).catch(function() {});
+    },
     /** 导出按钮操作 */
     handleExport() {
       const queryParams = this.queryParams;