Browse Source

feat: 招商门户页面开发

周玉环 3 weeks ago
parent
commit
b8c4128628

+ 1 - 1
xinkeaboard-promotion-portal/auto-imports.d.ts

@@ -6,5 +6,5 @@
 // biome-ignore lint: disable
 export {}
 declare global {
-
+  const ElMessage: typeof import('element-plus/es')['ElMessage']
 }

+ 1 - 0
xinkeaboard-promotion-portal/package.json

@@ -8,6 +8,7 @@
   },
   "dependencies": {
     "axios": "^1.11.0",
+    "crypto-js": "^4.2.0",
     "element-plus": "^2.10.7",
     "pinia": "^2.0.0",
     "vue": "^3.2.0",

+ 8 - 0
xinkeaboard-promotion-portal/pnpm-lock.yaml

@@ -11,6 +11,9 @@ importers:
       axios:
         specifier: ^1.11.0
         version: 1.11.0
+      crypto-js:
+        specifier: ^4.2.0
+        version: 4.2.0
       element-plus:
         specifier: ^2.10.7
         version: 2.10.7(vue@3.5.18(typescript@5.9.2))
@@ -608,6 +611,9 @@ packages:
     resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
     engines: {node: '>= 8'}
 
+  crypto-js@4.2.0:
+    resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==}
+
   cssesc@3.0.0:
     resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
     engines: {node: '>=4'}
@@ -1879,6 +1885,8 @@ snapshots:
       shebang-command: 2.0.0
       which: 2.0.2
 
+  crypto-js@4.2.0: {}
+
   cssesc@3.0.0: {}
 
   csstype@3.1.3: {}

+ 0 - 0
xinkeaboard-promotion-portal/src/assets/images/record.png → xinkeaboard-promotion-portal/src/assets/images/record-1.png


BIN
xinkeaboard-promotion-portal/src/assets/images/record-2.png


BIN
xinkeaboard-promotion-portal/src/assets/images/record-3.png


BIN
xinkeaboard-promotion-portal/src/assets/images/record-center.png


BIN
xinkeaboard-promotion-portal/src/assets/images/record-top.png


File diff suppressed because it is too large
+ 8 - 0
xinkeaboard-promotion-portal/src/assets/images/record-top.svg


+ 18 - 1
xinkeaboard-promotion-portal/src/components/CompetitorWebsite.vue

@@ -18,4 +18,21 @@ defineExpose({
 });
 </script>
 
-<style lang="scss" scoped></style>
+<style lang="scss" scoped>
+.product-des {
+  :deep(.el-input) {
+    width: 504px !important;
+    height: 48px !important;
+  }
+
+  :deep(.el-input__inner) {
+    line-height: 48px;
+    font-size: 16px;
+    &::placeholder {
+      font-weight: 400;
+      font-size: 16px;
+      color: rgba(40, 46, 48, 0.6);
+    }
+  }
+}
+</style>

+ 15 - 0
xinkeaboard-promotion-portal/src/components/CountrySelct.vue

