Browse Source

feat: 引入用户导览库

zhouyuhao 5 months ago
parent
commit
62252082db

+ 1 - 0
package.json

@@ -27,6 +27,7 @@
     "crypto-js": "^4.2.0",
     "crypto-js": "^4.2.0",
     "dayjs": "^1.11.13",
     "dayjs": "^1.11.13",
     "decimal.js": "^10.4.3",
     "decimal.js": "^10.4.3",
+    "driver.js": "^1.3.6",
     "echarts": "^5.5.1",
     "echarts": "^5.5.1",
     "element-plus": "^2.8.1",
     "element-plus": "^2.8.1",
     "exceljs": "^4.4.0",
     "exceljs": "^4.4.0",

+ 3 - 1
src/main.ts

@@ -14,6 +14,8 @@ import VXETablePluginExportXLSX from 'vxe-table-plugin-export-xlsx'
 import ExcelJS from 'exceljs'
 import ExcelJS from 'exceljs'
 import { VLoading } from './directive/VLoading'
 import { VLoading } from './directive/VLoading'
 
 
+import 'driver.js/dist/driver.css' // 导入样式
+
 import { createApp } from 'vue'
 import { createApp } from 'vue'
 import { createPinia } from 'pinia'
 import { createPinia } from 'pinia'
 
 
@@ -87,4 +89,4 @@ app.config.globalProperties.$toggleDarkMode = () => {
   }
   }
 }
 }
 
 
-app.mount('#app')
+app.mount('#app')

+ 71 - 0
src/styles/driver.scss

@@ -0,0 +1,71 @@
+div.driver-popover {
+  width: 400px;
+  max-width: 400px;
+  padding: 15px;
+  padding-bottom: 8px;
+  background-color: var(--color-tour-popover-bg);
+}
+// 标题
+header.driver-popover-title {
+  color: var(--color-neutral-1);
+}
+
+// 角标
+div.driver-popover-arrow {
+  border-bottom-color: var(--color-tour-popover-bg);
+}
+
+// 内容
+div.driver-popover-description {
+  color: var(--color-neutral-1);
+  text-align: left;
+}
+
+// 页数
+span.driver-popover-progress-text {
+  color: var(--color-tour-step-color);
+  font-size: 12px;
+}
+.driver-popover-footer {
+  .driver-popover-navigation-btns {
+    // 上一步
+    button.driver-popover-prev-btn {
+      width: 96px;
+      height: 32px;
+      background-color: var(--color-btn-default-bg-color);
+      color: var(--color-neutral-1);
+      fill: var(--color-neutral-1);
+      text-shadow: none;
+      border: 1px solid var(--color-tour-prev-btn-border);
+      margin-left: 8px !important;
+      border-radius: 6px;
+      text-align: center;
+      &:hover {
+        // border: 1px solid var(--color-btn-default-bg-hover);
+        background-color: var(--color-btn-default-bg-hover);
+        fill: var(--color-theme);
+        span {
+          color: var(--color-theme);
+        }
+      }
+    }
+    button.driver-popover-next-btn {
+      width: 96px;
+      height: 32px;
+      background-color: var(--color-tour-next-btn-bg);
+      fill: var(--color-tour-next-btn-color);
+      border: none;
+      text-shadow: none;
+      color: var(--color-tour-next-btn-color);
+      border-radius: 6px;
+      text-align: center;
+      &:hover {
+        background-color: var(--color-tour-next-btn-hover-bg);
+        fill: var(--color-tour-next-btn-bg);
+        span {
+          color: var(--color-btn-default-dark-hover) !important;
+        }
+      }
+    }
+  }
+}

+ 1 - 0
src/styles/index.scss

@@ -4,6 +4,7 @@
 @import './theme.scss';
 @import './theme.scss';
 @import './Antdui.scss';
 @import './Antdui.scss';
 @import './icons/iconfont.css';
 @import './icons/iconfont.css';
+@import './driver.scss';
 
 
 #app {
 #app {
   font-size: var(--font-size-3);
   font-size: var(--font-size-3);

+ 14 - 0
src/styles/theme.scss

@@ -250,6 +250,12 @@
   --color-upload-file-bg: #fef8f2;
   --color-upload-file-bg: #fef8f2;
   --color-upload-file-color: #b5b9bf;
   --color-upload-file-color: #b5b9bf;
   --color-upload-file-border-bg: #f5b279;
   --color-upload-file-border-bg: #f5b279;
+
+  --color-tour-popover-bg: #fff;
+  --color-tour-prev-btn-border: #eaebed;
+  --color-tour-next-btn-bg: #2b2f36;
+  --color-tour-next-btn-color: #fff;
+  --color-tour-step-color: #b5b9bf;
 }
 }
 
 
 :root.dark {
 :root.dark {
@@ -259,6 +265,7 @@
   --color-neutral-1: #f0f1f3;
   --color-neutral-1: #f0f1f3;
   --color-neutral-2: rgba(240, 241, 243, 0.7);
   --color-neutral-2: rgba(240, 241, 243, 0.7);
   --color-neutral-3: rgba(240, 241, 243, 0.3);
   --color-neutral-3: rgba(240, 241, 243, 0.3);
+
   --color-border: #3f434a;
   --color-border: #3f434a;
   --color-header-bg: #30353c;
   --color-header-bg: #30353c;
 
 
@@ -386,4 +393,11 @@
   --vxe-ui-input-border-color: #656f7d;
   --vxe-ui-input-border-color: #656f7d;
   --vxe-ui-table-menu-background-color: #3e454f;
   --vxe-ui-table-menu-background-color: #3e454f;
   --color-vxe-table-visited-row-bg: #3c4149;
   --color-vxe-table-visited-row-bg: #3c4149;
+
+  --color-tour-popover-bg: #cf5f00;
+  --color-tour-prev-btn-border: #e6c5aa;
+  --color-tour-next-btn-bg: #f0f1f3;
+  --color-tour-next-btn-hover-bg: #e3e5e8;
+  --color-tour-next-btn-color: #ed6d00;
+  --color-tour-step-color: rgba(240, 241, 243, 0.7);
 }
 }

