Sfoglia il codice sorgente

Merge branch 'master' into 企微聊天

ct 2 settimane fa
parent
commit
e6cd5e50ac

+ 2 - 2
.env.development

@@ -1,7 +1,7 @@
 # 页面标题
-VUE_APP_TITLE =木易华康SCRM销售端
+VUE_APP_TITLE =测试平台SCRM销售端
 # 公司名称
-VUE_APP_COMPANY_NAME =福州市木易华康医药有限公司
+VUE_APP_COMPANY_NAME =测试有限公司
 # ICP备案号
 VUE_APP_ICP_RECORD =闽ICP备2020016609号-3
 # ICP网站访问地址

+ 2 - 2
.env.prod-bjzm

@@ -22,7 +22,7 @@ VUE_APP_COS_BUCKET = bjzmky-1323137866
 # 存储桶配置
 VUE_APP_COS_REGION = ap-chongqing
 # 线路一地址
-VUE_APP_VIDEO_LINE_1 = https://bjzmky-1323137866.cos.ap-chongqing.myqcloud.com
+VUE_APP_VIDEO_LINE_1 = https://bjzmkytcpv.ylrzcloud.com
 # 线路二地址
 VUE_APP_VIDEO_LINE_2 = https://bjzmobs.ylrztop.com
 
@@ -37,7 +37,7 @@ VUE_APP_BASE_API = '/prod-api'
 VUE_APP_COURSE_DEFAULT = 1
 
 #项目所属
-VUE_APP_PROJECT_FROM=bjczwh
+VUE_APP_PROJECT_FROM=bjzm
 
 # 路由懒加载
 VUE_CLI_BABEL_TRANSPILE_MODULES = true

+ 2 - 2
.env.prod-test

@@ -1,7 +1,7 @@
 # 页面标题
 VUE_APP_TITLE =互联网医院管理系统
-# 公司名称
-VUE_APP_COMPANY_NAME =银川鑫泰互联网医院有限公司
+# 公司名称 
+VUE_APP_COMPANY_NAME 互联网医院有限公司
 # ICP备案号
 VUE_APP_ICP_RECORD =宁ICP备2022001349号
 # ICP网站访问地址

+ 16 - 0
Dockerfile

@@ -0,0 +1,16 @@
+
+
+#基于官方 NGINX 镜像部署(精简镜像)
+FROM nginx:alpine
+
+# 复制本地打包好的 dist 目录到 NGINX 静态资源目录
+COPY ./dist /usr/share/nginx/html
+
+# 替换 NGINX 默认配置(用我们自定义的 nginx.conf)
+COPY ./nginx.conf /etc/nginx/nginx.conf
+
+# 暴露容器端口(与 nginx.conf 中 listen 一致)
+#EXPOSE 80
+
+# 启动 NGINX(前台运行,避免容器启动后退出)
+CMD ["nginx", "-g", "daemon off;"]

+ 38 - 0
nginx.conf

@@ -0,0 +1,38 @@
+# nginx.conf
+worker_processes  5;
+
+events {
+    worker_connections  1024;
+}
+
+http {
+    include       mime.types;
+    default_type  application/octet-stream;
+    client_max_body_size 200m;
+    sendfile        on;
+    keepalive_timeout  65;
+
+    server {
+        listen       80;  # 容器内端口
+        server_name  localhost;
+
+        # 前端静态资源目录(对应容器内的 /usr/share/nginx/html)
+        root   /usr/share/nginx/html;
+        index  index.html;
+
+        # 关键:处理 VUE Router history 模式(避免刷新 404)
+        location / {
+            try_files $uri $uri/ /index.html;  # 所有路由指向 index.html
+        }
+
+        # 关键:反向代理 API(解决跨域,替换为你的真实后端地址)
+        location /prod-api/ {
+            proxy_pass http://192.168.52.215:7773/;  # 后端 API 地址(末尾加 / 避免路径拼接问题)
+            proxy_set_header Host $host;
+            proxy_set_header X-Real-IP $remote_addr;
+            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+            proxy_set_header X-Forwarded-Proto $scheme;  # 传递 HTTPS 协议
+        }
+
+     }
+}