@@ -161,6 +161,21 @@ defineExpose({
 .country-select {
   display: flex;
   align-items: center;
+
+  :deep(.el-input) {
+    width: 412px !important;
+    height: 48px !important;
+  }
+
+  :deep(.el-input__inner) {
+    line-height: 48px;
+    font-size: 16px;
+    &::placeholder {
+      font-weight: 400;
+      font-size: 16px;
+      color: rgba(40, 46, 48, 0.6);
+    }
+  }
 }
 
 .country-wrap {

+ 18 - 1
xinkeaboard-promotion-portal/src/components/ProductDescription.vue

@@ -18,4 +18,21 @@ defineExpose({
 });
 </script>
 
-<style lang="scss" scoped></style>
+<style lang="scss" scoped>
+.product-des {
+  :deep(.el-input) {
+    width: 504px !important;
+    height: 48px !important;
+  }
+
+  :deep(.el-input__inner) {
+    line-height: 48px;
+    font-size: 16px;
+    &::placeholder {
+      font-weight: 400;
+      font-size: 16px;
+      color: rgba(40, 46, 48, 0.6);
+    }
+  }
+}
+</style>

+ 76 - 33
xinkeaboard-promotion-portal/src/components/TopContent.vue

@@ -27,13 +27,14 @@
         <el-button v-if="currentStep !== 3" @click="nextStep">下一步</el-button>
         <el-button v-if="currentStep === 3" @click="acceptRecod">获取报告</el-button>
       </div>
-      <!-- <div class="head-content-wrap"></div> -->
     </div>
   </div>
 </template>
 
 <script lang="ts" setup>
-import { ref } from 'vue';
+import { ref, computed } from 'vue';
+import { useMainStore } from '../store';
+import { useRouter } from 'vue-router';
 import CountrySelct from '@/components/CountrySelct.vue';
 import ProductDescription from '@/components/ProductDescription.vue';
 import CompetitorWebsite from '@/components/CompetitorWebsite.vue';
@@ -44,37 +45,94 @@ import {
   analysisRival,
   analysisQualitative
 } from '@/utils/api';
+import { showMessage } from '@/utils/common';
+const mainStore = useMainStore();
+const router = useRouter();
+
+const currentStep = computed(() => mainStore.getCurrentStep);
 
-const currentStep = ref<number>(1);
 const CountrySelctRef = ref();
 const ProductDescriptionRef = ref();
 const CompetitorWebsiteRef = ref();
 
+const validate = () => {
+  const { locationName, productName, description } = getFormData();
+  let message: string = '';
+  switch (currentStep.value) {
+    case 1:
+      if (!productName) {
+        message = '请输入产品名称';
+        break;
+      }
+
+      if (!locationName) {
+        message = '请选择目标区域市场';
+        break;
+      }
+      break;
+    case 2:
+      if (!description) {
+        message = '请输入产品描述';
+        break;
+      }
+      break;
+    default:
+      break;
+  }
+  return message;
+};
+
 const nextStep = () => {
-  currentStep.value++;
+  const message = validate();
+  if (message) {
+    return showMessage({
+      type: 'error',
+      message
+    });
+  }
+  mainStore.setCurrentStep(currentStep.value + 1);
 };
 
 const prevStep = () => {
-  currentStep.value--;
+  mainStore.setCurrentStep(currentStep.value - 1);
 };
 
-const acceptRecod = () => {
+const getFormData = () => {
   const locationName = CountrySelctRef.value.getLocationName();
   const productName = CountrySelctRef.value.getProductName();
   const description = ProductDescriptionRef.value.getDescription();
   const competitorWebsite = CompetitorWebsiteRef.value.getWebsite();
-  console.log(locationName, productName, description, competitorWebsite, '-=-=-=-=');
-  Promise.all([
-    analysisKeyword({ productName: '自行车', locationName: 'United States' }),
-    analysisSuggestions({ productName: '自行车', locationName: 'United States' }),
-    analysisRival({
-      competitorWebsite: 'https://www.popmart.com,https://us.louisvuitton.com/eng-us/homepage',
-      locationName: 'United States'
-    }),
-    analysisQualitative('自行车')
-  ]).then((res) => {
-    console.log(res, '=========');
-  });
+
+  return {
+    locationName,
+    productName,
+    description,
+    competitorWebsite
+  };
+};
+
+const acceptRecod = () => {
+  const { locationName, productName, description, competitorWebsite } = getFormData();
+  if (!competitorWebsite) {
+    return showMessage({
+      type: 'error',
+      message: '请输入竞品网站'
+    });
+  }
+  router.push('/record');
+
+  // console.log(locationName, productName, description, competitorWebsite, '-=-=-=-=');
+  // Promise.all([
+  //   analysisKeyword({ productName: '自行车', locationName: 'United States' }),
+  //   analysisSuggestions({ productName: '自行车', locationName: 'United States' }),
+  //   analysisRival({
+  //     competitorWebsite: 'https://www.popmart.com,https://us.louisvuitton.com/eng-us/homepage',
+  //     locationName: 'United States'
+  //   }),
+  //   analysisQualitative('自行车')
+  // ]).then((res) => {
+  //   console.log(res, '=========');
+  // });
 };
 </script>
 
@@ -160,21 +218,6 @@ const acceptRecod = () => {
         border-radius: 0;
         margin-left: 10px;
       }
-
-      :deep(.el-input) {
-        width: 412px !important;
-        height: 48px !important;
-      }
-
-      :deep(.el-input__inner) {
-        line-height: 48px;
-        font-size: 16px;
-        &::placeholder {
-          font-weight: 400;
-          font-size: 16px;
-          color: rgba(40, 46, 48, 0.6);
-        }
-      }
     }
   }
 }

+ 5 - 0
xinkeaboard-promotion-portal/src/router/index.ts

@@ -6,6 +6,11 @@ const routes = [
     name: 'Home',
     component: () => import('../views/Home.vue'),
   },
+  {
+    path: '/record',
+    name: 'Record',
+    component: () => import('../views/Record.vue'),
+  },
 ]
 
 const router = createRouter({

+ 11 - 6
xinkeaboard-promotion-portal/src/store/index.ts

@@ -1,12 +1,17 @@
-import { defineStore } from 'pinia'
+import { defineStore } from 'pinia';
 
 export const useMainStore = defineStore('main', {
   state: () => ({
-    count: 0,
+    currentStep: 1
   }),
   actions: {
-    increment() {
-      this.count++
-    },
+    setCurrentStep(val: number) {
+      this.currentStep = val;
+    }
   },
-})
+  getters: {
+    getCurrentStep(): number {
+      return this.currentStep;
+    }
+  }
+});

