Prechádzať zdrojové kódy

Merge remote-tracking branch 'company/master'

dongdong.xiang 2 mesiacov pred
rodič
commit
4446c05b29

+ 27 - 0
.env.development

@@ -1,3 +1,30 @@
+# 页面标题
+VUE_APP_TITLE =互联网医院管理系统
+# 公司名称
+VUE_APP_COMPANY_NAME =重庆云联融智科技有限公司
+# ICP备案号
+VUE_APP_ICP_RECORD =蜀ICP备2023036719号
+# ICP网站访问地址
+VUE_APP_ICP_URL =https://beian.miit.gov.cn
+# 网站LOG
+VUE_APP_LOG_URL =@/assets/logo/logo.png
+# 存储桶配置
+VUE_APP_OBS_ACCESS_KEY_ID = K2UTJGIN7UTZJR2XMXYG
+# 存储桶配置
+VUE_APP_OBS_SECRET_ACCESS_KEY = sbyeNJLbcYmH6copxeFP9pAoksM4NIT9Zw4x0SRX
+# 存储桶配置
+VUE_APP_OBS_SERVER = https://obs.cn-north-4.myhuaweicloud.com
+# 存储桶配置
+VUE_APP_OBS_BUCKET = zkzh-hw079058881
+# 存储桶配置
+VUE_APP_COS_BUCKET = zkzh-1323137866
+# 存储桶配置
+VUE_APP_COS_REGION = ap-chongqing
+# 线路一地址
+VUE_APP_VIDEO_LINE_1 = https://zkzhtcpv.ylrzcloud.com
+# 线路二地址
+VUE_APP_VIDEO_LINE_2 = https://zkzhobs.ylrztop.com
+
 # 开发环境配置
 ENV = 'development'
 

+ 33 - 0
.env.prod-fby

@@ -0,0 +1,33 @@
+# 页面标题
+VUE_APP_TITLE =互联网医院管理系统
+# 公司名称
+COMPANY_NAME =重庆云联融智科技有限公司
+# ICP备案号
+ICP_RECORD =蜀ICP备2023036719号
+# ICP网站访问地址
+ICP_URL =https://beian.miit.gov.cn
+# 网站LOG
+VUE_APP_LOG_URL =@/assets/logo/logo.png
+# 存储桶配置
+OSS_ACCESS_KEY_ID = K2UTJGIN7UTZJR2XMXYG
+# 存储桶配置
+OSS_SECRET_ACCESS_KEY = sbyeNJLbcYmH6copxeFP9pAoksM4NIT9Zw4x0SRX
+# 存储桶配置
+OSS_SERVER = https://obs.cn-north-4.myhuaweicloud.com
+# 存储桶配置
+OSS_BUCKET = hzyy-1323137866
+# 存储桶配置
+OSS_REGION = ap-chongqing
+# 线路一地址
+OSS_LINE_1 = https://hzyytcpv.ylrzcloud.com
+# 线路二地址
+OSS_LINE_2 = https://hzyyobs.ylrztop.com
+
+# 生产环境配置
+ENV = 'production'
+
+#FS管理系统/生产环境
+VUE_APP_BASE_API = '/prod-api'
+
+# 路由懒加载
+VUE_CLI_BABEL_TRANSPILE_MODULES = true

+ 31 - 0
.env.prod-zkzh

@@ -0,0 +1,31 @@
+# 页面标题
+VUE_APP_TITLE = 中康SCRM管理系统
+# 公司名称
+VUE_APP_COMPANY_NAME = 陕西中康智慧药房有限公司
+# ICP备案号
+VUE_APP_ICP_RECORD = 陕ICP备2024048690号-2
+# ICP网站访问地址
+VUE_APP_ICP_URL =https://beian.miit.gov.cn
+# 网站LOG
+VUE_APP_LOG_URL =@/assets/logo/logo.png
+# 存储桶配置
+VUE_APP_OBS_ACCESS_KEY_ID = K2UTJGIN7UTZJR2XMXYG
+# 存储桶配置
+VUE_APP_OBS_SECRET_ACCESS_KEY = sbyeNJLbcYmH6copxeFP9pAoksM4NIT9Zw4x0SRX
+# 存储桶配置
+VUE_APP_OBS_SERVER = https://obs.cn-north-4.myhuaweicloud.com
+# 存储桶配置
+VUE_APP_OBS_BUCKET = zkzh-hw079058881
+# 存储桶配置
+VUE_APP_COS_BUCKET = zkzh-1323137866
+# 存储桶配置
+VUE_APP_COS_REGION = ap-chongqing
+# 线路一地址
+VUE_APP_VIDEO_LINE_1 = https://zkzhtcpv.ylrzcloud.com
+# 线路二地址
+VUE_APP_VIDEO_LINE_2 = https://zkzhobs.ylrztop.com
+# 生产环境配置
+ENV = 'production'
+
+#FS管理系统/生产环境
+VUE_APP_BASE_API = '/prod-api'

+ 3 - 0
package.json

