Browse Source

首页加载逻辑、搜索页、搜索框组件

cxmo 2 years ago
parent
commit
bcbbe657fc

+ 1 - 0
package.json

@@ -11,6 +11,7 @@
   "dependencies": {
     "axios": "^0.26.0",
     "element-plus": "^2.0.2",
+    "lodash": "^4.17.21",
     "moment": "^2.29.1",
     "normalize.css": "^8.0.1",
     "vue": "^3.2.25",

BIN
src/assets/icon-close.png


BIN
src/assets/icon-search.png


BIN
src/assets/report-empty.png


+ 87 - 0
src/components/Search.vue

@@ -0,0 +1,87 @@
+<script setup>
+import { computed, ref ,defineEmits} from "vue";
+
+const emit = defineEmits(["search",'clean','blur'])
+let searchData = ref("");
+let isFocus = ref(false);
+let input = ref(null);
+let showClean = computed(() => {
+  return searchData.value.length > 0 ? true : false;
+});
+const handleClick = () => {
+  //聚焦到input框上,样式改变
+  isFocus.value = true;
+  input.value.focus();
+};
+const handleBlur = () => {
+  if (!showClean.value) {
+    isFocus.value = false;
+  }
+  
+};
+const handleClean = () => {
+  searchData.value = "";
+  emit('clean')
+};
+const handleSearch = ()=>{
+    if(!searchData.value.length) return
+    emit('search',searchData)
+}
+</script>
+<template>
+  <div class="search-wrap" @click="handleClick" :class="{ focus: isFocus }">
+    <span v-show="!isFocus"></span>
+    <input
+      type="text"
+      placeholder="请输入标题/关键词"
+      v-model="searchData"
+      @blur="handleBlur"
+      @keyup.enter="handleSearch"
+      ref="input"
+    />
+    <span class="clean" v-show="showClean" @click="handleClean"></span>
+  </div>
+</template>
+
+
+
+<style lang="scss" scoped>
+.search-wrap {
+  display: flex;
+  align-items: center;
+  padding: 10px 20px;
+  position: absolute;
+  right: 160px;
+  width: 300px;
+  height: 40px;
+  top: 50%;
+  margin-top: -20px;
+  border-radius: 20px;
+  background-color: #ebebeb;
+  border: 1px solid #ebebeb;
+  transition: 0.3s;
+  &.focus {
+    background: #ffffff;
+    border: 1px solid #f3a52f;
+  }
+  span {
+    display: inline-block;
+    width: 14px;
+    height: 14px;
+    background: no-repeat center/cover url("../assets/icon-search.png");
+    margin-right: 6px;
+    &.clean {
+      width: 20px;
+      height: 20px;
+      margin-right: 0;
+      background: no-repeat center/cover url("../assets/icon-close.png");
+    }
+  }
+  input {
+    flex: 1;
+    background: none;
+    outline: none;
+    border: none;
+  }
+}
+</style>

+ 340 - 89
src/views/report/List.vue

@@ -1,27 +1,106 @@
 <script setup>
-import { computed, onMounted, ref } from "vue";
-import { varietyData, informationData, reportData } from "./staticData.js";
+import { computed, onMounted, reactive, ref } from "vue";
+import {
+  varietyData,
+  informationData,
+  reportData,
+  searchData,
+} from "./staticData.js";
 import { useRouter } from "vue-router";
+import throttle from "lodash/throttle";
+import Search from "@/components/Search.vue";
+
+/*通过ref=xxx获取的dom */
+let report = ref(null);
+let reportContainer = ref(null);
 
-let report = ref(null); //报告页面的dom
 let icon_more_path = new URL("../../assets/icon-more.png", import.meta.url)
   .href; //查看更多的icon
-let activeTab = ref("宏观经济"); //默认激活的nav
-let activeSubTab = ref("宏观"); //默认激活的subnav
+let report_empty_path = new URL("../../assets/report-empty.png",import.meta.url)
+let mounted = ref(false);
+let pageModel = ref("normal");
+
+/*nav数据 */
+let activeTab = ref("宏观经济");
+let activeSubTab = ref("宏观");
 let varietySubData = computed(() => {
-  //subnav
   return varietyData.find((i) => i.label === activeTab.value).child;
 });
-let showMore = false; //是否展示加载更多按钮
+
+const getData = () => {
+  return new Promise((resolve, _) => {
+    resolve("aaa");
+  });
+};
+const getSearchData = (keyWord) => {
+  changePageStyle("search");
+};
+
+const changePageStyle = (model) => {
+  pageModel.value = model;
+  //normal,search
+  if (model === "normal") {
+  } else {
+    report.value.style.height = "auto";
+  }
+};
+const handleSearch = (data) => {
+  console.log("data", data.value);
+  getSearchData(data.value);
+};
+
+let showMore = ref(false); //是否展示加载更多按钮
+let autoShow = ref(false); //是否展示研报主体内容
+let showData = ref([]); //有加载更多的情况下,展示的研报主体内容
+let showBackTop = ref(false); //是否显示回到顶部按钮
 const checkMore = () => {
-  //检查reportData长度是否超过el-main的clientHeight
-  //reportData-item的minHeight为(125*childNum+56)
+  //检查reportData长度是否超过el-main的clientHeight,reportData-item的minHeight为(125*childNum+56)
+  const clientHeight =
+    document.getElementsByClassName("el-main")[0].clientHeight;
+  let sumHeight = 0,
+    showIndex = 0;
+  reportData.forEach((item, index) => {
+    sumHeight += item.child.length * 125 + 56;
+    if (sumHeight >= clientHeight) {
+      showIndex = index;
+      showMore.value = true;
+      report.value.style.height = clientHeight - 40 + "px";
+    }
+  });
+  showData.value = reportData.slice(0, showIndex);
+  autoShow.value = true;
+};
+const showMoreData = () => {
+  showData.value = reportData;
+  showMore.value = false;
+  report.value.style.height = "auto";
+  reportContainer.value.style.overflow = "auto";
 };
+const backToTop = () => {
+  window.scrollTo({ top: 0, behavior: "smooth" });
+};
+const checkScroll = throttle(() => {
+  const { clientHeight, scrollHeight } =
+    document.getElementsByClassName("layout-wrap")[0];
+  const scrollTop =
+    document.documentElement.scrollTop || document.body.scrollTop;
+  if (scrollTop >= 125) {
+    showBackTop.value = true;
+  } else {
+    showBackTop.value = false;
+  }
+  if (scrollTop + clientHeight >= scrollHeight) {
+    //请求下一页数据
+    getData();
+  }
+}, 300);
+
 const router = useRouter();
 const toClassPage = () => {
   //跳转至研报分类页
   router.push({ name: "ReportClassify" });
 };
+const toReportDetail = () => {};
 const changeTab = (className, item) => {
   if (className === "main") {
     activeTab.value = item.label;
@@ -30,87 +109,134 @@ const changeTab = (className, item) => {
     activeSubTab.value = item.label;
   }
 };
-onMounted(() => {});
+
+onMounted(async () => {
+  await getData();
+  checkMore();
+  document.addEventListener("scroll", checkScroll);
+  mounted.value = true;
+});
 </script>
 <template>
   <div class="report-list-page" ref="report">
-    <div class="report-main">
-      <div class="nav-wrap">
-        <div class="main-nav">
-          <div
-            class="main-nav-item"
-            :class="{ active: activeTab === item.label }"
-            v-for="item in varietyData"
-            :key="item.label"
-            @click="changeTab('main', item)"
-          >
-            {{ item.label }}
-          </div>
-          <div class="more" @click="toClassPage">
-            <span>查看更多</span>
-            <img :src="icon_more_path" class="more-img" />
+    <template v-if="pageModel === 'normal'">
+      <div class="report-main">
+        <div class="nav-wrap">
+          <div class="main-nav">
+            <div
+              class="main-nav-item"
+              :class="{ active: activeTab === item.label }"
+              v-for="item in varietyData"
+              :key="item.label"
+              @click="changeTab('main', item)"
+            >
+              {{ item.label }}
+            </div>
+            <div class="more" @click="toClassPage">
+              <span>查看更多</span>
+              <img :src="icon_more_path" class="more-img" />
+            </div>
           </div>
-        </div>
-        <div class="sub-nav">
-          <div
-            class="sub-nav-item"
-            v-for="item in varietySubData"
-            :key="item.label"
-            :class="{ active: activeSubTab === item.label }"
-            @click="changeTab('sub', item)"
-          >
-            {{ item.label }}
+          <div class="sub-nav">
+            <div
+              class="sub-nav-item"
+              v-for="item in varietySubData"
+              :key="item.label"
+              :class="{ active: activeSubTab === item.label }"
+              @click="changeTab('sub', item)"
+            >
+              {{ item.label }}
+            </div>
           </div>
         </div>
-      </div>
-      <div class="container">
-        <div
-          class="report-item"
-          v-for="item in reportData"
-          :key="item.timeLabel"
-        >
-          <div class="item-title">{{ item.timeLabel }}</div>
-          <div class="time-point-wrap">
+        <div class="container" ref="reportContainer">
+          <template v-if="autoShow">
             <div
-              class="time-point-item"
-              v-for="point in item.child"
-              :key="point.timeLabel"
+              class="report-item"
+              v-for="item in showData"
+              :key="item.timeLabel"
             >
-              <div class="point-time">{{ point.timeLabel }}</div>
-              <div class="point-title">{{ point.title }}</div>
-              <div class="point-content">{{ point.abstract }}</div>
-              <div class="point-tab" v-for="tab in point.label" :key="tab">
-                #{{ tab }}
+              <div class="item-title">{{ item.timeLabel }}</div>
+              <div class="time-point-wrap">
+                <div
+                  class="time-point-item"
+                  v-for="point in item.child"
+                  :key="point.timeLabel"
+                >
+                  <div class="point-time">{{ point.timeLabel }}</div>
+                  <div class="point-title">{{ point.title }}</div>
+                  <div class="point-content multy-ellipsis">{{ point.abstract }}</div>
+                  <div class="point-tab" v-for="tab in point.label" :key="tab">
+                    #{{ tab }}
+                  </div>
+                  <div class="point-see" @click="toReportDetail">查看全部</div>
+                </div>
               </div>
             </div>
+          </template>
+
+          <div class="check-more-wrap" v-if="showMore">
+            <div class="check-more" @click="showMoreData">加载更多</div>
           </div>
         </div>
-        <div class="check-more" v-if="showMore">加载更多</div>
       </div>
-    </div>
-    <div class="report-aside">
-      <div class="aside-item">
-        <div class="aside-title">最新资讯</div>
-        <div class="infomation-wrap">
-          <div
-            class="infomation-item"
-            v-for="item in informationData"
-            :key="item.title"
-          >
-            <div class="item-title">{{ item.title }}</div>
-            <div class="item-content">{{ item.no }}|{{ item.main }}</div>
+      <div class="report-aside">
+        <div class="aside-item">
+          <div class="aside-title">最新资讯</div>
+          <div class="infomation-wrap">
+            <div
+              class="infomation-item"
+              v-for="item in informationData"
+              :key="item.title"
+            >
+              <div class="item-title">{{ item.title }}</div>
+              <div class="item-content">{{ item.no }}|{{ item.main }}</div>
+            </div>
           </div>
         </div>
-      </div>
-      <div class="aside-item">
-        <div class="aside-title" style="margin-top:60px;">上新公告</div>
-        <div class="annoucement">
+        <div class="aside-item">
+          <div class="aside-title" style="margin-top: 60px">上新公告</div>
+          <div class="annoucement">
             <div class="title">行业调研</div>
             <p class="content">带您感知最微观的行业变化</p>
+          </div>
         </div>
+      </div></template
+    >
+    <template v-if="pageModel === 'search'">
+      <div class="search-main">
+        <!-- <div class="search-item" v-for="item in searchData" :key="item.title">
+          <div class="search-title">
+            {{ item.title }}
+          </div>
+          <div class="search-content multy-ellipsis">
+            {{ item.abstract }}
+          </div>
+          <div class="search-label" v-for="tab in item.label" :key="tab">
+            #{{ tab }}
+          </div>
+          <div class="search-time">
+            {{item.time}}
+          </div>
+        </div> -->
+        <template v-if="searchData.length">
+          <el-empty :image="report_empty_path" :image-size="400"
+          description="找不到对应报告,试试别的搜索词吧" />
+        </template>
       </div>
-    </div>
+    </template>
+    <transition name="fade">
+      <div class="back-top" v-show="showBackTop" @click="backToTop"></div>
+    </transition>
   </div>
+  <template v-if="mounted">
+    <teleport to=".head-main">
+      <Search
+        @search="handleSearch"
+        @clean="changePageStyle('normal')"
+      ></Search>
+    </teleport>
+  </template>
 </template>
 
 
@@ -119,7 +245,11 @@ onMounted(() => {});
 $bg-color: #f6f6f6;
 $active-color: #f3a52f;
 .report-list-page {
+  position: relative;
   .report-main {
+    display: flex;
+    flex-direction: column;
+    height: 100%;
     margin-right: 220px;
     margin-left: -20px;
     .nav-wrap {
@@ -147,6 +277,9 @@ $active-color: #f3a52f;
             border: 1px solid $active-color;
             box-shadow: 0px 6px 7px #fff7eb;
           }
+          &.active {
+            box-shadow: none;
+          }
         }
         .more {
           cursor: pointer;
@@ -154,6 +287,7 @@ $active-color: #f3a52f;
           color: $active-color;
           display: flex;
           align-items: center;
+          margin-left: 80px;
           .more-img {
             width: 16px;
             height: 16px;
@@ -182,7 +316,10 @@ $active-color: #f3a52f;
       }
     }
     .container {
+      position: relative;
+      flex: 1;
       padding: 30px;
+      overflow: hidden;
       .report-item {
         margin-top: 20px;
         &:first-child {
@@ -229,6 +366,7 @@ $active-color: #f3a52f;
               margin-bottom: 10px;
               font-size: 14px;
               color: #666666;
+              max-height: 50px;
             }
             .point-time {
               &::before {
@@ -255,20 +393,52 @@ $active-color: #f3a52f;
                 margin-right: 0;
               }
             }
+            .point-see {
+              float: right;
+              cursor: pointer;
+              width: 88px;
+              height: 30px;
+              color: $active-color;
+              text-align: center;
+              line-height: 30px;
+              background: #fffbf5;
+              border: 1px solid $active-color;
+              border-radius: 15px;
+            }
           }
         }
       }
+      .check-more-wrap {
+        position: absolute;
+        width: 100%;
+        left: 0;
+        bottom: 0;
+        background-color: white;
+        .check-more {
+          margin: auto;
+          cursor: pointer;
+          width: 140px;
+          height: 40px;
+          box-sizing: border-box;
+          text-align: center;
+          line-height: 40px;
+          border-radius: 20px;
+          background-color: #fffbf5;
+          color: $active-color;
+          border: 1px solid $active-color;
+        }
+      }
     }
   }
 
   .report-aside {
     position: absolute;
-    top: 0;
-    right: 0;
+    top: -20px;
+    bottom: -20px;
+    right: -20px;
     width: 240px;
-    height: 100%;
     box-sizing: border-box;
-    padding: 102px 30px;
+    padding: 30px;
     border-left: 2px solid #f2f2f2;
     .aside-title {
       font-size: 18px;
@@ -306,26 +476,107 @@ $active-color: #f3a52f;
         }
       }
     }
