Browse Source

新增漂浮在线客服

fly 1 week ago
parent
commit
f1d9c4a295
6 changed files with 317 additions and 30 deletions
  1. 26 1
      src/App.vue
  2. 227 0
      src/components/FloatService.vue
  3. 27 5
      src/views/OrderList.vue
  4. 20 19
      src/views/Profile.vue
  5. 2 2
      src/views/home/index.vue
  6. 15 3
      src/views/jifen.vue

+ 26 - 1
src/App.vue

@@ -4,22 +4,47 @@
     <div class="tab-bar" v-if="!$route.meta.hideTabBar">
       <TabBar></TabBar>
     </div>
+    <float-service @service-click="toKefu" />
   </div>
 </template>
 
 <script>
 import TabBar from '@/components/TabBar.vue'
+import FloatService from '@/components/FloatService.vue'
+import { getUserInfo } from '@/api/home'
 
 export default {
   name: 'App',
   components: {
-    TabBar
+    TabBar,
+    FloatService
+  },
+  data() {
+    return {
+      userInfo: null
+    }
   },
   computed: {
     showTabBar() {
       const hideTabBarRoutes = ['/login', '/register', '/profile/detail', '/culture/detail','/InvitePeople']
       return !hideTabBarRoutes.includes(this.$route.path)
     }
+  },
+  methods: {
+    async toKefu() {
+      if (!this.userInfo) {
+        const res = await getUserInfo();
+        this.userInfo = res.data;
+      }
+      if (this.userInfo && this.userInfo.link && this.userInfo.link[0]) {
+        window.open(this.userInfo.link[0].value, '_blank');
+      }
+    }
+  },
+  async mounted() {
+    // 预先获取用户信息
+    const res = await getUserInfo();
+    this.userInfo = res.data;
   }
 }
 </script>

+ 227 - 0
src/components/FloatService.vue