+ 57 - 0
src/utils/driverGuide.ts

@@ -0,0 +1,57 @@
+// src/composables/useDriver.ts
+import { driver, type DriveStep, type Config } from 'driver.js'
+import 'driver.js/dist/driver.css'
+
+let driverInstance: ReturnType<typeof driver> | null = null
+
+/**
+ * useDriver composable
+ * @param steps 引导步骤数组
+ * @param globalConfig 可选的 driver 配置
+ */
+export function useDriver(steps: DriveStep[], globalConfig?: Config) {
+  if (!driverInstance) {
+    driverInstance = driver({
+      ...globalConfig,
+      showButtons: ['previous', 'next', 'close'],
+      onPopoverRender: (popoverDOM, { config, state, driver }) => {
+        console.log('Popover rendered:', popoverDOM)
+        // 避免重复插入
+        if (!popoverDOM.container.querySelector('.custom-driver-close')) {
+          const closeBtn = document.createElement('span')
+          closeBtn.innerHTML = '×'
+          closeBtn.className = 'custom-driver-close'
+          closeBtn.style.cssText = `
+            position: absolute;
+            top: 8px;
+            right: 12px;
+            font-size: 18px;
+            font-weight: bold;
+            color: #999;
+            cursor: pointer;
+          `
+          closeBtn.onclick = () => {
+            driverInstance?.destroy()
+          }
+          popoverDOM.container.style.position = 'relative'
+          popoverDOM.container.appendChild(closeBtn)
+        }
+
+        // 如果用户配置了自己的 onPopoverRender,也执行它
+        globalConfig?.onPopoverRender?.(popoverDOM, { config, state, driver })
+      }
+    })
+  }
+
+  driverInstance.setSteps(steps)
+
+  return {
+    start: (stepIndex = 0) => driverInstance?.drive(stepIndex),
+    moveNext: () => driverInstance?.moveNext(),
+    movePrevious: () => driverInstance?.movePrevious(),
+    highlight: (step: DriveStep) => driverInstance?.highlight(step),
+    destroy: () => driverInstance?.destroy(),
+    isActive: () => driverInstance?.isActive(),
+    driverInstance // 暴露实例给你更多操作
+  }
+}

+ 36 - 2
src/views/Tracking/src/components/TrackingDetail/src/TrackingDetail.vue

@@ -17,6 +17,39 @@ import { formatTimezone } from '@/utils/tools'
 import { useThemeStore } from '@/stores/modules/theme'
 import { useThemeStore } from '@/stores/modules/theme'
 import ShareLinkDialog from './components/ShareLinkDialog.vue'
 import ShareLinkDialog from './components/ShareLinkDialog.vue'
 
 
+import { driver } from 'driver.js'
+
+const driverObj = driver({
+  showProgress: true,
+  progressText: 'Step {{current}} of {{total}}',
+  stagePadding: 2,
+  showButtons: ['next', 'previous'],
+  steps: [
+    {
+      element: '#step-home-1',
+      popover: {
+        title: 'Animated Tour Example',
+        description: "Here is the code example showing animated tour. Let's walk you through it.",
+        side: 'left',
+        align: 'start'
+      }
+    },
+    {
+      element: '#step-home-2',
+      popover: {
+        title: 'Import the Library',
+        description: 'It works the same in vanilla JavaScript as well as frameworks.',
+        side: 'bottom',
+        align: 'start'
+      }
+    }
+  ]
+})
+
+const test = () => {
+  driverObj.drive()
+}
+
 const route = useRoute()
 const route = useRoute()
 
 
 const themeStore = useThemeStore()
 const themeStore = useThemeStore()
@@ -136,7 +169,7 @@ const openShareDialog = () => {
         <VTag large :type="allData?.transportInfo?.status">{{
         <VTag large :type="allData?.transportInfo?.status">{{
           allData?.transportInfo?.status
           allData?.transportInfo?.status
         }}</VTag>
         }}</VTag>
-        <div class="right-operation">
+        <div class="right-operation" id="step-home-1">
           <el-button
           <el-button
             v-if="
             v-if="
               (allData?.canViewAMSLog || allData?.canViewISFLog) &&
               (allData?.canViewAMSLog || allData?.canViewISFLog) &&
@@ -148,9 +181,10 @@ const openShareDialog = () => {
             <span class="font_family icon-icon_log_b" style="margin-right: 4px"></span
             <span class="font_family icon-icon_log_b" style="margin-right: 4px"></span
             >AMS/ISF</el-button
             >AMS/ISF</el-button
           >
           >
+          <el-button @click="test">测试</el-button>
         </div>
         </div>
       </div>
       </div>
-      <div class="detail-info">
+      <div class="detail-info" id="step-home-2">
         <div class="item transport-way">
         <div class="item transport-way">
           <div class="origin">
           <div class="origin">
             <div class="title">Origin</div>
             <div class="title">Origin</div>