-    .annoucement{
-        margin-top: 20px;
-        padding:18px;
-        height:110px;
-        border: 1px solid black;
-        border-radius: 5px;
-        background: no-repeat center/cover url("../../assets/announce-pic.png")  ;
-       /*  background-image: url("../../assets/announce-pic.png");
+    .annoucement {
+      margin-top: 20px;
+      padding: 18px;
+      height: 110px;
+      border: 1px solid black;
+      border-radius: 5px;
+      background: no-repeat center/cover url("../../assets/announce-pic.png");
+      /*  background-image: url("../../assets/announce-pic.png");
         background-repeat: no-repeat;
         background-position: center;
         background-size: cover; */
-        .title{
-            color: #CFC09F;
-            font-size: 18px;
-        }
-        .content{
-            color: #CFC09F;
-            line-height: 18px;
+      .title {
+        color: #cfc09f;
+        font-size: 18px;
+      }
+      .content {
+        color: #cfc09f;
+        line-height: 18px;
+      }
+    }
+  }
+  .back-top {
+    position: fixed;
+    z-index: 2000;
+    cursor: pointer;
+    width: 60px;
+    height: 60px;
+    bottom: 110px;
+    right: 30px;
+    border-radius: 50%;
+    background-color: #ffffff;
+    border: 2px solid #e6e6e6;
+    box-shadow: 0px 0px 6px #e3e3e3;
+    &::before {
+      content: "";
+      width: 0;
+      display: block;
+      margin: auto;
+      border: 17px solid $active-color;
+      border-color: transparent transparent $active-color transparent;
+    }
+  }
+  .search-main {
+    .search-item {
+      cursor: pointer;
+      padding: 30px;
+      width: 820px;
+      margin: 20px auto;
+      background-color: #ffffff;
+      border: 1px solid #e5e5e5;
+      box-shadow: 3px 3px 12px rgba(184, 184, 184, 0.16);
+      border-radius: 4px;
+      &:first-child{
+        margin-top: 0px;
+      }
+      .search-title {
+        color: #333333;
+        font-size: 18px;
+      }
+      .search-content {
+        font-size: 14px;
+        color: #666666;
+        margin: 15px 0;
+        max-height: 50px;
+      }
+      .search-label {
+        display: inline-block;
+        font-size: 14px;
+        color: $active-color;
+        margin-right: 30px;
+        &:last-child {
+          margin-right: 0;
         }
+      }
+      .search-time{
+        float: right;
+        color: #999999;
+      }
     }
+    :deep .el-empty__description{
+      p{
+        color: #333333;
+      }
+    }
+  }
+
+  .multy-ellipsis {
+    overflow: hidden;
+    text-overflow: ellipsis;
+    display: -webkit-box;
+    -webkit-line-clamp: 3;
+    -webkit-box-orient: vertical;
+  }
+  .fade-enter-active,
+  .fade-leave-active {
+    transition: opacity 0.3s ease;
+  }
+
+  .fade-enter-from,
+  .fade-leave-to {
+    opacity: 0;
   }
 }
 </style>