@@ -8,6 +8,9 @@
     "dev": "vue-cli-service serve",
     "build:prod": "vue-cli-service build",
     "build:stage": "vue-cli-service build --mode staging",
+    "build:prod-jz": "vue-cli-service build --mode prod-jz",
+    "build:prod-zkzh": "vue-cli-service build --mode prod-zkzh",
+    "build:prod-fby": "vue-cli-service build --mode prod-fby",
     "preview": "node build/index.js --preview",
     "lint": "eslint --ext .js,.vue src",
     "test:unit": "jest --clearCache && vue-cli-service test:unit",

+ 10 - 1
src/api/system/dict/data.js

@@ -58,4 +58,13 @@ export function exportData(query) {
     method: 'get',
     params: query
   })
-}
+}
+
+// 不分页获取数据字典列表
+export function allList(query) {
+  return request({
+    url: '/system/dict/data/allList',
+    method: 'get',
+    params: query
+  })
+}

+ 53 - 0
src/api/system/keyword.js

@@ -0,0 +1,53 @@
+import request from '@/utils/request'
+
+// 查询系统关键字列表
+export function listKeyword(query) {
+  return request({
+    url: '/system/keyword/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询系统关键字详细
+export function getKeyword(keywordId) {
+  return request({
+    url: '/system/keyword/' + keywordId,
+    method: 'get'
+  })
+}
+
+// 新增系统关键字
+export function addKeyword(data) {
+  return request({
+    url: '/system/keyword',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改系统关键字
+export function updateKeyword(data) {
+  return request({
+    url: '/system/keyword',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除系统关键字
+export function delKeyword(keywordId) {
+  return request({
+    url: '/system/keyword/' + keywordId,
+    method: 'delete'
+  })
+}
+
+// 导出系统关键字
+export function exportKeyword(query) {
+  return request({
+    url: '/system/keyword/export',
+    method: 'get',
+    params: query
+  })
+}

+ 4 - 4
src/components/VideoUpload/index.vue

@@ -285,9 +285,9 @@ export default {
         console.log("isPrivate=======>",this.isPrivate)
         let line_1='' ;
         if (this.isPrivate===0){
-          line_1 = `https://tcpv.ylrzcloud.com${data.urlPath}`;
+          line_1 = `${process.env.VUE_APP_VIDEO_LINE_1}${data.urlPath}`;
         }else {
-          line_1 = `https://fbytcpv.ylrzcloud.com${data.urlPath}`;
+          line_1 = `${process.env.VUE_APP_VIDEO_LINE_1}${data.urlPath}`;
         }
 
         let urlPathWithoutFirstSlash = data.urlPath.substring(1);
@@ -311,9 +311,9 @@ export default {
         console.log("华为OBS返回========>",data);
         let line_2='' ;
         if (this.isPrivate===0){
-          line_2 = `https://obs.ylrztop.com/${data.urlPath}`;
+          line_2 = `${process.env.VUE_APP_VIDEO_LINE_2}/${data.urlPath}`;
         }else {
-          line_2 = `https://fbyobs.ylrztop.com/${data.urlPath}`;
+          line_2 = `${process.env.VUE_APP_VIDEO_LINE_2}/${data.urlPath}`;
         }
         // this.$emit("update:videoUrl", data);
         this.$emit("update:line_2", line_2);

+ 9 - 9
src/layout/components/Navbar.vue

@@ -10,7 +10,7 @@
     <div class="right-menu">
       <template v-if="device!=='mobile'">
         <!-- <search id="header-search" class="right-menu-item" />
-        
+
         -->
         <screenfull id="screenfull" class="right-menu-item hover-effect" />
 
@@ -22,7 +22,7 @@
 
       <el-dropdown class="avatar-container right-menu-item hover-effect" trigger="click">
         <div class="avatar-wrapper">
-          <img :src="avatar" class="user-avatar">
+          <img :src="logImg" class="user-avatar">
           <i class="el-icon-caret-bottom" />
         </div>
         <el-dropdown-menu slot="dropdown">
@@ -50,7 +50,7 @@ import Screenfull from '@/components/Screenfull'
 import SizeSelect from '@/components/SizeSelect'
 import Search from '@/components/HeaderSearch'
 
- 
+
 export default {
   components: {
     Breadcrumb,
@@ -85,14 +85,14 @@ export default {
   },
   data() {
     return {
-       
+
     }
   },
   created() {
-    
+
   },
   methods: {
-    
+
     toggleSideBar() {
       this.$store.dispatch('app/toggleSideBar')
     },
@@ -151,7 +151,7 @@ export default {
     }
 
     .right-menu-item {
-    
+
       position: relative;
       display: inline-block;
       padding: 0 8px;
@@ -169,7 +169,7 @@ export default {
           top:0px;
 
         }
-      
+
       &.hover-effect {
         cursor: pointer;
         transition: background .3s;
@@ -207,4 +207,4 @@ export default {
 }
 
 </style>
- 
+

+ 3 - 6
src/layout/components/Sidebar/Logo.vue

@@ -2,11 +2,11 @@
   <div class="sidebar-logo-container" :class="{'collapse':collapse}">
     <transition name="sidebarLogoFade">
       <router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
-        <img v-if="logo" :src="logo" class="sidebar-logo">
+        <img v-if="logImg" :src="logImg" class="sidebar-logo">
         <h1 v-else class="sidebar-title">{{ title }} </h1>
       </router-link>
       <router-link v-else key="expand" class="sidebar-logo-link" to="/">
-        <!-- <img v-if="logo" :src="logo" class="sidebar-logo"> -->
+        <!-- <img v-if="logImg" :src="logImg" class="sidebar-logo"> -->
         <h1 class="sidebar-title">{{ title }} </h1>
       </router-link>
     </transition>
@@ -14,8 +14,6 @@
 </template>
 
 <script>
-import logoImg from '@/assets/logo/crm.png'
-import { getToken } from "@/utils/auth";
 export default {
   name: 'SidebarLogo',
   props: {
@@ -26,8 +24,7 @@ export default {
   },
   data() {
     return {
-      title: '云联大健康私域平台',
-      logo: logoImg
+      title: process.env.VUE_APP_TITLE,
     }
   }
 }

+ 2 - 0
src/main.js

@@ -52,6 +52,8 @@ Vue.prototype.qwIm = qwIm
 
 
 
+// 全局配置
+Vue.prototype.logImg = require(process.env.VUE_APP_LOG_URL)
 // 全局方法挂载
 Vue.prototype.getDicts = getDicts
 Vue.prototype.getConfigKey = getConfigKey

+ 11 - 4
src/utils/cos.js

@@ -2,10 +2,16 @@ import COS from 'cos-js-sdk-v5';
 import { Message } from 'element-ui';
 import { getTmpSecretKey } from '@/api/common';
 
+console.log('环境变量:', process.env);
+console.log('NODE_ENV:', process.env.NODE_ENV);
+console.log('VUE_APP_COS_BUCKET:', process.env.VUE_APP_COS_BUCKET);
+console.log('VUE_APP_COS_REGION:', process.env.VUE_APP_COS_REGION);
+
 const config = {
-    Bucket: 'fby-1323137866',
-    Region: 'ap-chongqing',
+  Bucket: process.env.VUE_APP_COS_BUCKET,
+  Region: process.env.VUE_APP_COS_REGION,
 };
+console.log('COS配置:', config);
 
 // 上传到腾讯云cos
 export const uploadObject = async (file,onProgress,type,callBackUp) => {
@@ -34,7 +40,7 @@ export const uploadObject = async (file,onProgress,type,callBackUp) => {
             },
         });
 
-
+        console.log("初始化成功")
         let fileName = file.name || ""
         const upload_file_name = new Date().getTime() + '.' + fileName.split(".")[fileName.split(".").length - 1];
         let date =  new Date()
@@ -45,10 +51,11 @@ export const uploadObject = async (file,onProgress,type,callBackUp) => {
         let videoKey = `/userVideo/${uploadDay}/${upload_file_name}`
         let courseKey = `/course/${uploadDay}/${upload_file_name}`
         let key = type ===1 ? courseKey : videoKey;
+        console.log("开始上传")
         return new Promise((resolve, reject) => {
+            console.log("uploadFile")
             cos.uploadFile(
                 {
-
                     Bucket: config.Bucket, /* 必须 */
                     Region: config.Region, /* 存储桶所在地域,必须字段 */
                     Key: key, // 文件名

+ 4 - 4
src/utils/obs.js

@@ -4,9 +4,9 @@ import ObsClient from "esdk-obs-browserjs/src/obs";
 export const uploadToOBS = async(file,progressCallback,type) =>  {
     try {
         const obsClient = new ObsClient({
-            access_key_id: 'K2UTJGIN7UTZJR2XMXYG',
-            secret_access_key: 'sbyeNJLbcYmH6copxeFP9pAoksM4NIT9Zw4x0SRX',
-            server: 'https://obs.cn-north-4.myhuaweicloud.com'
+          access_key_id: process.env.VUE_APP_OBS_ACCESS_KEY_ID,
+          secret_access_key: process.env.VUE_APP_OBS_SECRET_ACCESS_KEY,
+          server: process.env.VUE_APP_OBS_SERVER
         });
         let fileName = file.name || ""
         const upload_file_name = new Date().getTime() + '.' + fileName.split(".")[fileName.split(".").length - 1];
@@ -29,7 +29,7 @@ export const uploadToOBS = async(file,progressCallback,type) =>  {
         return new Promise((resolve, reject) => {
             //上传对象
             obsClient.putObject({
-                Bucket: 'fby-hw079058881',//桶名称
+                Bucket: process.env.VUE_APP_OBS_BUCKET,//桶名称
                 Key: key,//文件名
                 Body: file,
                 ProgressCallback: callback,//进度回调

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

@@ -570,7 +570,7 @@ export default {
 
     handleResetPwd(row) {
       const companyIds = row.companyId || this.ids;
-      this.$confirm('是否确认重复密码为123456?', "警告", {
+      this.$confirm('是否确认重复密码为cq654321!!', "警告", {
           confirmButtonText: "确定",
           cancelButtonText: "取消",
           type: "warning"

+ 12 - 3
src/views/course/courseQuestionBank/index.vue

@@ -227,6 +227,7 @@
         <el-alert v-if="!form.type" title="选择题目类别后添加选项" type="warning" show-icon></el-alert>
 
         <el-form-item label="选项"  v-if="form.type">
+          <el-button @click="addRow" size="mini" type="primary">新增选项</el-button>
           <el-table border :data="question"  :cell-style="{ textAlign: 'center' }"		 :header-cell-style="{textAlign: 'center'}"   >
             <el-table-column label="序号" width="65px" >
               <template slot-scope="scope">
@@ -267,7 +268,6 @@
             <el-table-column label="操作">
               <template slot-scope="scope">
                 <el-button @click="deleteRow(scope.$index)"   size="mini" type="text" v-if="question.length>1">删除</el-button>
-                <el-button @click="addRow(scope.$index+1)" size="mini" type="text" >新增</el-button>
               </template>
             </el-table-column>
           </el-table>
@@ -344,6 +344,9 @@ export default {
       statusOptions: [],
       question:[
         { name: "", isAnswer: 0, indexId:0},
+        { name: "", isAnswer: 0, indexId:1},
+        { name: "", isAnswer: 0, indexId:2},
+        { name: "", isAnswer: 0, indexId:3},
       ],
       // 遮罩层
       loading: true,
@@ -486,8 +489,8 @@ export default {
       }
 
     },
-    addRow(val) {
-      this.question.push({ name: '', isAnswer: 0,indexId:val});},
+    addRow() {
+      this.question.push({ name: '', isAnswer: 0,indexId: this.question.length});},
     deleteRow(index) {
       if (this.form.type === 1 && this.question[index] === this.selectedAnswer) {
         this.selectedAnswer = null;
@@ -570,6 +573,12 @@ export default {
     submitForm() {
       this.$refs["form"].validate(valid => {
         if (valid) {
+
+          if (this.question.some(q => q.name === "")) {
+            this.$message.error("不能存在空选项")
+            return
+          }
+
           this.form.question=JSON.stringify(this.question)
 
           if (this.form.type===2){

+ 12 - 43
src/views/course/userCourse/index.vue

@@ -95,39 +95,9 @@
         >导出
         </el-button>
       </el-col>
-      <el-col :span="1.5">
-        <el-button
-          v-if="queryParams.isShow==0"
-          type="success"
-          plain
-          icon="el-icon-edit"
-          size="mini"
-          :disabled="multiple"
-          @click="putOn"
-          v-hasPermi="['course:userCourse:putOn']"
-        >上架
-        </el-button>
-      </el-col>
-      <el-col :span="1.5">
-        <el-button
-          v-if="queryParams.isShow==1"
-          type="success"
-          plain
-          icon="el-icon-edit"
-          size="mini"
-          :disabled="multiple"
-          @click="pullOff"
-          v-hasPermi="['course:userCourse:pullOff']"
-        >下架
-        </el-button>
-      </el-col>
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
 
-    <el-tabs type="card" v-model="queryParams.isShow" @tab-click="handleClick">
-      <el-tab-pane label="已上架" name="1"></el-tab-pane>
-      <el-tab-pane label="待上架" name="0"></el-tab-pane>
-    </el-tabs>
     <el-table height="600" border v-loading="loading" :data="userCourseList" @selection-change="handleSelectionChange">
       <el-table-column type="selection" width="55" align="center"/>
       <el-table-column label="课程ID" align="center" prop="courseId"/>
@@ -156,15 +126,6 @@
             @click="handleCatalog(scope.row)"
           >目录管理
           </el-button>
-          <el-button
-            size="mini"
-            type="text"
-            :icon="scope.row.isShow === 1 ? 'el-icon-close' : 'el-icon-open'"
-            @click="handleShow(scope.row)"
-            v-hasPermi="['course:userCourse:editShow']"
-          >
-            {{ scope.row.isShow === 1 ? '下架' : '上架' }}
-          </el-button>
           <el-button
             size="mini"
             type="text"
@@ -250,6 +211,16 @@
         <el-form-item label="课程封面" prop="imgUrl">
           <ImageUpload v-model="form.imgUrl" type="image" :num="10" :width="150" :height="150"/>
         </el-form-item>
+        <el-form-item label="关联公司" prop="tags">
+          <el-select v-model="companyIds" multiple placeholder="请选择公司" filterable clearable style="width: 90%;">
+            <el-option
+              v-for="dict in companyOptions"
+              :key="dict.dictValue"
+              :label="dict.dictLabel"
+              :value="dict.dictValue"
+            />
+          </el-select>
+        </el-form-item>
       </el-form>
       <div slot="footer" class="dialog-footer">
         <el-button type="primary" @click="submitForm">确 定</el-button>
@@ -494,10 +465,6 @@ export default {
         this.$refs.userCourseCatalogDetails.getDetails(courseId, row.courseName, row.isPrivate);
       }, 200);
     },
-    handleClick(tab, event) {
-      this.queryParams.isShow = tab.name;
-      this.getList();
-    },
     /** 转换课堂分类数据结构 */
     normalizer(node) {
       if (node.children && !node.children.length) {
@@ -566,6 +533,7 @@ export default {
       };
       this.tags = [];
       this.subCategoryOptions = []
+      this.companyIds = []
       this.resetForm("form");
     },
     /** 搜索按钮操作 */
@@ -639,6 +607,7 @@ export default {
       this.$refs["form"].validate(valid => {
         if (valid) {
 
+          this.form.companyIds = this.companyIds.toString()
           // 私域课程
           this.form.isPrivate = 1
           if (this.form.courseId != null) {

+ 0 - 25
src/views/course/userCourse/public.vue

@@ -312,20 +312,6 @@
             </el-form-item>
           </el-col>
         </el-row>
-        <el-row>
-          <el-col :span="8">
-            <el-form-item label="发课时间" prop="sendTime">
-              <el-time-picker
-                v-model="form.sendTime"
-                value-format="HH:mm"
-                format="HH:mm"
-                style="width: 100px"
-                :picker-options="{ selectableRange: startTimeRange }"
-              >
-              </el-time-picker>
-            </el-form-item>
-          </el-col>
-        </el-row>
         <el-row>
           <el-col :span="8">
             <el-form-item label="排序" prop="sort">
@@ -440,16 +426,6 @@
             </el-form-item>
           </el-col>
         </el-row>
-        <el-form-item label="关联公司" prop="tags">
-          <el-select v-model="companyIds" multiple placeholder="请选择公司" filterable clearable style="width: 90%;">
-            <el-option
-              v-for="dict in companyOptions"
-              :key="dict.dictValue"
-              :label="dict.dictLabel"
-              :value="dict.dictValue"
-            />
-          </el-select>
-        </el-form-item>
       </el-form>
       <div slot="footer" class="dialog-footer">
         <el-button type="primary" @click="submitForm">确 定</el-button>
@@ -841,7 +817,6 @@ export default {
           } else {
             this.form.tags = null
           }
-          this.form.companyIds = this.companyIds.toString()
           this.form.isPrivate = 0
           if (this.form.courseId != null) {
             updateUserCourse(this.form).then(response => {

+ 7 - 3
src/views/course/userCoursePeriod/index.vue

@@ -224,8 +224,8 @@
     </el-container>
 
     <!-- 添加或修改会员营期对话框-->
-    <el-drawer :title="title" :visible.sync="open" width="700px" append-to-body>
-      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
+    <el-drawer :title="title" :visible.sync="open" size="700px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="120px">
         <el-form-item label="营期名称" prop="periodName">
           <el-input v-model="form.periodName" placeholder="请输入营期名称" />
         </el-form-item>
@@ -257,6 +257,9 @@
             <el-radio :label="2" >单课程</el-radio>
           </el-radio-group>
         </el-form-item>
+        <el-form-item label="销售可查看天数" prop="periodType">
+          <el-input-number :min="0" v-model="form.maxViewNum" style="width: 200px" />
+        </el-form-item>
         <el-form-item label="开营日期" prop="periodStartingTime">
           <el-date-picker
             :disabled = "isDisabledDateRange"
@@ -924,6 +927,7 @@ export default {
         dateRange: [],
         date: null,
         days: [],
+        maxViewNum: 0,
         periodEndTime: null,
         timeRange: [], // 看课时间范围
         viewStartTime: null, // 看课开始时间
@@ -1660,7 +1664,7 @@ export default {
 
 /* 添加训练营表单样式 */
 .drawer-footer {
-  position: absolute;
+  //position: absolute;
   bottom: 0;
   left: 0;
   right: 0;

+ 11 - 11
src/views/course/videoResource/index.vue

@@ -84,19 +84,19 @@
                 {{ scope.$index + 1 }}
               </template>
             </el-table-column>
-      <el-table-column label="素材名称" align="center" :show-overflow-tooltip="true" prop="resourceName" width="300"/>
-      <el-table-column label="文件名称" align="center" :show-overflow-tooltip="true" prop="fileName" width="300"/>
-      <el-table-column label="分类" align="center" width="120">
+      <el-table-column label="素材名称" align="center" :show-overflow-tooltip="true" prop="resourceName"/>
+      <el-table-column label="文件名称" align="center" :show-overflow-tooltip="true" prop="fileName"/>
+      <el-table-column label="分类" align="center">
         <template slot-scope="scope">
           <span v-if="scope.row.typeId">{{ getTypeName(scope.row.typeId) }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="子分类" align="center" width="120">
+      <el-table-column label="子分类" align="center">
         <template slot-scope="scope">
           <span v-if="scope.row.typeSubId">{{ getTypeName(scope.row.typeSubId) }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="视频文件" align="center" width="120">
+      <el-table-column label="视频文件" align="center">
         <template slot-scope="scope">
           <a
             @click="handleVideoPreview(scope.row.videoUrl)"
@@ -105,7 +105,7 @@
           </a>
         </template>
       </el-table-column>
-      <el-table-column label="CDN" align="center" width="120">
+      <el-table-column label="CDN" align="center">
         <template slot-scope="scope">
           <a
             @click="copy(scope.row.videoUrl)"
@@ -114,7 +114,7 @@
           </a>
         </template>
       </el-table-column>
-      <el-table-column label="关联题目" align="center" width="150">
+      <el-table-column label="关联题目" align="center">
         <template slot-scope="scope">
           <a
             @click="handleViewProject(scope.row, 3)"
@@ -143,12 +143,12 @@
           </a>
         </template>
       </el-table-column>
-      <el-table-column label="视频时长" align="center" width="150">
+      <el-table-column label="视频时长" align="center">
         <template slot-scope="scope">
           <div style="padding: 4px 12px;background: linear-gradient(to right, rgb(196 219 255), #409EFF)">{{ formatDuration(scope.row.duration) }}</div>
         </template>
       </el-table-column>
-      <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="120" fixed="right">
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
         <template slot-scope="scope">
           <el-button
             size="mini"
@@ -1029,7 +1029,7 @@ export default {
           onProgress(progressEvent);
         }, 1);
 
-        let line_1 = `https://rttcpv.ylrzcloud.com${data.urlPath}`;
+        let line_1 = `${process.env.VUE_APP_VIDEO_LINE_1}${data.urlPath}`;
 
         form.fileKey = data.urlPath.substring(1);
         form.videoUrl = line_1;
@@ -1053,7 +1053,7 @@ export default {
           onProgress(progressEvent);
         }, 1);
 
-        form.line2 = `https://rtobs.ylrztop.com/${data.urlPath}`;
+        form.line2 = `${process.env.VUE_APP_VIDEO_LINE_2}/${data.urlPath}`;
         this.$message.success("线路二上传成功");
       } catch (error) {
         this.$message.error("线路二上传失败");

+ 13 - 4
src/views/login.vue

@@ -5,7 +5,7 @@
         <img src="../assets/image/login_left.png" alt="">
       </div>
       <el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form">
-        <h3 class="title">云联大健康私域平台</h3>
+        <h3 class="title">{{vueAppTitle}}</h3>
         <el-form-item prop="username">
           <el-input v-model="loginForm.username" type="text" auto-complete="off" placeholder="账号">
             <svg-icon slot="prefix" icon-class="user" class="el-input__icon input-icon" />
@@ -53,8 +53,8 @@
     </div>
     <!--  底部  -->
     <div class="el-login-footer">
-      <span>版权信息  </span>
-      <a href="https://beian.miit.gov.cn" target="_bank">备</a>
+      <span>{{companyName}}</span>
+      <a :href="icpUrl" target="_bank">{{icpRecord}}</a>
     </div>
   </div>
 </template>
@@ -69,6 +69,10 @@ export default {
   data() {
     return {
       codeUrl: "",
+      vueAppTitle: process.env.VUE_APP_TITLE,
+      companyName: process.env.VUE_APP_COMPANY_NAME,
+      icpRecord: process.env.VUE_APP_ICP_RECORD,
+      icpUrl: process.env.ICP_URL,
       cookiePassword: "",
       loginForm: {
         username: "",
@@ -82,7 +86,12 @@ export default {
           { required: true, trigger: "blur", message: "用户名不能为空" }
         ],
         password: [
-          { required: true, trigger: "blur", message: "密码不能为空" }
+          { required: true, trigger: "blur", message: "密码不能为空" },
+          {
+            pattern: /^(?=.*[A-Za-z])(?=.*\d)(?=.*[^A-Za-z0-9]).{8,20}$/,
+            message: "密码长度为 8-20 位,必须包含字母、数字和特殊字符",
+            trigger: ["blur", "change"],
+          }
         ],
         code: [{ required: true, trigger: "change", message: "验证码不能为空" }]
       },

+ 25 - 14
src/views/statistics/member/index.vue

@@ -165,31 +165,37 @@
         <!-- 序号列 -->
         <el-table-column label="序号" type="index" width="60" align="center" />
         <!-- 统计日期列 -->
-        <el-table-column prop="date" label="统计日期" min-width="100" align="center" />
+        <el-table-column prop="statDate" label="统计日期" min-width="100" align="center" />
         <!-- 会员名称列 -->
-        <el-table-column prop="memberName" label="会员名称" min-width="100" align="center" />
-        <!-- 性别列 -->
-        <el-table-column prop="gender" label="性别" width="60" align="center" />
+        <el-table-column prop="nickName" label="会员名称" min-width="100" align="center" />
         <!-- 手机号列 -->
-        <el-table-column prop="mobile" label="手机号" min-width="100" align="center" />
+        <el-table-column prop="phone" label="手机号" min-width="100" align="center" />
         <!-- 会员标签列 -->
-        <el-table-column prop="memberTag" label="会员标签" min-width="100" align="center" />
+        <el-table-column prop="tag" label="会员标签" min-width="100" align="center" />
         <!-- 所属群管列 -->
-        <el-table-column prop="groupName" label="所属群管" min-width="100" align="center" />
+        <el-table-column prop="companyUserName" label="所属群管" min-width="100" align="center" />
         <!-- 所属经销商列 -->
-        <el-table-column prop="dealerName" label="所属经销商" min-width="100" align="center" />
+        <el-table-column prop="companyName" label="所属经销商" min-width="100" align="center" />
         <!-- 观看课程列 -->
-        <el-table-column prop="viewCount" label="观看课程" min-width="80" align="center" />
+        <el-table-column prop="count" label="观看课程" min-width="80" align="center" />
         <!-- 完课课程列 -->
-        <el-table-column prop="finishCount" label="完课课程" min-width="80" align="center" />
+        <el-table-column prop="overCount" label="完课课程" min-width="80" align="center" />
         <!-- 完课率列 -->
-        <el-table-column prop="finishRate" label="完课率" min-width="80" align="center" />
+        <el-table-column prop="finishRate" label="完课率" min-width="80" align="center">
+          <template slot-scope="scope">
+            {{ getPercentage(scope.row.overCount,  scope.row.count) }}
+          </template>
+        </el-table-column>
         <!-- 观看次数列 -->
-        <el-table-column prop="viewTimes" label="观看次数" min-width="80" align="center" />
+        <el-table-column prop="watchCount" label="观看次数" min-width="80" align="center" />
         <!-- 完课次数列 -->
-        <el-table-column prop="finishTimes" label="完课次数" min-width="80" align="center" />
+        <el-table-column prop="overCount" label="完课次数" min-width="80" align="center" />
         <!-- 视频率列 -->
-        <el-table-column prop="videoRate" label="视频率" min-width="80" align="center" />
+        <el-table-column prop="videoRate" label="视频率" min-width="80" align="center" >
+          <template slot-scope="scope">
+            {{ getPercentage(scope.row.overCount,  scope.row.watchCount) }}
+          </template>
+        </el-table-column>
 
         <!-- 操作列 -->
         <el-table-column label="操作" fixed="right" min-width="160" align="center">
@@ -315,6 +321,11 @@ export default {
 
   // ============= 组件方法 =============
   methods: {
+    getPercentage(part, total, precision = 2) {
+      if (total === 0) return '0%';
+      const percent = (part / total) * 100;
+      return percent.toFixed(precision) + '%';
+    },
     /**
      * 获取表格数据
      * 根据查询参数从服务器获取会员统计数据

+ 5 - 2
src/views/system/config/config.vue

@@ -60,6 +60,9 @@
                 <el-input    v-model="form1.refundAddress"   ></el-input>
               </el-tooltip>
             </el-form-item>
+            <el-form-item label="会员海报图片" prop="userPosterImage">
+              <ImageUpload v-model="form1.userPosterImage" type="image" :num="10" :width="150" :height="150" :limit="1"/>
+            </el-form-item>
            <div   class="footer">
               <el-button type="primary" @click="submitForm1">提  交</el-button>
             </div>
@@ -412,7 +415,7 @@
              <el-button @click="addDisabledTime" style="margin-top: 10px">添加时间段</el-button>
            </el-form-item>
 
-           <el-form-item label="红包模式">
+           <el-form-item label="红包模式" v-if="form18.rewardType==1">
              <el-radio-group v-model="form18.redPacketMode">
                <el-radio label="1">总公司</el-radio>
                <el-radio label="2">分公司</el-radio>
@@ -690,7 +693,7 @@ export default {
           if(key=="store.config"){
               this.configId=response.data.configId;
               this.configKey=response.data.configKey;
-            CompanyRedPackageLogsthis.form1 =JSON.parse(response.data.configValue);
+              this.form1 =JSON.parse(response.data.configValue);
               if(this.form1.certs!=null){
                   this.photoArr=this.form1.certs.split(",");
               }

+ 336 - 0
src/views/system/keyword/index.vue

@@ -0,0 +1,336 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="关键字" prop="keyword">
+        <el-input
+          v-model="queryParams.keyword"
+          placeholder="请输入关键字"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="类型" prop="type">
+        <el-select v-model="queryParams.type" placeholder="请选择类型" clearable size="small">
+          <el-option
+            v-for="dict in typeOptions"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="dict.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="所属公司" prop="companyId">
+        <el-select filterable v-model="queryParams.companyId" placeholder="请选择所属公司" clearable size="small">
+          <el-option
+            v-for="item in companys"
+            :key="item.companyId"
+            :label="item.companyName"
+            :value="item.companyId"
+          />
+        </el-select>
+      </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="primary"
+          plain
+          icon="el-icon-plus"
+          size="mini"
+          @click="handleAdd"
+          v-hasPermi="['system:keyword:add']"
+        >新增</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="success"
+          plain
+          icon="el-icon-edit"
+          size="mini"
+          :disabled="single"
+          @click="handleUpdate"
+          v-hasPermi="['system:keyword:edit']"
+        >修改</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="danger"
+          plain
+          icon="el-icon-delete"
+          size="mini"
+          :disabled="multiple"
+          @click="handleDelete"
+          v-hasPermi="['system:keyword: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="['system:keyword:export']"
+        >导出</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table border v-loading="loading" :data="keywordList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="关键字" align="center" prop="keyword" />
+      <el-table-column label="类型" align="center" prop="typeName" />
+      <el-table-column label="所属公司" align="center" prop="companyName" />
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleUpdate(scope.row)"
+            v-hasPermi="['system:keyword:edit']"
+          >修改</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+            v-hasPermi="['system:keyword:remove']"
+          >删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+    <!-- 添加或修改关键字对话框 -->
+    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
+        <el-form-item label="关键字" prop="keyword">
+          <el-input v-model="form.keyword" placeholder="请输入关键字" />
+        </el-form-item>
+        <el-form-item label="类型" prop="type">
+          <el-select v-model="form.type" placeholder="请选择类型">
+            <el-option
+              v-for="dict in typeOptions"
+              :key="dict.dictValue"
+              :label="dict.dictLabel"
+              :value="dict.dictValue"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="所属公司" prop="companyId">
+          <el-select filterable v-model="form.companyId" placeholder="请选择所属公司">
+            <el-option
+              v-for="item in companys"
+              :key="item.companyId"
+              :label="item.companyName"
+              :value="item.companyId"
+            />
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { listKeyword, getKeyword, delKeyword, addKeyword, updateKeyword, exportKeyword } from "@/api/system/keyword";
+import { allList } from "@/api/system/dict/data";
+import { getCompanyList } from "@/api/company/companyUser";
+
+export default {
+  name: "Keyword",
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 关键字表格数据
+      keywordList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 类型选项
+      typeOptions: [],
+      // 公司列表
+      companys: [],
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        keyword: null,
+        type: null,
+        companyId: null,
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+      }
+    };
+  },
+  created() {
+    this.getList();
+    this.getTypeOptions();
+    this.getCompanyOptions();
+  },
+  methods: {
+    /** 查询关键字列表 */
+    getList() {
+      this.loading = true;
+      listKeyword(this.queryParams).then(response => {
+        this.keywordList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        keywordId: null,
+        keyword: null,
+        type: null,
+        companyId: null,
+        createTime: null,
+        updateTime: null
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.keywordId)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加关键字";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const keywordId = row.keywordId || this.ids
+      getKeyword(keywordId).then(response => {
+        this.form = response.data;
+        // 强制把type转成字符串
+        this.form.type = String(response.data.type);
+        this.open = true;
+        this.title = "修改关键字";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          // 提交前移除临时属性
+          const submitData = { ...this.form };
+          delete submitData.typeName;
+          delete submitData.companyName;
+
+          if (this.form.keywordId != null) {
+            updateKeyword(submitData).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addKeyword(submitData).then(response => {
+              this.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const keywordIds = row.keywordId || this.ids;
+      this.$confirm('是否确认删除该数据?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          return delKeyword(keywordIds);
+        }).then(() => {
+          this.getList();
+          this.msgSuccess("删除成功");
+        }).catch(() => {});
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有关键字数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(() => {
+          this.exportLoading = true;
+          return exportKeyword(queryParams);
+        }).then(response => {
+          this.download(response.msg);
+          this.exportLoading = false;
+        }).catch(() => {});
+    },
+    /** 获取类型选项 */
+    getTypeOptions() {
+      allList({ dictType: "keyword_type" }).then(response => {
+        this.typeOptions = response.data;
+      });
+    },
+    /** 获取公司选项 */
+    getCompanyOptions() {
+      getCompanyList().then(response => {
+        this.companys = response.data;
+      });
+    }
+  }
+};
+</script>

+ 3 - 3
src/views/system/user/profile/index.vue

@@ -7,9 +7,9 @@
             <span>个人信息</span>
           </div>
           <div>
-            <div class="text-center">
-              <userAvatar :user="user" />
-            </div>
+<!--            <div class="text-center">-->
+<!--              <userAvatar :user="user" />-->
+<!--            </div>-->
             <ul class="list-group list-group-striped">
               <li class="list-group-item">
                 <svg-icon icon-class="user" />用户名称

+ 5 - 1
src/views/system/user/profile/resetPwd.vue

@@ -42,7 +42,11 @@ export default {
         ],
         newPassword: [
           { required: true, message: "新密码不能为空", trigger: "blur" },
-          { min: 6, max: 20, message: "长度在 6 到 20 个字符", trigger: "blur" }
+          {
+            pattern: /^(?=.*[A-Za-z])(?=.*\d)(?=.*[^A-Za-z0-9]).{8,20}$/,
+            message: "密码长度为 8-20 位,必须包含字母、数字和特殊字符",
+            trigger: ["blur", "change"],
+          }
         ],
         confirmPassword: [
           { required: true, message: "确认密码不能为空", trigger: "blur" },