@@ -0,0 +1,227 @@
+<template>
+  <div 
+    class="float-service" 
+    @touchstart="startDrag"
+    @touchmove="onDrag"
+    @touchend="endDrag"
+    @click="handleServiceClick"
+    :style="serviceStyle"
+  >
+    <div class="service-icon">
+      <svg t="1751785432156" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4282" width="24" height="24"><path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64z m0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z" fill="#ffffff" p-id="4283"></path><path d="M464 336h-64c-4.4 0-8 3.6-8 8v384c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V592h56c17.7 0 32 14.3 32 32v104c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V624c0-57.3-46.7-104-104-104h-40V344c0-4.4-3.6-8-8-8z" fill="#ffffff" p-id="4284"></path><path d="M672 464c26.5 0 48-21.5 48-48s-21.5-48-48-48-48 21.5-48 48 21.5 48 48 48z m0 160c26.5 0 48-21.5 48-48s-21.5-48-48-48-48 21.5-48 48 21.5 48 48 48z" fill="#ffffff" p-id="4285"></path></svg>
+    </div>
+    <div class="service-text">
+      <span>在</span>
+      <span>线</span>
+      <span>客</span>
+      <span>服</span>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'FloatService',
+  data() {
+    return {
+      servicePosition: {
+        x: 0,
+        y: window.innerHeight / 2
+      },
+      isDragging: false,
+      startX: 0,
+      startY: 0,
+      lastValidX: 0,
+      lastValidY: 0,
+      dragStartTime: 0,
+      isDragMove: false
+    }
+  },
+  computed: {
+    serviceStyle() {
+      return {
+        transform: `translate(${this.servicePosition.x}px, ${this.servicePosition.y}px)`,
+        transition: this.isDragging ? 'none' : 'all 0.3s ease'
+      }
+    }
+  },
+  methods: {
+    startDrag(event) {
+      this.isDragging = true;
+      this.dragStartTime = Date.now();
+      this.isDragMove = false;
+      const touch = event.touches[0];
+      this.startX = touch.clientX - this.servicePosition.x;
+      this.startY = touch.clientY - this.servicePosition.y;
+      this.lastValidX = this.servicePosition.x;
+      this.lastValidY = this.servicePosition.y;
+      
+      // 阻止默认事件,防止触发click
+      event.preventDefault();
+    },
+    onDrag(event) {
+      if (!this.isDragging) return;
+      this.isDragMove = true;
+      const touch = event.touches[0];
+      
+      // 计算新位置
+      let newX = touch.clientX - this.startX;
+      let newY = touch.clientY - this.startY;
+      
+      // 限制Y轴范围
+      const maxY = window.innerHeight - 100;
+      newY = Math.max(50, Math.min(newY, maxY));
+      
+      // 更新位置
+      this.servicePosition.x = newX;
+      this.servicePosition.y = newY;
+      
+      // 阻止页面滚动
+      event.preventDefault();
+    },
+    endDrag(event) {
+      if (!this.isDragging) return;
+      
+      // 如果拖动时间小于200ms且移动距离小于10px,认为是点击
+      const dragTime = Date.now() - this.dragStartTime;
+      const dragDistance = Math.abs(this.servicePosition.x - this.lastValidX) + 
+                         Math.abs(this.servicePosition.y - this.lastValidY);
+      
+      if (dragTime < 200 && dragDistance < 10 && !this.isDragMove) {
+        this.$emit('service-click');
+      }
+      
+      this.isDragging = false;
+      this.isDragMove = false;
+      
+      // 计算是否吸附到左边还是右边
+      const windowWidth = window.innerWidth;
+      const halfWidth = windowWidth / 2;
+      
+      // 如果在左半边,吸附到左边,否则吸附到右边
+      // 考虑按钮宽度(22px),确保完全贴边
+      this.servicePosition.x = this.servicePosition.x < halfWidth ? 0 : (windowWidth - 30);
+      
+      // 保存位置
+      localStorage.setItem('servicePosition', JSON.stringify(this.servicePosition));
+      
+      // 阻止默认事件,防止触发click
+      event.preventDefault();
+    },
+    handleServiceClick(event) {
+      // 如果是通过触摸操作触发的,忽略click事件
+      if (this.isDragging || this.isDragMove) {
+        event.preventDefault();
+        return;
+      }
+      // 只处理非触摸设备的点击
+      if (!('ontouchstart' in window)) {
+        this.$emit('service-click');
+      }
+    },
+    handleResize() {
+      // 确保按钮在窗口大小改变时仍然可见
+      const windowWidth = window.innerWidth;
+      const windowHeight = window.innerHeight;
+      
+      // 限制Y轴范围
+      const maxY = windowHeight - 100;
+      this.servicePosition.y = Math.max(50, Math.min(this.servicePosition.y, maxY));
+      
+      // 确保X轴吸附正确,考虑按钮宽度
+      this.servicePosition.x = this.servicePosition.x > windowWidth / 2 ? (windowWidth - 22) : 0;
+      
+      // 保存新位置
+      localStorage.setItem('servicePosition', JSON.stringify(this.servicePosition));
+    }
+  },
+  mounted() {
+    // 从localStorage获取上次保存的位置
+    const savedPosition = localStorage.getItem('servicePosition');
+    if (savedPosition) {
+      try {
+        const position = JSON.parse(savedPosition);
+        // 确保位置在有效范围内
+        const windowWidth = window.innerWidth;
+        const windowHeight = window.innerHeight;
+        this.servicePosition = {
+          x: position.x > windowWidth / 2 ? windowWidth - 60 : 0,
+          y: Math.max(50, Math.min(position.y, windowHeight - 100))
+        };
+      } catch (e) {
+        console.error('Failed to parse saved position:', e);
+      }
+    }
+
+    // 监听窗口大小变化
+    window.addEventListener('resize', this.handleResize);
+  },
+  beforeDestroy() {
+    // 保存位置到localStorage
+    localStorage.setItem('servicePosition', JSON.stringify(this.servicePosition));
+    window.removeEventListener('resize', this.handleResize);
+  }
+}
+</script>
+
+<style scoped>
+.float-service {
+  position: fixed;
+  top: 0;
+  left: 0;
+  background-color: #1989fa;
+  border-radius: 20px;
+  padding: 10px 5px;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  cursor: pointer;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
+  z-index: 999;
+  touch-action: none;
+  user-select: none;
+  width: 22px;
+}
+
+.service-icon {
+  margin-bottom: 5px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+
+.service-text {
+  display: flex;
+  flex-direction: column;
+  color: white;
+  font-size: 14px;
+  line-height: 1.5;
+}
+
+.service-text span {
+  display: block;
+  text-align: center;
+  margin: 2px 0;
+}
+
+.float-service:active {
+  background-color: #0e7bfa;
+}
+
+/* 移动端适配 */
+@media (max-width: 375px) {
+  .float-service {
+    padding: 8px 4px;
+    width: 40px;
+  }
+  
+  .service-icon svg {
+    width: 20px;
+    height: 20px;
+  }
+  
+  .service-text {
+    font-size: 12px;
+  }
+}
+</style> 

+ 27 - 5
src/views/OrderList.vue

@@ -12,12 +12,14 @@
     <div class="record-list">
       <div class="listItem" v-for="(item, index) in records" :key="index">
         <div class="flex listItemC">
+          <div class="type-text">类型</div>
           <div class="tr4">金额</div>
           <div class="status-text">状态</div>
           <div class="remark">详情</div>
           <div class="right">日期</div>
         </div>
         <div class="flex listItemB">
+          <div class="type-text">{{ getTypeText(item.type) }}</div>
           <div class="tr4">{{ item.money }}</div>
           <div class="status-text">{{ getStatusText(item.stat) }}</div>
           <div class="remark">{{ item.remark }}</div>
@@ -68,6 +70,16 @@ export default {
         2: '提现失败'
       };
       return statusMap[stat] || '未知';
+    },
+    getTypeText(type) {
+      const typeMap = {
+        1: '每日分红',
+        2: '科技分红',
+        3: '保障金',
+        4: '养老金',
+        5: '科技股'
+      };
+      return typeMap[type] || '未知类型';
     }
   }
 }