+ 1 - 1
package.json

@@ -109,7 +109,7 @@
     "normalize.css": "7.0.0",
     "nprogress": "0.2.0",
     "path-to-regexp": "2.4.0",
-    "qrcode.vue": "^3.6.0",
+    "qrcode.vue": "^1.7.0",
     "qrcodejs2": "0.0.2",
     "quill": "1.3.7",
     "screenfull": "4.2.0",

+ 8 - 0
src/api/company/companyUser.js

@@ -360,3 +360,11 @@ export function getBoundUsers(companyUserId) {
     method: 'get'
   })
 }
+
+export function unBind(userId) {
+  return request({
+    url: '/company/user/unBind',
+    method: 'post',
+    data: { userId: userId }
+  })
+}

+ 26 - 2
src/views/company/companyUser/profile/userInfo.vue

@@ -2,7 +2,7 @@
   <el-form ref="form" :model="user" :rules="rules" label-width="80px">
     <el-form-item label="用户昵称" prop="nickName">
       <el-input v-model="user.nickName" />
-    </el-form-item> 
+    </el-form-item>
     <el-form-item label="手机号码" prop="phonenumber">
       <el-input v-model="user.phonenumber" maxlength="11" />
     </el-form-item>
@@ -16,6 +16,7 @@
       </el-radio-group>
     </el-form-item>
     <el-form-item>
+      <el-button type="success" size="mini" @click="unBind">换绑微信</el-button>
       <el-button type="primary" size="mini" @click="submit">保存</el-button>
       <el-button type="danger" size="mini" @click="close">关闭</el-button>
     </el-form-item>
@@ -23,7 +24,7 @@
 </template>
 
 <script>
