Browse Source

Merge branch 'master' of https://git.dev.advichcloud.com/feixiang/xinkeaboard

gaosheng 2 days ago
parent
commit
8a46f7b3cf

+ 2 - 0
xinkeaboard-promotion-portal/components.d.ts

@@ -8,6 +8,7 @@ export {}
 /* prettier-ignore */
 declare module 'vue' {
   export interface GlobalComponents {
+    CompetitorWebsite: typeof import('./src/components/CompetitorWebsite.vue')['default']
     ConfirmInfoDialog: typeof import('./src/components/ConfirmInfoDialog.vue')['default']
     CountrySelct: typeof import('./src/components/CountrySelct.vue')['default']
     ElButton: typeof import('element-plus/es')['ElButton']
@@ -17,6 +18,7 @@ declare module 'vue' {
     ElIcon: typeof import('element-plus/es')['ElIcon']
     ElInput: typeof import('element-plus/es')['ElInput']
     Home: typeof import('./src/components/Home.vue')['default']
+    ProductDescription: typeof import('./src/components/ProductDescription.vue')['default']
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']
     TopContent: typeof import('./src/components/TopContent.vue')['default']

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

@@ -23,6 +23,7 @@
     "eslint-config-prettier": "^8.0.0",
     "eslint-plugin-prettier": "^4.0.0",
     "eslint-plugin-vue": "^9.0.0",
+    "postcss-px-to-viewport-8-plugin": "^1.2.5",
     "prettier": "^2.8.0",
     "sass": "^1.90.0",
     "typescript": "^5.0.0",

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

@@ -48,6 +48,9 @@ importers:
       eslint-plugin-vue:
         specifier: ^9.0.0
         version: 9.33.0(eslint@8.57.1)
+      postcss-px-to-viewport-8-plugin:
+        specifier: ^1.2.5
+        version: 1.2.5
       prettier:
         specifier: ^2.8.0
         version: 2.8.8
@@ -1036,6 +1039,10 @@ packages:
   nth-check@2.1.1:
     resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
 
+  object-assign@4.1.1:
+    resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
+    engines: {node: '>=0.10.0'}
+
   once@1.4.0:
     resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
 
@@ -1100,6 +1107,9 @@ packages:
   pkg-types@2.2.0:
     resolution: {integrity: sha512-2SM/GZGAEkPp3KWORxQZns4M+WSeXbC2HEvmOIJe3Cmiv6ieAJvdVhDldtHqM5J1Y7MrR1XhkBT/rMlhh9FdqQ==}
 
+  postcss-px-to-viewport-8-plugin@1.2.5:
+    resolution: {integrity: sha512-+yc69+q/euV7iKh5fGXY6C/lpepmVx2DGFHeYj5BpzIFyBBpdACDjZyrZ8AV0kCg+J0bplBv4ZA1QTzgaK0rGg==}
+
   postcss-selector-parser@6.1.2:
     resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==}
     engines: {node: '>=4'}
@@ -2332,6 +2342,8 @@ snapshots:
     dependencies:
       boolbase: 1.0.0
 
+  object-assign@4.1.1: {}
+
   once@1.4.0:
     dependencies:
       wrappy: 1.0.2
@@ -2395,6 +2407,10 @@ snapshots:
       exsolve: 1.0.7
       pathe: 2.0.3
 
+  postcss-px-to-viewport-8-plugin@1.2.5:
+    dependencies:
+      object-assign: 4.1.1
+
   postcss-selector-parser@6.1.2:
     dependencies:
       cssesc: 3.0.0

+ 16 - 0
xinkeaboard-promotion-portal/postcss.config.cjs

@@ -0,0 +1,16 @@
+module.exports = {
+  plugins: [
+    require('postcss-px-to-viewport-8-plugin')({
+      unitToConvert: 'px',
+      viewportWidth: 1920,    // 你的设计稿宽度
+      unitPrecision: 6,
+      propList: ['*'],
+      viewportUnit: 'vw',
+      fontViewportUnit: 'vw',
+      selectorBlackList: [], 
+      minPixelValue: 1,
+      mediaQuery: true,
+      exclude: [/node_modules/],
+    })
+  ]
+}

BIN
xinkeaboard-promotion-portal/src/assets/images/Search.png


+ 3 - 0
xinkeaboard-promotion-portal/src/assets/styles/theme.scss

@@ -0,0 +1,3 @@
+:root {
+    --promotion--color-primary: #036eb8;
+}

+ 21 - 0
xinkeaboard-promotion-portal/src/components/CompetitorWebsite.vue

@@ -0,0 +1,21 @@
+<template>
+  <div class="product-des">
+    <el-input
+      v-model="website"
+      clearable
+      placeholder="第三步:竞品的网站,如果有多个用逗号隔开"
+    />
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { ref } from 'vue';
+
+const website = ref<string>('');
+
+defineExpose({
+  getWebsite: () => website.value
+});
+</script>
+
+<style lang="scss" scoped></style>

+ 60 - 29
xinkeaboard-promotion-portal/src/components/CountrySelct.vue

@@ -2,7 +2,7 @@
   <div class="country-select">
     <el-input
       v-model="productName"
-      style="width: 240px"
+      clearable
       placeholder="第一步:输入你的产品名称,选择目标区域市场"
     />
     <span class="country-wrap">
@@ -13,16 +13,16 @@
         <template #dropdown>
           <div class="custom-dropdown">
             <div class="custom-dropdown-filter">
-              <el-input v-model="search" placeholder="Select Country">
+              <el-input v-model="search" clearable placeholder="Select Country">
                 <template #prefix>
-                  <el-icon class="el-input__icon"><search /></el-icon>
+                  <img :src="SearchIcon" />
                 </template>
               </el-input>
             </div>
             <div class="custom-dropdown-list">
               <el-dropdown-menu>
                 <el-dropdown-item
-                  v-for="item in countryList"
+                  v-for="item in renderContryList"
                   :key="item.code"
                   @click="() => selectCountry(item)"
                 >
@@ -43,8 +43,9 @@
 </template>
 
 <script lang="ts" setup>
-import { reactive, ref } from 'vue';
+import { ComputedRef, ref, computed, onMounted } from 'vue';
 import countryData from '../assets/country.json';
+import SearchIcon from '../assets/images/Search.png';
 
 import type { DropdownInstance } from 'element-plus';
 
@@ -55,7 +56,6 @@ interface countryItem {
 }
 
 const images = import.meta.glob('@/assets/flags/4x3/*.svg', { eager: true, import: 'default' });
-const step = ref<number>(1);
 const productName = ref<string>('');
 const search = ref<string>('');
 const countryList: countryItem[] = countryData.map((item) => ({
@@ -63,17 +63,25 @@ const countryList: countryItem[] = countryData.map((item) => ({
   name: item.name,
   flag: images[`/src/assets/flags/4x3/${item.code}.svg`]
 }));
-const countryInfo = ref<countryItem>(countryList[0]);
+const renderContryList: ComputedRef<countryItem[]> = computed(() => {
+  return countryList.filter((item) => item.name.includes(search.value));
+});
+const countryInfo = ref<countryItem>(JSON.parse(JSON.stringify(countryList[0])));
 const dropdown = ref<DropdownInstance>();
 
 const selectCountry = (item: countryItem) => {
   countryInfo.value = item;
 };
 
-function showCountryList() {
+const showCountryList = () => {
   if (!dropdown.value) return;
   dropdown.value.handleOpen();
-}
+};
+
+defineExpose({
+  getLocationName: () => countryInfo.value.name,
+  getProductName: () => productName.value
+});
 </script>
 
 <style lang="scss">
@@ -81,17 +89,36 @@ function showCountryList() {
   .custom-dropdown {
     display: flex;
     flex-direction: column;
-    width: 240px;
-    height: 400px;
-    padding: 5px;
+    width: 280px;
+    height: 398px;
+    padding-left: 21px;
     box-sizing: border-box;
     overflow: hidden;
 
     &-filter {
       height: 48px;
 
+      .el-input__prefix {
+        img {
+          width: 16px;
+          height: 16px;
+        }
+      }
+
+      .el-input__inner {
+        font-size: 16px;
+        color: #282e30;
+        &::placeholder {
+          font-weight: 400;
+          font-size: 16px;
+          color: rgba(40, 46, 48, 0.6);
+        }
+      }
+
       .el-input__wrapper {
-        height: 45px;
+        width: 100%;
+        height: 100%;
+        box-shadow: none;
       }
     }
 
@@ -100,23 +127,30 @@ function showCountryList() {
       overflow: auto;
       height: 100%;
 
+      .el-dropdown-menu {
+        padding-top: 0 !important;
+      }
+
       .el-dropdown-menu__item {
         padding: 10px;
         font-weight: 400;
 
         .country-name {
+          font-size: 16px;
           color: #282e30;
           text-overflow: ellipsis;
           overflow: hidden;
           white-space: nowrap;
 
           &.active {
-            color: #036eb8;
+            color: var(--promotion--color-primary);
           }
         }
         img {
           width: 30px;
-          margin: 0 6px;
+          height: 24px;
+          margin-right: 7px;
+          // margin: 0 6px;
         }
       }
     }
@@ -127,31 +161,28 @@ function showCountryList() {
 .country-select {
   display: flex;
   align-items: center;
-  :deep(.el-input) {
-    width: 25rem !important;
-    height: 3rem !important;
-  }
-
-  :deep(.el-input__inner) {
-    &::placeholder {
-      font-size: 1rem !important;
-    }
-  }
 }
 
 .country-wrap {
+  width: 92px;
+  height: 68px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  background-color: #fff;
+  margin-left: 10px;
+
   &-item {
     display: inline-flex;
     justify-content: center;
     align-items: center;
-    width: 5.75rem;
-    height: 4.25rem;
+    width: 72px;
+    height: 48px;
     background-color: #ffffff;
-    margin-left: 0.625rem;
     cursor: pointer;
 
     img {
-      width: 4.5rem;
+      // width: 4.5rem;
     }
   }
 }

+ 21 - 0
xinkeaboard-promotion-portal/src/components/ProductDescription.vue

@@ -0,0 +1,21 @@
+<template>
+  <div class="product-des">
+    <el-input
+      v-model="description"
+      clearable
+      placeholder="第二步:输入产品的描述,例如功能和使用场景"
+    />
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { ref } from 'vue';
+
+const description = ref<string>('');
+
+defineExpose({
+  getDescription: () => description.value
+});
+</script>
+
+<style lang="scss" scoped></style>

+ 112 - 17
xinkeaboard-promotion-portal/src/components/TopContent.vue

@@ -1,8 +1,10 @@
 <template>
   <div class="head-content">
-    <img :src="Logo" />
+    <div class="head-content-logo">
+      <img :src="Logo" />
+    </div>
     <div class="head-content-wrap">
-      <h2>市场迷雾?罗经开路!</h2>
+      <span class="head-content-wrap__label">市场迷雾?罗经开路!</span>
       <span class="head-content-wrap__title">
         Lookeen 是一个服务于首次出海客户的 AI 商情系统(GTM Analytics
         Tools 市场进入分析工具)。目的是提供客户自己的产品品类在目标市场的商业情报,降低用户对产品在目标市场的不确定性。
@@ -11,57 +13,150 @@
         <span class="label">只需三步</span>
         <span class="tip">(每月可免费获取3次商品分析)</span>
       </div>
-      <div class="head-content-wrap__select">
-        <CountrySelct></CountrySelct>
+      <div class="head-content-wrap__info">
+        <CountrySelct ref="CountrySelctRef" v-show="currentStep === 1"></CountrySelct>
+        <ProductDescription
+          ref="ProductDescriptionRef"
+          v-show="currentStep === 2"
+        ></ProductDescription>
+        <CompetitorWebsite ref="CompetitorWebsiteRef" v-show="currentStep === 3"></CompetitorWebsite>
+        <el-button v-if="currentStep !== 1" @click="prevStep">上一步</el-button>
+        <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 CountrySelct from '@/components/CountrySelct.vue';
+import ProductDescription from '@/components/ProductDescription.vue';
+import CompetitorWebsite from '@/components/CompetitorWebsite.vue';
 import Logo from '@/assets/images/logo.svg';
+
+const currentStep = ref<number>(1);
+const CountrySelctRef = ref();
+const ProductDescriptionRef = ref();
+const CompetitorWebsiteRef = ref();
+
+const nextStep = () => {
+  currentStep.value++;
+};
+
+const prevStep = () => {
+  currentStep.value--;
+};
+
+const acceptRecod = () => {
+  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, '-=-=-=-=')
+};
+
+
 </script>
 
 <style lang="scss" scoped>
 .head-content {
+  width: 100%;
+  height: 100%;
+  position: relative;
+
+  &-logo {
+    position: absolute;
+    width: 194px;
+    height: 88px;
+    left: 260px;
+    top: 32px;
+
+    img {
+      width: 100%;
+      height: 100%;
+      object-fit: cover;
+    }
+  }
+
   &-wrap {
-    margin-top: 1rem;
-    h2 {
-      margin: 0;
+    position: absolute;
+    display: flex;
+    flex-direction: column;
+    width: 663px;
+    height: 548px;
+    left: 280px;
+    top: 125px;
+
+    &__label {
       font-weight: bold;
-      font-size: 2.5rem;
+      font-size: 40px;
       color: #282e30;
+      margin-top: 60px;
     }
-    padding-left: 1.25rem;
 
     &__title {
       display: inline-block;
+      width: 100%;
+      height: 66px;
       font-weight: 400;
-      font-size: 1rem;
+      font-size: 16px;
       color: #282e30;
-      margin-top: 1.25rem;
+      line-height: 22px;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      margin-top: 20px;
     }
 
     &__step {
-      margin-top: 3.75rem;
+      margin-top: 60px;
 
       .label {
         font-weight: bold;
-        font-size: 1.5rem;
-        color: #036eb8;
+        font-size: 24px;
+        color: var(--promotion--color-primary);
       }
 
       .tip {
         font-weight: 400;
-        font-size: 0.75rem;
+        font-size: 12px;
         color: #282e30;
-        margin-left: 0.625rem;
+        margin-left: 10px;
       }
     }
 
-    &__select {
+    &__info {
+      display: flex;
+      align-items: center;
+      height: 68px;
       margin-top: 10px;
+
+      :deep(.el-button) {
+        width: 110px;
+        height: 48px;
+        background-color: var(--promotion--color-primary);
+        font-weight: 400;
+        font-size: 16px;
+        color: #ffffff;
+        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);
+        }
+      }
     }
   }
 }

+ 1 - 0
xinkeaboard-promotion-portal/src/main.ts

@@ -1,6 +1,7 @@
 import { createApp } from 'vue'
 import ElementPlus from 'element-plus'
 import 'element-plus/dist/index.css'
+import './assets/styles/theme.scss'
 import App from './App.vue'
 import router from './router'
 import { createPinia } from 'pinia'

+ 12 - 0
xinkeaboard-promotion-portal/src/types/index.ts

@@ -0,0 +1,12 @@
+export interface CommonReqResponseData<T> {
+  data: T;
+  logMsg: any;
+  msg: 'string';
+  state: number;
+  timestamp: number;
+}
+
+export interface AnalysisKeywordReq {
+  productName: string;
+  locationName: string;
+}

+ 10 - 0
xinkeaboard-promotion-portal/src/utils/api.ts

@@ -0,0 +1,10 @@
+import { request } from "@/utils/http";
+
+interface AnalysisKeyword {
+    productName: string;
+    locationName: string;
+}
+
+
+// 关键词
+export const analysisKeyword = (payload: AnalysisKeyword) => request.post('/analysis/keyword', payload)

+ 1 - 1
xinkeaboard-promotion-portal/src/utils/http.ts

@@ -102,7 +102,7 @@ class AxiosHttp {
 }
 
 export const request = new AxiosHttp(
-  { baseURL: '/', timeout: 5000 },
+  { baseURL: '/api', timeout: 5000 },
   {
     requestInterceptors: [
       (config) => {

+ 40 - 43
xinkeaboard-promotion-portal/src/views/Home.vue

@@ -3,7 +3,7 @@
     <div
       class="promotion-portal-top"
       ref="topRef"
-      :style="{ height: topHeight + 'px', backgroundImage: `url(${AiBgImage})` }"
+      :style="{ backgroundImage: `url(${AiBgImage})` }"
     >
       <div class="promotion-portal-top__wrapper">
         <TopContent></TopContent>
@@ -12,12 +12,12 @@
     <div
       class="promotion-portal-tab record"
       ref="tab1Ref"
-      :style="{ height: tab1Height + 'px', backgroundImage: `url(${Tab1Image})` }"
+      :style="{ backgroundImage: `url(${Tab1Image})` }"
     ></div>
     <div
       class="promotion-portal-tab cando"
       ref="tab2Ref"
-      :style="{ height: tab2Height + 'px', backgroundImage: `url(${Tab2Image})` }"
+      :style="{ backgroundImage: `url(${Tab2Image})` }"
     ></div>
   </div>
 </template>
@@ -29,54 +29,55 @@ import AiBgImage from '@/assets/images/ai-bg.png';
 import Tab1Image from '@/assets/images/record.png';
 import Tab2Image from '@/assets/images/cando.png';
 
-const topRef = ref<HTMLElement | null>(null);
-const tab1Ref = ref<HTMLElement | null>(null);
-const tab2Ref = ref<HTMLElement | null>(null);
+// const topRef = ref<HTMLElement | null>(null);
+// const tab1Ref = ref<HTMLElement | null>(null);
+// const tab2Ref = ref<HTMLElement | null>(null);
 
-const topHeight = ref(0);
-const tab1Height = ref(0);
-const tab2Height = ref(0);
+// const topHeight = ref(0);
+// const tab1Height = ref(0);
+// const tab2Height = ref(0);
 
-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 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';
-}
+// function setRem() {
+//   const baseSize = 16; // 基准字体大小
+//   const designWidth = 1920; // 设计稿宽度
+//   const scale = window.innerWidth / designWidth;
+//   document.documentElement.style.fontSize = baseSize * scale + 'px';
+// }
 
-setRem();
+// setRem();
 
-function updateAllHeights() {
-  setHeight(topRef.value, AiBgImage, topHeight);
-  setHeight(tab1Ref.value, Tab1Image, tab1Height);
-  setHeight(tab2Ref.value, Tab2Image, tab2Height);
-}
+// 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);
+  // updateAllHeights();
+  // window.addEventListener('resize', updateAllHeights);
+  // window.addEventListener('resize', setRem);
 });
 
 onUnmounted(() => {
-  window.removeEventListener('resize', updateAllHeights);
-  window.addEventListener('resize', setRem);
+  // window.removeEventListener('resize', updateAllHeights);
+  // window.addEventListener('resize', setRem);
 });
 </script>
 
 <style lang="scss" scoped>
 .promotion-portal {
-  width: 100%;
+  width: 1920px;
+  height: 2250px;
   overflow: auto;
 
   &-top,
@@ -89,14 +90,10 @@ onUnmounted(() => {
   }
 
   &-top {
-    position: relative;
+    height: 750px;
     &__wrapper {
-      position: absolute;
-      top: 2rem;
-      left: 16.25rem;
-      width: 41rem;
-      height: 34rem;
-      overflow: auto;
+      width: 100%;
+      height: 100%;
     }
   }
 

+ 12 - 4
xinkeaboard-promotion-portal/vite.config.ts

@@ -20,12 +20,20 @@ export default defineConfig({
       '@': path.resolve(__dirname, 'src')
     }
   },
+  css: {
+    preprocessorOptions: {
+      scss: {
+        // Resolve the legacy JS API is deprecated and will be removed in Dart Sass 2.0.0 warning
+        silenceDeprecations: ['legacy-js-api']
+      }
+    }
+  },
   server: {
     proxy: {
-      '/api': {
-        target: '',
-        changeOrigin: true
-        // rewrite: () =>
+      '/api/': {
+        target: 'http://54.46.9.88:8001/',
+        changeOrigin: true,
+        rewrite: (path) => path.replace(/^\/api"/, '')
       }
     }
   }