zhb 1 ماه پیش
والد
کامیت
bcee904fd2

+ 8 - 24
App.vue

@@ -13,7 +13,6 @@ import {
 } from "@/hooks/useRoute";
 import useGlobalStore from "@/stores/use-global-store";
 import { useAppUpdate } from '@/hooks/useAppUpdate'
-import liveChat from '@/utils/liveChat';
 const { checkUpdate } = useAppUpdate()
 const globalStore = useGlobalStore()
 onLoad((options) => {
@@ -25,19 +24,11 @@ onShow((options) => {
 	checkUpdate()
 })
 onLaunch((options) => {
-	updateRoute();
+	// updateRoute();
 	checkUpdate()
 })
-const adjustLiveChatPosition = () => {
-	// #ifdef H5
-	const footerBody = document.getElementById('footer-body');
-	if (footerBody) {
-		liveChat.adjustPosition(footerBody.offsetHeight);
-	}
-	// #endif
-}
+
 watch(locale, () => {
-	console.log(locale.value, 23123)
 	const currentPath = route.path;
 	menu.value.forEach((item, index) => {
 		if (item.children) {
@@ -51,13 +42,7 @@ watch(locale, () => {
 		}
 	});
 }, { immediate: true })
-onBeforeUnmount(() => {
-	// #ifdef H5
-	if (typeof window !== 'undefined') {
-		window.removeEventListener('resize', adjustLiveChatPosition)
-	}
-	// #endif
-})
+
 onMounted(() => {
 	const sysInfo = uni.getSystemInfoSync();
 	globalStore.setBarHeight(sysInfo.statusBarHeight);
@@ -70,12 +55,6 @@ onMounted(() => {
 		if (instance) {
 			window.vm = instance.proxy
 		}
-
-		// 调用一次调整位置
-		adjustLiveChatPosition()
-
-		// 监听窗口大小变化,动态调整
-		window.addEventListener('resize', adjustLiveChatPosition)
 	}
 	// #endif
 });
@@ -106,6 +85,11 @@ span {
 	user-select: text !important;
 }
 
+/* 强制修复 uni-datetime-picker 重复渲染双日历 */
+// .uni-calendar+.uni-calendar {
+// 	display: none !important;
+// }
+
 :deep(.u-toolbar__wrapper__confirm) {
 	font-size: 20px !important;
 }

+ 61 - 6
components/cwg-page-wrapper.vue

@@ -4,7 +4,6 @@
       <cwg-pc-header @open-right-drawer="openRightDrawer" @open-left-drawer="openLeftDrawer" class="header-box"
         :sidebarVisible="sidebarVisible" />
       <view class="sidebar-mask mask-visible" v-if="sidebarVisible" @click="openLeftDrawer">
-
       </view>
       <view class="fixed"></view>
       <cwg-header v-if=pageTitle class="custom-header" :title="pageTitle" />
@@ -16,13 +15,12 @@
     <view class="page-content" :style="{ backgroundColor: bgColor }">
       <cwg-match-media :max-width="991" v-if="!isLoginPage">
         <view class="left-sidebar" :class="{ 'sidebar-visible': sidebarVisible }">
-          <cwg-sidebar />
+          <cwg-sidebar @handle-click="openLeftDrawer" />
         </view>
         <view class="sidebar-mask" v-if="sidebarVisible" @click="openLeftDrawer" :style="{
           opacity: maskVisible ? 1 : 0,
           transition: 'opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1)'
         }">
-
         </view>
       </cwg-match-media>
       <view class="content-info">
@@ -34,13 +32,20 @@
         <view class="content-wrapper" :class="{ 'content-wrapper-padding': isContentPadding }">
           <!-- <cwg-header /> -->
           <transition name="fade" mode="out-in">
-          <view>
-            <slot />
-          </view>
+            <view>
+              <slot />
+            </view>
           </transition>
           <cwg-custom-footer />
         </view>
       </view>
+      <cwg-match-media :max-width="991">
+        <view class="chat-icon"
+          :class="{ 'chat-icon-expanded': isChatIconExpanded, 'chat-icon-hidden': type == 'chat' }"
+          @click="handleChatIconClick">
+          <cwg-icon name="chat" color="#fff" />
+        </view>
+      </cwg-match-media>
       <view :style="{ height: isTabBarPage ? '60px' : '0px' }" />
     </view>
   </view>
@@ -87,12 +92,17 @@ const props = defineProps({
     type: String,
     default: '',
   },
+  type: {
+    type: String,
+    default: '',
+  },
 })
 const isDark = computed(() => globalStore.theme === 'dark')
 const isTabBarPage = ref(false)
 const rightDrawerRef = ref<any>(null)
 const noticeDrawerRef = ref<any>(null)
 const ibRef = ref<any>(null)
+const isChatIconExpanded = ref(false)
 
 // 事件处理函数
 const handleOpenIb = () => {
@@ -102,6 +112,18 @@ const handleOpenIb = () => {
 const handleOpenNoticeDrawer = () => {
   noticeDrawerRef.value?.open()
 }
+// 处理聊天图标点击
+const handleChatIconClick = () => {
+  // 如果还没显示 → 先滑出来
+  if (!isChatIconExpanded.value) {
+    isChatIconExpanded.value = true
+    return
+  }
+  router.push('/pages/common/chat')
+  setTimeout(() => {
+    isChatIconExpanded.value = false
+  }, 300)
+}
 
 const handleOpenRightDrawer = () => {
   openRightDrawer()
@@ -243,6 +265,39 @@ onShow(() => {
   min-height: calc(100vh - 56px);
 }
 
+.chat-icon {
+  width: px2rpx(50);
+  height: px2rpx(50);
+  border-radius: 50%;
+  background-color: #cf1322;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  position: fixed;
+  bottom: px2rpx(25);
+  right: px2rpx(-25);
+  z-index: 999;
+  cursor: pointer;
+  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+  box-shadow: 0 px2rpx(8) px2rpx(20) rgba(0, 0, 0, 0.15);
+  will-change: transform;
+}
+
+.chat-icon:hover {
+  transform: scale(1.1);
+}
+
+.chat-icon-expanded {
+  bottom: px2rpx(20);
+  right: px2rpx(20);
+  transform: scale(1.1);
+  box-shadow: 0 px2rpx(12) px2rpx(30) rgba(0, 0, 0, 0.2);
+}
+
+.chat-icon-hidden {
+  display: none;
+}
+
 .fixed {
   // position: fixed;
   // top: 0;

+ 11 - 4
components/cwg-sidebar.vue

@@ -38,15 +38,22 @@ import useUserStore from '@/stores/use-user-store'
 import { useMenuSplit } from '@/composables/useMenuSplit'
 import { computed } from 'vue'
 import { storeToRefs } from 'pinia'
-
-const { menus, setSubmenuRef, setMode, handleClick, handleSubmenuClick, mode } = useMenuSplit()
+const handleClick1 = (item: MenuItem) => {
+    console.log(item)
+    emit('handle-click', item)
+}
+const { menus, setSubmenuRef, setMode, handleClick, handleSubmenuClick, mode } = useMenuSplit(handleClick1)
 const userStore = useUserStore()
 const { userInfo } = storeToRefs(userStore)
+
+const emit = defineEmits(['handle-click'])
+
 // ib按钮展示
 const ibStatus = computed(() => {
-  const info: any = userInfo.value
-  return !!info && !!info.customInfo && info.customInfo.ibInvalid == 0 && !!info.ibInfo
+    const info: any = userInfo.value
+    return !!info && !!info.customInfo && info.customInfo.ibInvalid == 0 && !!info.ibInfo
 })
+
 </script>
 
 <style scoped lang="scss">

+ 12 - 4
composables/useMenuSplit.ts

@@ -6,6 +6,7 @@ import { useWindowWidth } from '@/composables/useWindowWidth'
 import useGlobalStore from '@/stores/use-global-store'
 import useRouter from '@/hooks/useRouter'
 import useRoute from '@/hooks/useRoute'
+import LiveChatService from '@/utils/liveChat.js'
 
 export interface MenuItem {
     path: string
@@ -27,7 +28,7 @@ function cloneMenu(menus: MenuItem[]): MenuItem[] {
     }))
 }
 
-export function useMenuSplit() {
+export function useMenuSplit(handleClick1: (item: MenuItem) => void) {
     const { locale } = useI18n()
     const globalStore = useGlobalStore()
     const mode = computed(() => globalStore.mode)
@@ -101,13 +102,18 @@ export function useMenuSplit() {
         // 无子菜单:执行跳转或特殊操作
         if (!item.children || item.children.length === 0) {
             // #ifdef H5
-            if (item.type === 'chat') {
-                if (window.LiveChatWidget) {
-                    window.LiveChatWidget.call('maximize')
+            if (item.type === 'chat' && !shouldShowLanguageMenu.value) {
+                if (LiveChatService) {
+                    LiveChatService.showChat();
                 }
                 return
+            } else {
+                handleClick1(item)
+                router.push(item.path)
+                return
             }
             // #endif
+            handleClick1(item)
             router.push(item.path)
             return
         }
@@ -135,7 +141,9 @@ export function useMenuSplit() {
             return
         }
         // 内部页面跳转
+        handleClick1(subItem)
         router.push(subItem.path)
+
     }
 
     // 窗口大小变化时重新计算所有已展开子菜单的高度

+ 58 - 24
pages/common/chat.vue

@@ -1,41 +1,75 @@
 <template>
-  <cwg-page-wrapper class="create-page" :isHeaderFixed="true">
-    <cwg-header :title="title" />
-    <view class="chat-wrapper">
-      <web-view v-if="chatUrl" :src="chatUrl" :webview-styles="webviewStyles" />
+  <cwg-page-wrapper class="webview-page" type="chat">
+    <view class="page-container">
+      <!-- WebView 内容区(自动占满剩余空间,不遮挡导航栏) -->
+      <view class="web-view-container">
+        <web-view :src="fileUrl" class="web-view" />
+      </view>
     </view>
   </cwg-page-wrapper>
 </template>
 
-<script setup lang="ts">
-import { computed } from 'vue'
+<script setup>
+import { ref, computed } from 'vue'
+import { onLoad } from '@dcloudio/uni-app'
+import Config from '@/config/index'
+const { Host80 } = Config
+import getWebBase from '@/utils/webBase'
+const webBase = getWebBase()
 import { useI18n } from 'vue-i18n'
-import { lang } from '@/composables/config'
-import useGlobalStore from '@/stores/use-global-store'
-
 const { t } = useI18n()
-const globalStore = useGlobalStore()
-
-const title = computed(() => t('Label.CustomerService') || '在线客服')
-const statusBarHeight = computed(() => globalStore.statusBarHeight || 0)
+const fileUrl = ref('')
+const title = ref('')
+const pageTitle = computed(() => t(title.value))
+const fileType = ref('')
+// 获取文件后缀
+const getFileExt = (name) => {
+  if (!name) return ''
+  return name.split('.').pop()?.toUpperCase()
+}
+onLoad((options) => {
+  title.value = options.title || '文件预览'
+  fileUrl.value = `/iframe/livechat.html`
+  console.log(fileUrl.value, 1212);
 
-const webviewStyles = computed(() => ({
-  progress: { color: '#ea002a' },
-}))
 
-const chatUrl = computed(() => {
-  const currentLang = lang.value || 'cn'
-  const base = 'https://secure.cwgmx.com/mobile/zopim.html'
-  const query = `lang=${encodeURIComponent(currentLang)}&statusBarHeight=${encodeURIComponent(String(statusBarHeight.value))}`
-  return `${base}?${query}`
 })
 </script>
 
 <style scoped lang="scss">
 @import "@/uni.scss";
 
-.chat-wrapper {
+.webview-page {
+  :deep(.content-wrapper) {
+    padding: 0 !important;
+  }
+
+  :deep(.page-content) {
+    height: calc(100vh - 56px);
+  }
+
+  :deep(.fixed) {
+    height: 56px;
+  }
+}
+
+/* 页面根容器:弹性布局,完美分配导航栏和web-view高度 */
+.page-container {
+  display: flex;
+  flex-direction: column;
+  width: 100%;
+  height: 100vh;
+}
+
+/* 顶部导航栏:正确适配安全区,高度计算正确 */
+.web-view-container {
+  flex: 1;
+  width: 100%;
+  overflow: hidden;
+}
+
+.web-view {
   width: 100%;
-  min-height: calc(100vh - 56px);
+  height: 100%;
 }
-</style>
+</style>

+ 1 - 1
pages/common/webview.vue

@@ -32,7 +32,7 @@ onLoad((options) => {
   fileType.value = getFileExt(options.url)
   // PDF 跳转到你的预览页
   if (fileType.value === 'PDF') {
-    fileUrl.value = `${Host80}${webBase}pdf/index.html?pdf=${encodeURIComponent(options.url)}`
+    fileUrl.value = `${Host80}${webBase}iframe/pdf.html?pdf=${encodeURIComponent(options.url)}`
   } else {
     fileUrl.value = options.url || ''
   }

+ 36 - 2
pages/customer/components/PaymentMethodsList.vue

@@ -8,6 +8,11 @@
       <view class="content">
         <view class="header">
           <text class="title">{{ item.name }}</text>
+          <view v-if="item.payTypeTags && item.payTypeTags.length > 0" class="pay-box">
+            <view v-for="(icon, index) in item.payTypeTags" :key="index" class="pay-icon-container">
+              <image class="pay-icon" :src="imgUrl + icon" mode="widthFix" />
+            </view>
+          </view>
         </view>
         <view class="info-list">
           <view class="info-item">
@@ -18,7 +23,7 @@
             <text class="info-label" v-t="'Label.Fee'" />
             <text class="info-value" v-if="item.feeType == 1">{{ item.free != null ? item.free + '%' : '-' }}</text>
             <text class="info-value" v-else-if="item.feeType == 2">${{ item.feeAmount != null ? item.feeAmount : '0'
-            }}</text>
+              }}</text>
             <text class="info-value" v-else>{{ item.free != null ? item.free + '%' : '-' }}</text>
           </view>
           <view class="info-item">
@@ -34,7 +39,7 @@
 <script setup>
 import { defineProps, defineEmits } from 'vue'
 import Config from '@/config/index'
-const { Code, Host05 } = Config
+const { Code, Host05, Host80 } = Config
 const imgUrl = Host05
 // 定义 props,接收支付方式列表
 const props = defineProps({
@@ -111,12 +116,41 @@ const handleClick = (item) => {
 
     .header {
       margin-bottom: px2rpx(12);
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
 
       .title {
         font-size: px2rpx(16);
         font-weight: 700;
         color: var(--color-navy-900, #1e2a3a);
         word-break: break-all;
+        flex: 1;
+        margin-right: px2rpx(12);
+      }
+
+      .pay-box {
+        display: flex;
+        align-items: center;
+        gap: px2rpx(8);
+        flex-shrink: 0;
+      }
+
+      .pay-icon-container {
+        width: px2rpx(32);
+        height: px2rpx(32);
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        background-color: var(--color-zinc-50, #f9f9f9);
+        border-radius: px2rpx(6);
+        border: 1px solid var(--color-zinc-100, #f3f4f6);
+      }
+
+      .pay-icon {
+        width: px2rpx(20);
+        height: px2rpx(20);
+        object-fit: contain;
       }
     }
 

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
static/icons/chat.svg


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 4 - 0
static/svg-icons-lib.js


+ 23 - 23
utils/liveChat.js

@@ -1,25 +1,21 @@
 export class LiveChatService {
   static instance = null;
   initialized = false;
-
   constructor() {
     this.initLiveChat();
   }
-
   static getInstance() {
     if (!LiveChatService.instance) {
       LiveChatService.instance = new LiveChatService();
     }
     return LiveChatService.instance;
   }
-
   initLiveChat() {
     if (this.initialized) return;
-
     // #ifdef H5
     window.__lc = window.__lc || {};
-    window.__lc.license = 18945964;
-
+    window.__lc.license = 18945964; // 你的LiveChat商户ID(必须保留)
+    // #endif
     (function (n, t, c) {
       function i(n) {
         return e._h ? e._h.apply(null, n) : e._q.push(n);
@@ -53,41 +49,45 @@ export class LiveChatService {
           t.head.appendChild(n);
         },
       };
+      // #ifdef H5
       !n.__lc.asyncInit && e.init();
       n.LiveChatWidget = n.LiveChatWidget || e;
+      n.LiveChatWidget.call('hide');
+      LiveChatWidget.on('visibility_changed', (data) => {
+        switch (data.visibility) {
+          case 'minimized':
+            n.LiveChatWidget.call('hide');
+            break
+        }
+      })
+      // #endif
     })(window, document, [].slice);
-    // #endif
-
     this.initialized = true;
   }
-
+  showChat() {
+    // #ifdef H5
+    if (window.LiveChatWidget) {
+      window.LiveChatWidget.call('maximize');
+    }
+    // #endif
+  }
   setLanguage(lang) {
     // #ifdef H5
     if (window.LiveChatWidget) {
-      if (lang === 'cn') {
-        window.LiveChatWidget.call('set_language', 'zh_CN');
-      } else {
-        window.LiveChatWidget.call('set_language', 'en');
-      }
+      window.LiveChatWidget.call('set_language', lang === 'cn' ? 'zh_CN' : 'en');
     }
     // #endif
   }
-
   adjustPosition(footerHeight) {
     // #ifdef H5
     if (window.innerWidth > 768) {
       const launcher = document.getElementById('chat-widget-launcher');
       const widget = document.getElementById('chat-widget-container');
-
-      if (launcher) {
-        launcher.style.bottom = `${footerHeight}px`;
-      }
-      if (widget) {
-        widget.style.bottom = `${footerHeight}px`;
-      }
+      if (launcher) launcher.style.bottom = `${footerHeight}px`;
+      if (widget) widget.style.bottom = `${footerHeight}px`;
     }
     // #endif
   }
 }
 
-export default LiveChatService.getInstance(); 
+export default LiveChatService.getInstance();

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است