-import { updateUserProfile } from "@/api/company/companyUser";
+import { updateUserProfile, unBind } from "@/api/company/companyUser";
 
 export default {
   props: {
@@ -58,6 +59,29 @@ export default {
     };
   },
   methods: {
+    unBind() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          this.$confirm(
+            '确定要取消绑定吗?取消绑定后登录需要重新扫码绑定。',
+            '提示',
+            {
+              confirmButtonText: '确定',
+              cancelButtonText: '取消',
+              type: 'warning'
+            }
+          ).then(() => {
+            unBind(this.user.userId).then(response => {
+              if (response.code === 200) {
+                this.msgSuccess("解绑成功");
+              }
+            });
+          }).catch(() => {
+            this.msgInfo("已取消操作");
+          });
+        }
+      });
+    },
     submit() {
       this.$refs["form"].validate(valid => {
         if (valid) {

+ 6 - 0
src/views/components/course/userCourseCatalogDetails.vue

@@ -33,6 +33,12 @@
       <el-table-column label="上传时间" align="center" prop="createTime" />
       <el-table-column label="默认红包" align="center" prop="redPacketMoney" />
       <el-table-column label="公司红包" align="center" prop="companyRedPacketMoney" />
+      <el-table-column label="是否上架" align="center" prop="isOnPut">
+        <template slot-scope="{ row }">
+          <el-tag v-if="row.isOnPut == 0">是</el-tag>
+          <el-tag type="danger" v-if="row.isOnPut == 1">否</el-tag>
+        </template>
+      </el-table-column>
       <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
         <template slot-scope="scope">
           <el-button

+ 75 - 3
src/views/course/courseRedPacketLog/index.vue

@@ -48,7 +48,28 @@
 	      @keyup.enter.native="handleQuery"
 	    />
 	  </el-form-item>
-		<el-form-item label="课程" prop="courseId">
+      <el-form-item label="营期" prop="courseId">
+        <el-select
+          v-model="queryParams.periodId"
+          placeholder="请选择课程"
+          filterable
+          clearable
+          size="small"
+          remote
+          :remote-method="remoteMethod"
+          :loading="loadingPeriod"
+          @focus="handleFocus"
+        >
+          <el-option
+            v-for="dict in periodLists"
+            :key="dict.periodId"
+            :label="dict.periodName"
+            :value="parseInt(dict.periodId)"
+          />
+        </el-select>
+      </el-form-item>
+
+      <el-form-item label="课程" prop="courseId">
 		     <el-select filterable  v-model="queryParams.courseId" placeholder="请选择课程"  clearable size="small" @change="courseChange(queryParams.courseId)">
 		         <el-option
 		           v-for="dict in courseLists"
@@ -113,6 +134,7 @@
       <el-table-column type="selection" width="55" align="center" />
       <el-table-column label="记录编号" align="center" prop="logId" />
       <el-table-column label="批次单号" align="center" prop="outBatchNo" />
+      <el-table-column label="营期名称" align="center" prop="periodName" />
       <el-table-column label="课程名称" align="center" prop="courseName" />
       <el-table-column label="小节名称" align="center" prop="title" />
       <el-table-column label="会员id" align="center" prop="userId" />
@@ -199,7 +221,7 @@
 <script>
 import { courseList,videoList,listCourseRedPacketLog,retryCourseRedPacketLog, myListCourseRedPacketLogNew, getCourseRedPacketLog, delCourseRedPacketLog, addCourseRedPacketLog, updateCourseRedPacketLog, exportCourseRedPacketLog } from "@/api/course/courseRedPacketLog";
 import { getCompanyList } from "@/api/company/company";
-
+import { periodList } from "@/api/course/userCoursePeriod";
 export default {
   name: "CourseRedPacketLog",
   data() {
@@ -208,6 +230,7 @@ export default {
 	  deptOptions:[],
       // 遮罩层
       loading: true,
+      loadingPeriod: false,
       // 导出遮罩层
       exportLoading: false,
       // 选中数组
@@ -220,6 +243,7 @@ export default {
       showSearch: true,
       activeName:"00",
       courseLists:[],
+      periodLists:[],
       videoList:[],
       // 总条数
       total: 0,
@@ -234,6 +258,7 @@ export default {
         pageNum: 1,
         pageSize: 10,
         courseId: null,
+        periodId: null,
         userId: null,
         nickName:null,
         videoId: null,
@@ -246,6 +271,10 @@ export default {
         phoneMk: null,
         sTime:null,
         eTime:null,
+      },
+      queryPeriod: {
+        pageNum: 1,
+        pageSize: 20
       },
 	   createTime:null,
       // 表单参数
@@ -263,9 +292,50 @@ export default {
 	  courseList().then(response => {
 	    this.courseLists = response.list;
 	  });
+    periodList(this.queryPeriod).then(response => {
+      this.periodLists = response.data;
+    });
   },
   methods: {
-	  handleClick(tab, event) {
+
+    // 远程搜索方法
+    async remoteMethod(query) {
+      this.searchKeyword = query
+      if (query !== '') {
+        this.loadingPeriod = true
+        await this.getPeriodList(query)
+        this.loadingPeriod = false
+      } else {
+        // 如果输入为空,可以清空列表或加载默认数据
+        this.periodLists = []
+      }
+    },
+
+    // 获取营期列表
+    async getPeriodList(keyword = '') {
+      try {
+        const params = {
+          periodName: keyword, // 搜索关键词
+          pageNum: 1,
+          pageSize: 50 // 根据需求调整
+        }
+
+        const response = await periodList(params)
+        this.periodLists = response.data.list || response.data || []
+      } catch (error) {
+        console.error('获取营期列表失败:', error)
+        this.periodLists = []
+      }
+    },
+    // 下拉框获取焦点时加载数据
+    async handleFocus() {
+      if (this.periodLists.length === 0) {
+        this.getPeriodList();
+      }
+    },
+
+
+    handleClick(tab, event) {
 	    this.activeName=tab.name;
 	    this.queryParams.status=tab.name
 	    console.log(this.queryParams.status)
@@ -313,6 +383,7 @@ export default {
       this.form = {
         logId: null,
         courseId: null,
+        periodId: null,
         userId: null,
         videoId: null,
         companyUserId: null,
@@ -334,6 +405,7 @@ export default {
 	   this.createTime=null;
 	  this.queryParams.sTime=null;
 	  this.queryParams.eTime=null;
+	  this.queryParams.periodId=null;
       this.handleQuery();
     },
     // 多选框选中数据

+ 15 - 1
src/views/course/courseWatchLog/index.vue

@@ -95,6 +95,9 @@
           </el-option>
         </el-select>
       </el-form-item>
+      <el-form-item label="所属部门" prop="deptId">
+        <treeselect style="width:220px" v-model="queryParams.deptId" :options="deptOptions" :show-count="true" placeholder="请选择所属部门" />
+      </el-form-item>
       <el-form-item v-if="companyName==2" label="所属销售" prop="companyUserId">
         <el-select v-model="queryParams.companyUserId" clearable filterable remote
                    placeholder="请输入关键词"
@@ -625,10 +628,14 @@ import Calendar from 'vue-mobile-calendar'
 import {infoSop} from "@/api/qw/sop";
 import {getCompanyUserListLikeName} from "../../../api/company/companyUser";
 import {getQwList} from "@/api/qw/qwUser";
+import {treeselect} from "@/api/company/companyDept";
+import Treeselect from "@riophae/vue-treeselect";
+import "@riophae/vue-treeselect/dist/vue-treeselect.css";
 Vue.use(Calendar)
 
 export default {
   name: "CourseWatchLog",
+  components: {Treeselect },
   data() {
     return {
       companyUserFirstLoad: true, // 首次加载标志
@@ -682,6 +689,7 @@ export default {
         }
       },
       courseLists:[],
+      deptOptions: undefined,
       scheduleLists:[], // 营期名称
       videoList:[],
       logTypeOptions:[],
@@ -781,6 +789,7 @@ export default {
         qwUserId: null,
         qwUserName: null,
         companyUserId: null,
+        deptId: null,
         companyId: null,
         courseId: null,
         periodId:null,
@@ -829,7 +838,7 @@ export default {
     courseList().then(response => {
       this.courseLists = response.list;
     });
-
+    this.getTreeselect();
     this.getDicts("sys_course_watch_log_type_new").then(response => {
       this.logTypeOptions = response.data;
     });
@@ -850,6 +859,11 @@ export default {
     this.loading=false;
   },
   methods: {
+    getTreeselect() {
+      treeselect().then((response) => {
+        this.deptOptions = response.data;
+      });
+    },
     // 添加辅助方法
     isEmptyArray(arr) {
       return !arr || arr.length === 0;

+ 140 - 23
src/views/live/liveConsole/LiveConsole.vue

@@ -111,8 +111,9 @@
             全局用户自见
           </label>
         </div>
-        <el-scrollbar class="custom-scrollbar" style="height: 500px; width: 100%;" ref="manageRightRef">
-          <el-row v-for="m in msgList" :key="m.msgId">
+        <div class="message-container" @click="handleMessageBoxClick">
+          <el-scrollbar class="custom-scrollbar" style="height: 500px; width: 100%;" ref="manageRightRef">
+            <el-row v-for="m in msgList" :key="m.msgId">
             <el-row v-if="m.userId !== userId" style="margin-top: 5px" type="flex" align="top" >
               <el-col :span="3" style="margin-left: 10px"><el-avatar :src="m.avatar"/></el-col>
               <el-col :span="15">
@@ -124,11 +125,11 @@
                     </div>
                   </el-col>
                   <el-col>
-                    <a style="cursor: pointer;color: #ff0000;padding: 8px 8px 0 0;font-size: 12px;" @click="changeUserState(m)">{{ m.msgStatus === 1 ? '解禁' : '禁言' }}</a>
-                    <a style="cursor: pointer;color: #ff0000;padding: 8px 8px 0 0;font-size: 12px;" @click="blockUser(m)">拉黑</a>
-                    <a v-if="m.singleVisible === 1" style="cursor: pointer;color: #ff0000;padding: 8px 8px 0 0;font-size: 12px;" @click="singleVisible(m)">解除用户自见</a>
-                    <a v-else style="cursor: pointer;color: #ff0000;padding: 8px 8px 0 0;font-size: 12px;" @click="singleVisible(m)">用户自见</a>
-                    <a style="cursor: pointer;color: #ff0000;padding: 8px 8px 0 0;font-size: 12px;" @click="deleteMsg(m)">删除</a>
+                    <a style="cursor: pointer;color: #ff0000;padding: 8px 8px 0 0;font-size: 12px;" @click.stop="changeUserState(m)">{{ m.msgStatus === 1 ? '解禁' : '禁言' }}</a>
+                    <a style="cursor: pointer;color: #ff0000;padding: 8px 8px 0 0;font-size: 12px;" @click.stop="blockUser(m)">拉黑</a>
+                    <a v-if="m.singleVisible === 1" style="cursor: pointer;color: #ff0000;padding: 8px 8px 0 0;font-size: 12px;" @click.stop="singleVisible(m)">解除用户自见</a>
+                    <a v-else style="cursor: pointer;color: #ff0000;padding: 8px 8px 0 0;font-size: 12px;" @click.stop="singleVisible(m)">用户自见</a>
+                    <a style="cursor: pointer;color: #ff0000;padding: 8px 8px 0 0;font-size: 12px;" @click.stop="deleteMsg(m)">删除</a>
                   </el-col>
                 </el-row>
               </el-col>
@@ -145,7 +146,18 @@
           </el-row>
           <!-- 底部留白 -->
           <div style="height: 20px;"></div>
-        </el-scrollbar>
+          </el-scrollbar>
+          <!-- 加载最新消息按钮 -->
+          <el-button
+            v-if="showLoadLatestBtn"
+            class="load-latest-btn"
+            type="primary"
+            size="small"
+            @click.stop="loadLatestMessages"
+            icon="el-icon-refresh">
+            加载最新消息
+          </el-button>
+        </div>
         <!--        <div class="message-list">-->
         <!--          <div class="message-item" v-for="msg in msgList" :key="msg.id">-->
         <!--            <div class="message-avatar">-->
@@ -390,6 +402,15 @@ export default {
       topMsgForm: {
         msg: '',
         duration: 5
+      },
+      // 消息滚动控制
+      isAutoScrollEnabled: true, // 是否启用自动滚动
+      autoScrollTimer: null, // 自动滚动定时器
+      showLoadLatestBtn: false, // 是否显示加载最新消息按钮
+      msgParams: {
+        pageNum: 1,
+        pageSize: 30,
+        liveId: null
       }
     };
   },
@@ -431,6 +452,65 @@ export default {
     this.initScrollListeners();
   },
   methods: {
+    // 点击消息框
+    handleMessageBoxClick() {
+      // 点击消息框时,停止自动滚动
+      this.isAutoScrollEnabled = false;
+      // 停止自动滚动定时器
+      if (this.autoScrollTimer) {
+        clearTimeout(this.autoScrollTimer);
+        this.autoScrollTimer = null;
+      }
+      // 检查是否在底部,如果不在底部则显示加载最新消息按钮
+      this.$nextTick(() => {
+        if (this.$refs.manageRightRef && this.$refs.manageRightRef.wrap) {
+          const wrap = this.$refs.manageRightRef.wrap;
+          const scrollHeight = wrap.scrollHeight;
+          const clientHeight = wrap.clientHeight;
+          const currentScrollTop = wrap.scrollTop;
+          const maxScrollTop = scrollHeight - clientHeight;
+
+          if (currentScrollTop < maxScrollTop - 50) {
+            this.showLoadLatestBtn = true;
+          }
+        }
+      });
+    },
+    // 滚动到底部
+    scrollToBottom(forceScroll = false) {
+      // 如果自动滚动被禁用且不是强制滚动,则不执行
+      console.log("scrollToBottom")
+      console.log(!this.isAutoScrollEnabled && !forceScroll)
+      console.log(this.$refs.manageRightRef && this.$refs.manageRightRef.wrap)
+      console.log(forceScroll || this.isAutoScrollEnabled)
+      if (!this.isAutoScrollEnabled && !forceScroll) {
+        return;
+      }
+
+      if (this.$refs.manageRightRef && this.$refs.manageRightRef.wrap) {
+        this.$nextTick(() => {
+          const wrap = this.$refs.manageRightRef.wrap;
+          if (!wrap) return;
+
+
+          // 强制滚动或启用自动滚动时,直接滚动到底部并隐藏按钮
+          if (forceScroll || this.isAutoScrollEnabled) {
+            this.showLoadLatestBtn = false;
+            this.$refs.manageRightRef.wrap.scrollTop = this.$refs.manageRightRef.wrap.scrollHeight - this.$refs.manageRightRef.wrap.clientHeight
+          }
+        });
+      }
+    },
+    // 加载最新消息
+    loadLatestMessages() {
+      this.showLoadLatestBtn = false;
+      // 恢复自动滚动
+      this.isAutoScrollEnabled = true;
+      // 重新请求最新消息
+      this.resetMsgParams();
+      // loadMsgList 中会自动滚动到底部,因为 isAutoScrollEnabled 已经是 true
+      this.loadMsgList();
+    },
     singleVisible(m){
       // 过滤当前所有消息 找到userId的相同的消息 更改他们的自可见状态
       m.singleVisible= m.singleVisible === 1 ? 0 : 1
@@ -637,12 +717,19 @@ export default {
             this.msgList.shift()
           }
           this.msgList.push(message)
-          // 移动到底部
-          this.$nextTick(() => {
-            setTimeout(() => {
-              this.$refs.manageRightRef.wrap.scrollTop = this.$refs.manageRightRef.wrap.scrollHeight - this.$refs.manageRightRef.wrap.clientHeight
-            }, 200)
-          })
+          // 如果启用自动滚动,自动滚动到底部
+          console.log("handleWsMessage")
+          console.log(this.isAutoScrollEnabled)
+          if (this.isAutoScrollEnabled) {
+            this.$nextTick(() => {
+              this.autoScrollTimer = setTimeout(() => {
+                this.scrollToBottom();
+              }, 200);
+            });
+          } else {
+            // 自动滚动被禁用时,显示加载最新消息按钮
+            this.showLoadLatestBtn = true;
+          }
         } else if (cmd === 'entry' || cmd === 'out') {
           const user = data;
           const online = cmd === 'entry' ? 0 : 1; // 0=在线,1=离线
@@ -1076,22 +1163,13 @@ export default {
           let totalPage = (total % this.msgParams.pageSize == 0) ? Math.floor(total / this.msgParams.pageSize) : Math.floor(total / this.msgParams.pageSize + 1);
           rows.forEach(row => {
             if (!this.msgList.some(m => m.msgId === row.msgId)) {
-
               let user = this.alDisplayList.find(u => u.userId === row.userId)
               if (user) {
                 row.msgStatus = user.msgStatus
               } else {
                 row.msgStatus = 0
               }
-
               this.msgList.push(row)
-
-              // 移动到底部
-              this.$nextTick(() => {
-                setTimeout(() => {
-                  this.$refs.manageRightRef.wrap.scrollTop = this.$refs.manageRightRef.wrap.scrollHeight - this.$refs.manageRightRef.wrap.clientHeight
-                }, 200)
-              })
             }
           })
 
@@ -1100,6 +1178,31 @@ export default {
           this.alDisplayList.forEach(u => {
             this.msgList.filter(m => m.userId === u.userId).forEach(m => m.msgStatus = u.msgStatus)
           })
+
+          // 所有消息加载完成后,根据自动滚动状态决定是否滚动
+          this.$nextTick(() => {
+            setTimeout(() => {
+              if (this.isAutoScrollEnabled) {
+                // 如果启用自动滚动,强制滚动到底部并隐藏按钮
+                this.scrollToBottom(true);
+              } else {
+                // 如果禁用自动滚动,检查是否在底部,决定是否显示按钮
+                if (this.$refs.manageRightRef && this.$refs.manageRightRef.wrap) {
+                  const wrap = this.$refs.manageRightRef.wrap;
+                  const scrollHeight = wrap.scrollHeight;
+                  const clientHeight = wrap.clientHeight;
+                  const currentScrollTop = wrap.scrollTop;
+                  const maxScrollTop = scrollHeight - clientHeight;
+
+                  if (currentScrollTop < maxScrollTop - 50) {
+                    // this.showLoadLatestBtn = true;
+                  } else {
+                    this.showLoadLatestBtn = false;
+                  }
+                }
+              }
+            }, 300);
+          });
         }
       })
 
@@ -1200,6 +1303,7 @@ export default {
         pageSize: 30,
         liveId: this.liveId
       };
+      // 重置时不改变按钮状态,由后续的滚动逻辑决定
       this.taskParams = {
         currentPage: 1,
         pageSize: 20,
@@ -1320,6 +1424,9 @@ export default {
     if (this.autoMsgTimer != null) {
       clearInterval(this.autoMsgTimer);
     }
+    if (this.autoScrollTimer) {
+      clearTimeout(this.autoScrollTimer);
+    }
   },
   // 使用 deactivated 和 activated 钩子替代 beforeDestroy 和 destroyed
   deactivated() {
@@ -1623,4 +1730,14 @@ export default {
   text-align: center;
   border-radius: 4px;
 }
+.message-container {
+  position: relative;
+}
+.load-latest-btn {
+  position: absolute;
+  bottom: 20px;
+  right: 20px;
+  z-index: 10;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
+}
 </style>

+ 3 - 2
src/views/qw/externalContactUnassigned/index.vue

@@ -195,7 +195,7 @@
       :total="total"
       :page.sync="queryParams.pageNum"
       :limit.sync="queryParams.pageSize"
-      :page-sizes="[100, 200, 300, 500]"
+      :page-sizes="[10,100, 200, 300, 500]"
       @pagination="getList"
     />
 
@@ -293,7 +293,7 @@ export default {
       // 查询参数
       queryParams: {
         pageNum: 1,
-        pageSize: 500,
+        pageSize: 10,
         userId: null,
         qwUserName: null,
         externalUserId: null,
@@ -441,6 +441,7 @@ export default {
     },
     // 多选框选中数据
     handleSelectionChange(selection) {
+      
       this.ids = selection.map(item => item.id)
       this.single = selection.length!==1
       this.multiple = !selection.length

+ 15 - 0
src/views/users/user/blacklist.vue

@@ -11,6 +11,16 @@
           @keyup.enter.native="handleQuery"
         />
       </el-form-item>
+      <el-form-item label="项目" prop="projectId">
+        <el-select v-model="queryParams.projectId" placeholder="请选择项目" filterable clearable size="small">
+          <el-option
+            v-for="dict in projectOptions"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="dict.dictValue"
+          />
+        </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>
@@ -111,11 +121,13 @@ export default {
       total: 0,
       // 用户列表数据
       userList: [],
+      projectOptions:[],
       // 查询参数
       queryParams: {
         pageNum: 1,
         pageSize: 10,
         keyword: null,
+        projectId: null,
         status: 2
       },
       selectUser: []
@@ -123,6 +135,9 @@ export default {
   },
   created() {
     this.getList();
+    this.getDicts("sys_course_project").then(response => {
+      this.projectOptions = response.data;
+    });
   },
   methods: {
     /** 查询客户列表 */