@@ -144,12 +156,19 @@ export default {
 }
 
 .tr4 {
-  flex: 0 0 75px;
+  flex: 0 0 60px;
   text-align: center;
 }
 
+.type-text {
+  flex: 0 0 70px;
+  text-align: center;
+  color: #666;
+  font-size: 14px;
+}
+
 .right {
-  flex: 0 0 132px;
+  flex: 0 0 90px;
   text-align: right;
 }
 
@@ -170,11 +189,14 @@ export default {
 }
 
 .remark {
-  flex: 1 1 87px;
+  flex: 1 1 60px;
   text-align: left;
   color: #666;
-  font-size: 13px;
-  padding-left: 10px;
+  font-size: 12px;
+  padding-left: 8px;
   word-break: break-all;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  
 }
 </style> 

+ 20 - 19
src/views/Profile.vue

@@ -29,19 +29,19 @@
 
     <!-- 收益板块组 -->
     <div class="profit-section">
-      <div class="asset-card-v4">
+   <!--   <div class="asset-card-v4">
         <div class="asset-info-v4">
           <span class="asset-label-v4">每日分红</span>
           <span class="asset-amount-v4">{{ userInfo.dayred }}</span>
         </div>
         <button class="asset-btn-v4 button-click-effect" @click="handleRedFlagWithdraw">提现</button>
-      </div>
+      </div> -->
       <div class="asset-card-v4">
         <div class="asset-info-v4">
           <span class="asset-label-v4">科技分红</span>
           <span class="asset-amount-v4">{{ userInfo.kjfh }}</span>
         </div>
-        <button class="asset-btn-v4 button-click-effect" @click="handleRedFlagWithdraw">提现</button>
+        <button class="asset-btn-v4 button-click-effect" @click="handleTechDividendWithdraw">提现</button>
       </div>
       <div class="asset-card-v4">
         <div class="asset-info-v4">
@@ -50,20 +50,20 @@
         </div>
         <button class="asset-btn-v4 button-click-effect" @click="handleRetirementWithdraw">提现</button>
       </div>