+ 0 - 30
xinkeaboard-promotion-portal/src/utils/api2.ts

@@ -1,30 +0,0 @@
-import { request } from '@/utils/http1';
-
-interface AnalysisKeyword {
-  productName: string;
-  locationName: string;
-}
-
-interface AnalysisRival {
-  competitorWebsite: string;
-  locationName: string;
-}
-
-interface AnalysisQualitative {
-    keyword: string;
-}
-
-// 关键词
-export const analysisKeyword = (payload: AnalysisKeyword) =>
-  request.post('/analysis/keyword', payload);
-
-//推荐
-export const analysisSuggestions = (payload: AnalysisKeyword) =>
-  request.post('/analysis/suggestions', payload);
-
-// 竞品
-export const analysisRival = (payload: AnalysisRival) => request.post('/analysis/rival', payload);
-
-
-// 定性分析接口
-export const analysisQualitative = (payload: AnalysisQualitative) => request.get('/analysis/qualitative', payload);

+ 19 - 0
xinkeaboard-promotion-portal/src/utils/common.ts

@@ -0,0 +1,19 @@
+import { ElMessage } from 'element-plus';
+
+let msgBoxInstance: any = null;
+/**
+ * [showMessage 统一封装消息]
+ *
+ * @return  {[params]}  [return description]
+ */
+export const showMessage = (params: {
+  type: string;
+  message: string;
+  icon?: string;
+  duration?: number;
+}) => {
+  if (msgBoxInstance) {
+    msgBoxInstance.close();
+  }
+  msgBoxInstance = ElMessage(params as any);
+};

+ 28 - 0
xinkeaboard-promotion-portal/src/utils/hmac.ts

@@ -0,0 +1,28 @@
+import CryptoJS from 'crypto-js';
+
+const DEFAULT_KEY = 'SK'; // 加密密钥(口令,可以自己换)
+
+/**
+ * 加密
+ */
+export function encrypt(text: string, key: string = DEFAULT_KEY): string {
+  return CryptoJS.AES.encrypt(text, key).toString();
+}
+
+/**
+ * 解密
+ */
+export function decrypt(cipherText: string, key: string = DEFAULT_KEY): string {
+  const bytes = CryptoJS.AES.decrypt(cipherText, key);
+  return bytes.toString(CryptoJS.enc.Utf8);
+}
+
+const SK = 'U2FsdGVkX1/iQRVGm2kCZOyzkiAAXjP+65RuEZEOcK+p/j+8YoWrBjMbnSyQu2mL';
+
+export function generateSign(params: { msgId: string; url: string; nonce: string }): string {
+  const { msgId, url, nonce } = params;
+  const newStr = [msgId, url, nonce].join('&');
+  return CryptoJS.HmacSHA256(newStr, decrypt(SK)).toString(CryptoJS.enc.Hex);
+}
+
+

+ 30 - 25
xinkeaboard-promotion-portal/src/utils/http.ts