+ 47 - 3
src/views/report/staticData.js

@@ -88,7 +88,7 @@ const varietyData = [{
         ]
     }
 ]
-//mock最新资讯数据
+//mock 最新资讯数据
 const informationData = [{
         title: '股债日评',
         no: 178,
@@ -105,7 +105,7 @@ const informationData = [{
         main: '甲醇库存甲醇库存甲醇库存甲醇库存'
     }
 ]
-//mock研报首页数据
+//mock 研报首页数据
 const reportData = [{
     timeLabel: '3.28周一',
     child: [{
@@ -118,6 +118,16 @@ const reportData = [{
         title: '啦啦啦啦',
         abstract: '随便写点什么',
         label:['宏观双周报']
+    },{
+        timeLabel: '15:40:49',
+        title: '关注中美领导交流',
+        abstract: 'aaaa',
+        label:['双周报','宏观双周报']
+    },{
+        timeLabel: '15:42:48',
+        title: '啦啦啦啦',
+        abstract: '随便写点什么',
+        label:['宏观双周报']
     }
 ]
 },{
@@ -136,9 +146,43 @@ const reportData = [{
 ]
 }
 ]
+//mock 首页的搜索数据
+const searchData = [
+    {
+        title:'地产政策的变化和影响',
+        abstract:'中美领导视频会在本周进行,从会前吹风来看,中美在碳中和和关税上存在政策诉求。如果中美关税下调,则人民币会有明确的升值反应。中美领导视频会在本周进行,从会前吹风来看,中美在碳中和和关税上存在政策诉求。如果中美关税下调,则人民币会有明确的升值反应。中美在碳中和和关税上存在政策诉求。如果中美关税下调,则人民币会有明确的升值反应。中美领导视频会在本周进行,从会前吹中美在碳中和和关税上存在政策诉求。如果中美关税下调,则人民币会有明确的升值反应。中美领导视频会在本周进行,从会前吹',
+        label:['双周报','煤炭双周报'],
+        time:'2021年8月31日 15:30'
+    },
+    {
+        title:'地产政策的变化和影响2',
+        abstract:'abjahajh',
+        label:['双周报','煤炭双周报'],
+        time:'2021年8月31日 15:30'
+    },
+    {
+        title:'地产政策的变化和影响3',
+        abstract:'abjahajh',
+        label:['双周报','煤炭双周报'],
+        time:'2021年8月31日 15:30'
+    },
+    {
+        title:'地产政策的变化和影响4',
+        abstract:'abjahajh',
+        label:['双周报','煤炭双周报'],
+        time:'2021年8月31日 15:30'
+    },
+    {
+        title:'地产政策的变化和影响5',
+        abstract:'abjahajh',
+        label:['双周报','煤炭双周报'],
+        time:'2021年8月31日 15:30'
+    }
+]
 
 export {
     varietyData,
     informationData,
-    reportData
+    reportData,
+    searchData
 }