-      <div class="asset-card-v4">
+     <!-- <div class="asset-card-v4">
         <div class="asset-info-v4">
           <span class="asset-label-v4">养老金</span>
           <span class="asset-amount-v4">{{ userInfo.score?userInfo.score:0 }}</span>
         </div>
         <button class="asset-btn-v4 button-click-effect" @click="handlePartySalaryWithdraw">提现</button>
-      </div>
-      <div class="asset-card-v4">
+      </div> -->
+      <!-- <div class="asset-card-v4">
         <div class="asset-info-v4">
           <span class="asset-label-v4">科技股</span>
           <span class="asset-amount-v4">{{ userInfo.guquan }}</span>
         </div>
         <button class="asset-btn-v4 button-click-effect" @click="handleDailyCashWithdraw">提现</button>
-      </div>
+      </div> -->
       <div class="asset-card-v4">
         <div class="asset-info-v4">
           <span class="asset-label-v4">我的积分</span>
@@ -225,30 +225,31 @@ export default {
         this.showConfirm = false;
       }
     },
-    // 红旗资产提现
+    
     handleRedFlagWithdraw() {
       this.$router.push('/mention?type=1');
-      // this.$refs.toast.show('暂未开启', 'info');
     },
-    // 退休补贴提现
-    handleRetirementWithdraw() {
+    
+    handleTechDividendWithdraw() {
       this.$router.push('/mention?type=2');
-      // this.$refs.toast.show('中央资金筹备中', 'info');
     },
-    // 党员薪资提现
-    handlePartySalaryWithdraw() {
+    
+    handleRetirementWithdraw() {
       this.$router.push('/mention?type=3');
-      // this.$refs.toast.show('统一打款', 'info');
     },
-    // 每日现金提现
-    handleDailyCashWithdraw() {
+   
+    handlePartySalaryWithdraw() {
       this.$router.push('/mention?type=4');
     },
-    // 医疗补贴提现
+   
+    handleDailyCashWithdraw() {
+      this.$router.push('/mention?type=5');
+    },
+   
     handleMedicalWithdraw() {
       this.$router.push('/mention?type=5');
     },
-    // 原始股权提现
+  
     handleOriginalSharesWithdraw() {
       this.$router.push('/jifen');
     },

+ 2 - 2
src/views/home/index.vue

@@ -120,7 +120,7 @@ export default {
       },
       newsList: [],
       showDialog: true,
-      showAuthDialog: false,
+      showAuthDialog: false
     };
   },
   components: {
@@ -202,7 +202,7 @@ export default {
       if (this.userInfo.link && this.userInfo.link[0] && this.userInfo.link[0].value) {
         window.open(this.userInfo.link[0].value, '_blank');
       }
-    },
+    }
   }
 };
 </script>

+ 15 - 3
src/views/jifen.vue

@@ -74,6 +74,7 @@
 <script>
 import Toast from '@/components/Toast.vue';
 import { getLotteryPrize, exchangePrize } from '@/api/profile';
+import { getUserInfo } from '@/api/home';
 
 export default {
   name: 'JifenPage',
@@ -84,14 +85,25 @@ export default {
     return {
       showConfirmDialog: false,
       selectedProduct: null,
-      products: []
+      products: [],
+      userPoints: 0  // 添加用户积分属性
     }
   },
   mounted() {
     this.loadProducts();
+    this.loadUserInfo();
   },
   methods: {
-   
+    // 加载用户信息
+    async loadUserInfo() {
+      try {
+        const res = await getUserInfo();
+        this.userPoints = res.data.jifen || 0; // 获取用户积分
+      } catch (error) {
+        console.error('获取用户信息失败:', error);
+        this.$refs.toast.show('获取用户信息失败', 'error');
+      }
+    },
 
     // 加载商品列表
     async loadProducts() {
@@ -127,7 +139,7 @@ export default {
        console.log(res);
         if(res.code == 1){
           this.$refs.toast.show('兑换成功!', 'success');
-          this.userPoints -= this.selectedProduct.points;
+          this.userPoints -= this.selectedProduct.probability; // 修正为probability字段
           this.showConfirmDialog = false;
           this.selectedProduct = null;
         }else{