@@ -1,22 +1,25 @@
-import axios from "axios";
-import { ElMessage } from "element-plus"; // 你也可以换成自己的提示组件
-import router from "@/router"; // 如果需要跳转登录页
-import type { CommonReqResponseData } from '../types/index';
+import axios from 'axios';
+import router from '@/router'; // 如果需要跳转登录页
+import { generateSign } from '@/utils/hmac';
+import { showMessage } from '@/utils/common';
 
 // 创建 axios 实例
 const http = axios.create({
-  baseURL: import.meta.env.VITE_BASE_API || "/api", // 根据环境变量配置
-  timeout: 60000, // 超时时间
+  baseURL: import.meta.env.VITE_BASE_API || '/api', // 根据环境变量配置
+  timeout: 60000 // 超时时间
 });
 
 // 请求拦截器
 http.interceptors.request.use(
   (config) => {
-    // 这里可以统一加 token
-    const token = localStorage.getItem("token");
-    if (token) {
-      config.headers.Authorization = `Bearer ${token}`;
-    }
+    const { url } = config;
+    const extendParams = {
+      msgId: '730d7bbaf1914e8ba60e9c922d58a8c8',
+      url: url as string,
+      nonce: Date.now().toString()
+    };
+    const signature = generateSign(extendParams);
+    Object.assign(config.headers, extendParams, { signature });
     return config;
   },
   (error) => {
@@ -27,38 +30,40 @@ http.interceptors.request.use(
 // 响应拦截器
 http.interceptors.response.use(
   (response: any) => {
-    console.log(response, '-=-=-')
-    const state = response.state;
+    const res = response.data;
     // 你可以根据后端约定的 code 来处理
-    if (state !== 200) {
-      ElMessage.error(response.msg || "请求出错");
+    if (res.state !== 200) {
+      showMessage({
+        type: 'error',
+        message: response.msg || '请求出错'
+      });
     }
     return response; // 正常返回数据
   },
   (error) => {
+    let message: string = '';
     if (error.response) {
       const status = error.response.status;
       switch (status) {
         case 401:
-          ElMessage.error("登录已过期,请重新登录");
-          localStorage.removeItem("token");
-          router.push("/login");
-          break;
-        case 403:
-          ElMessage.error("没有权限");
+          message = '没有权限';
           break;
         case 404:
-          ElMessage.error("接口不存在");
+          message = '接口不存在';
           break;
         case 500:
-          ElMessage.error("服务器错误");
+          message = '服务器错误';
           break;
         default:
-          ElMessage.error(error.message);
+          message = error.message;
       }
     } else {
-      ElMessage.error("网络错误");
+      message = '网络错误';
     }
+    showMessage({
+      type: 'error',
+      message
+    });
     return Promise.reject(error);
   }
 );

+ 0 - 132
xinkeaboard-promotion-portal/src/utils/http1.ts

@@ -1,132 +0,0 @@
-import axios, {
-  AxiosInstance,
-  AxiosRequestConfig,
-  AxiosResponse,
-  Canceler,
-  AxiosError
-} from 'axios';
-
-type RequestInterceptor = (
-  config: AxiosRequestConfig
-) => AxiosRequestConfig | Promise<AxiosRequestConfig>;
-type ResponseInterceptor = (response: AxiosResponse) => AxiosResponse | Promise<AxiosResponse>;
-
-interface AxiosInterceptorOptions {
-  requestInterceptors?: RequestInterceptor[];
-  responseInterceptors?: ResponseInterceptor[];
-}
-
-class AxiosHttp {
-  private axiosInstance: AxiosInstance;
-  private cancelMap = new Map<string, Canceler>();
-
-  constructor(config?: AxiosRequestConfig, options?: AxiosInterceptorOptions) {
-    this.axiosInstance = axios.create(config);
-
-    // 添加请求拦截器
-    options?.requestInterceptors?.forEach((interceptor) => {
-      this.axiosInstance.interceptors.request.use(
-        async (config: any) => {
-          config = await interceptor(config);
-          return config;
-        },
-        (error: AxiosError) => Promise.reject(error)
-      );
-    });
-
-    // 添加响应拦截器
-    options?.responseInterceptors?.forEach((interceptor) => {
-      this.axiosInstance.interceptors.response.use(
-        async (response: AxiosResponse) => {
-          response = await interceptor(response);
-          return response;
-        },
-        (error: AxiosError) => Promise.reject(error)
-      );
-    });
-
-    // 统一请求拦截 - 生成取消Token
-    this.axiosInstance.interceptors.request.use((config) => {
-      const requestKey = this.getRequestKey(config);
-      if (this.cancelMap.has(requestKey)) {
-        // 取消重复请求
-        const cancel = this.cancelMap.get(requestKey);
-        cancel?.(`取消重复请求: ${requestKey}`);
-        this.cancelMap.delete(requestKey);
-      }
-      config.cancelToken = new axios.CancelToken((cancel: Canceler) => {
-        this.cancelMap.set(requestKey, cancel);
-      });
-      return config;
-    });
-
-    // 请求完成后清除取消函数
-    this.axiosInstance.interceptors.response.use(
-      (response: AxiosResponse) => {
-        const requestKey = this.getRequestKey(response.config);
-        this.cancelMap.delete(requestKey);
-        return response;
-      },
-      (error: AxiosError) => {
-        if (axios.isCancel(error)) {
-          console.warn(error.message);
-        }
-        if (error.config) {
-          const requestKey = this.getRequestKey(error.config);
-          this.cancelMap.delete(requestKey);
-        }
-        return Promise.reject(error);
-      }
-    );
-  }
-
-  private getRequestKey(config: AxiosRequestConfig): string {
-    const url = config.url || '';
-    const method = config.method || 'get';
-    const params = config.params ? JSON.stringify(config.params) : '';
-    const data = config.data ? JSON.stringify(config.data) : '';
-    return [method, url, params, data].join('&');
-  }
-
-  request<T = any>(config: AxiosRequestConfig): Promise<T> {
-    return this.axiosInstance.request<T>(config).then((res: AxiosResponse) => res.data);
-  }
-
-  get<T = any>(url: string, params?:any, config?: AxiosRequestConfig): Promise<T> {
-    return this.request<T>({ ...config, method: 'get', url, params });
-  }
-
-  post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
-    return this.request<T>({ ...config, method: 'post', url, data });
-  }
-}
-
-export const request = new AxiosHttp(
-  { baseURL: '/api', timeout: 5000 },
-  {
-    requestInterceptors: [
-      (config) => {
-        config.headers = config.headers || {};
-        config.headers.Authorization = 'Bearer token';
-        return config;
-      }
-    ],
-    responseInterceptors: [
-      (response) => {
-        if (response.status !== 200) {
-          throw new Error('请求失败');
-        }
-        return response;
-      }
-    ]
-  }
-);
-
-// api
-//   .get('/users')
-//   .then((data) => console.log(data))
-//   .catch((err) => console.error(err.message));
-
-// // 同时重复请求会取消前一个请求
-// api.get('/users');
-// api.get('/users'); // 这会取消上一个

+ 28 - 59
xinkeaboard-promotion-portal/src/views/Home.vue

@@ -1,83 +1,50 @@
 <template>
   <div class="promotion-portal">
-    <div
-      class="promotion-portal-top"
-      ref="topRef"
-      :style="{ backgroundImage: `url(${AiBgImage})` }"
-    >
+    <div class="promotion-portal-top" :style="{ backgroundImage: `url(${AiBgImage})` }">
       <div class="promotion-portal-top__wrapper">
         <TopContent></TopContent>
       </div>
     </div>
-    <div
-      class="promotion-portal-tab record"
-      ref="tab1Ref"
-      :style="{ backgroundImage: `url(${Tab1Image})` }"
-    ></div>
-    <div
-      class="promotion-portal-tab cando"
-      ref="tab2Ref"
-      :style="{ backgroundImage: `url(${Tab2Image})` }"
-    ></div>
+    <div class="promotion-portal-tab record" :style="{ backgroundImage: `url(${Tab1})` }"></div>
+    <div class="promotion-portal-tab cando" :style="{ backgroundImage: `url(${Tab2Image})` }"></div>
   </div>
 </template>
 
 <script lang="ts" setup>
-import { ref, onMounted, onUnmounted } from 'vue';
+import { computed, onMounted, onUnmounted } from 'vue';
 import TopContent from '@/components/TopContent.vue';
+import { useMainStore } from '../store';
 import AiBgImage from '@/assets/images/ai-bg.png';
-import Tab1Image from '@/assets/images/record.png';
 import Tab2Image from '@/assets/images/cando.png';
+import Record1 from '@/assets/images/record-1.png';
+import Record2 from '@/assets/images/record-2.png';
+import Record3 from '@/assets/images/record-3.png';
 
-// const topRef = ref<HTMLElement | null>(null);
-// const tab1Ref = ref<HTMLElement | null>(null);
-// const tab2Ref = ref<HTMLElement | null>(null);
+const mainStore = useMainStore();
 
-// const topHeight = ref(0);
-// const tab1Height = ref(0);
-// const tab2Height = ref(0);
+const currentStep = computed(() => mainStore.getCurrentStep);
 
-// function setHeight(refEl: HTMLElement | null, imgUrl: string, heightRef: typeof topHeight) {
-//   if (!refEl) return;
-//   const img = new Image();
-//   img.src = imgUrl;
-//   img.onload = () => {
-//     const ratio = img.height / img.width;
-//     heightRef.value = refEl.offsetWidth * ratio;
-//   };
-// }
-
-// function setRem() {
-//   const baseSize = 16; // 基准字体大小
-//   const designWidth = 1920; // 设计稿宽度
-//   const scale = window.innerWidth / designWidth;
-//   document.documentElement.style.fontSize = baseSize * scale + 'px';
-// }
-
-// setRem();
-
-// function updateAllHeights() {
-//   setHeight(topRef.value, AiBgImage, topHeight);
-//   setHeight(tab1Ref.value, Tab1Image, tab1Height);
-//   setHeight(tab2Ref.value, Tab2Image, tab2Height);
-// }
-
-onMounted(() => {
-  // updateAllHeights();
-  // window.addEventListener('resize', updateAllHeights);
-  // window.addEventListener('resize', setRem);
+const Tab1 = computed(() => {
+  if (currentStep.value === 1) {
+    return Record1;
+  }
+  if (currentStep.value === 2) {
+    return Record2;
+  }
+  if (currentStep.value === 3) {
+    return Record3;
+  }
 });
+console.log(Tab1, 'Tab1');
+onMounted(() => {});
 
-onUnmounted(() => {
-  // window.removeEventListener('resize', updateAllHeights);
-  // window.addEventListener('resize', setRem);
-});
+onUnmounted(() => {});
 </script>
 
 <style lang="scss" scoped>
 .promotion-portal {
   width: 1920px;
-  height: 2250px;
+  height: 100%;
   overflow: auto;
 
   &-top,
@@ -98,11 +65,13 @@ onUnmounted(() => {
   }
 
   &-tab.record {
-    /* backgroundImage 由绑定 style 设置 */
+    width: 100%;
+    height: 750px;
   }
 
   &-tab.cando {
-    /* backgroundImage 由绑定 style 设置 */
+    width: 100%;
+    height: 750px;
   }
 }
 </style>

+ 59 - 0
xinkeaboard-promotion-portal/src/views/Record.vue

@@ -0,0 +1,59 @@
+<template>
+  <div class="record">
+    <div class="record-head"></div>
+    <div class="record-wrap">
+      <div class="record-wrap-content">
+        <div class="record-wrap-content__overview"></div>
+        <div class="overview-left"></div>
+        <div class="overview-right"></div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup></script>
+
+<style lang="scss" scoped>
+.record {
+  position: relative;
+  width: 1920px;
+  height: 100%;
+  overflow: auto;
+  background-color: #fff;
+
+  &-head {
+    height: 1330px;
+    background-image: url('../assets/images/record-top.svg');
+    background-repeat: no-repeat; /* 不重复 */
+    background-position: center center; /* 居中显示 */
+    background-size: cover;
+  }
+
+  &-wrap {
+    position: absolute;
+    top: 460px;
+    width: 100%;
+    display: flex;
+    justify-content: center;
+
+    &-content {
+      position: relative;
+      width: 1336px;
+      &__overview {
+        width: 100%;
+        height: 179px;
+        background: rgba(255, 255, 255, 0.65);
+        box-shadow: -9px 13px 22px 0px rgba(5, 112, 183, 0.06);
+        border-radius: 4px;
+        margin-top: 35px;
+      }
+
+      .overview-left {
+      }
+
+      .overview-right {
+      }
+    }
+  }
+}
+</style>

Some files were not shown because too many files changed in this diff