瀏覽代碼

冲突解决

hbchen 2 年之前
父節點
當前提交
41999989a7

+ 7 - 0
src/api/common.js

@@ -133,4 +133,11 @@ export const apiReadOneNotice=params=>{
  */
  export const apiDelOneNotice=params=>{
     return post('/message/delete',params)
+}
+
+/**
+ * 公告FICC品种权限数据
+ */
+export const apiFICCPermissionList=()=>{
+    return get('/company/permission/tree',{})
 }

+ 26 - 0
src/api/priceDriven.js

@@ -0,0 +1,26 @@
+/* 价格驱动 */
+
+import {get,post} from './http'
+
+/**
+ * 菜单
+ */
+export const tabList = parmas=>{
+  return get('/price_driven/tab',parmas)
+}
+
+/**
+ * 价格驱动详情
+ * @param {chart_permission_id} params 
+ * @returns 
+ */
+export const priceDrivenDetail = params => {
+  return get('/price_driven/detail',params)
+}
+
+/**
+ * 埋点 price_driven_id source_agent 1-小程序 2-小程序PC 3-公众号 4-Web官网
+ */
+export const priceDrivenLog = params => {
+  return post('/price_driven/visit_log',params)
+}

+ 8 - 0
src/api/report.js

@@ -204,4 +204,12 @@ export const apiReportHotCommentList=params=>{
  */
 export const apiDelMyComment=params=>{
     return post('/comment/del',params)
+}
+
+/**
+ * 日度点评分享图片
+ * @param title
+ */
+export const apiRddpShareImg=params=>{
+    return post('/report/detail/rddp_share_img',params)
 }

+ 32 - 0
src/api/sandBox.js

@@ -0,0 +1,32 @@
+/**
+ * 沙盘推演模块
+ */
+ import {get,post} from './http'
+
+ /**
+  * 有权限的二级分类以及下面的沙盘图数量
+  */
+ export const apiSandBoxPermission=()=>{
+     return get('/sandbox/permission_count')
+ }
+
+ /**
+  * 沙盘图列表
+  * @param  page_size 一页的数据量
+  * @param  current_index 页数
+  * @param  keyword 搜索关键词
+  * @param  chart_permission_id 二级分类Id
+  * @param  is_high_light 是否高亮  true | false
+  */
+  export const apiSandBoxList=(params)=>{
+    return get('/sandbox/list',params)
+}
+
+/**
+ * 沙盘图详情
+ * @param sandbox_id 沙盘ID
+ */
+ export const apiSandTableDetail = (params) => {
+    return get("/sandbox/detail", params);
+};
+ 

+ 1 - 1
src/api/user.js

@@ -16,7 +16,7 @@ export const apiUserInfo=()=>{
  * @param company_name 公司名
  * @param permission 选择的权限
  * @param real_name 姓名
- * @param source 来源:我的1、活动2、图库3、研报4
+ * @param source 来源:我的1、活动2、图库3、研报4、沙盘推演7
  * @param source_agent 来源平台:1:小程序、2:小程序(pc)、3:公众号、4:官网web(pc)
  * @param from_page 来源页面: '活动列表'、'活动详情'等
  */

+ 31 - 0
src/api/video.js

@@ -0,0 +1,31 @@
+/**
+ * 视频社区模块
+ */
+ import {get,post} from './http'
+
+/**
+ * 视频列表
+ * @param page_index
+ * @param page_size
+ * @param keywords
+ * @param video_id
+ * @param chart_permission_id
+ */
+ export const apiVideoList=params=>{
+    return get('/community/video/list',params)
+}
+
+/**
+ * 视频播放埋点
+ * @param video_id
+ * @param source_agent 来源平台:1:小程序、2:小程序(pc)、3:公众号、4:官网web(pc)
+ */
+export const apiVideoPlayLog=params=>{
+    let source_agent=2
+	if(window.__wxjs_environment === 'miniprogram'){
+		source_agent=2
+	}else{
+		source_agent=4
+	}
+    return post('/community/video/play_log',{...params,source_agent:source_agent})
+}

二進制
src/assets/leftNav/sandBox-s.png


二進制
src/assets/leftNav/video-s.png


二進制
src/assets/pricedriven/new_ico.png


二進制
src/assets/pricedriven/nodata.png


二進制
src/assets/video-play-btn.png


+ 21 - 7
src/layout/component/Aside.vue

@@ -29,19 +29,33 @@ const menuList = reactive([
     icon_path: new URL('../../assets/leftNav/report-s.png', import.meta.url).href,
     children: null,
   },
-  // {
-  //   MenuId: 1,
-  //   name: "ETA图库",
-  //   path: "/chart/list",
-  //   icon_path: new URL('../../assets/leftNav/chart-s.png', import.meta.url).href,
-  //   children: null,
-  // },
+  {
+    MenuId: 2,
+    name: "价格驱动",
+    path: "/pricedriven",
+    icon_path: new URL('../../assets/leftNav/chart-s.png', import.meta.url).href,
+    children: null,
+  },
   {
     MenuId: 3,
     name: "活动",
     path: "/activity/list",
     icon_path: new URL('../../assets/leftNav/activity-s.png', import.meta.url).href,
     children: null,
+  },
+  {
+  MenuId: 4,
+  name: "沙盘推演",
+  path: "/sandBox/list",
+  icon_path: new URL('../../assets/leftNav/sandBox-s.png', import.meta.url).href,
+  children: null,
+},
+{
+    MenuId: 5,
+    name: "视频社区",
+    path: "/video/list",
+    icon_path: new URL('../../assets/leftNav/video-s.png', import.meta.url).href,
+    children: null,
   },{
     MenuId: 6,
     name: "问答",

+ 2 - 0
src/main.js

@@ -14,6 +14,8 @@ if(import.meta.env.MODE==='test'){
     const vConsole = new VConsole();
 }
 
+console.log('测试下新发布平台');
+
 const app = createApp(App)
 app.directive('drag', drag)
 

+ 85 - 21
src/router/index.js

@@ -224,37 +224,58 @@ const routes=[
       }
     ]
   },
-  //问答模块
+
+  // 沙盘推演模块
   {
-    path: "/question",
-    name: "Question",
-    redirect:'/question/list',
+    path:'/sandBox',
+    name:"sandBox",
     component: () => import("@/layout/Index.vue"),
-    meta: {
-      title:"问答"
+    redirect:'/sandBox/list',
+    meta:{
+      title:"沙盘推演"
     },
-    children: [
+    children:[
       {
-        path: "list",
-        name: "QuestionList",
-        component: () => import("@/views/question/List.vue"),
-        meta: {
-          title: "问答社区",
-          keepAlive:true,
-          isRoot:true
-        }
-      },{
-        path: "mylist",
-        name: "QuestionMyList",
-        component: () => import("@/views/question/MyList.vue"),
+        path:"list",
+        name:"sandBoxList",
+        component:()=>import('@/views/sandBox/List.vue'),
         meta: {
-          title: "我的问答",
+          title: "沙盘推演",
+          keepAlive:false,
           isRoot:true
         }
       }
     ]
   },
-
+    //问答模块
+    {
+      path: "/question",
+      name: "Question",
+      redirect:'/question/list',
+      component: () => import("@/layout/Index.vue"),
+      meta: {
+        title:"问答"
+      },
+      children: [
+        {
+          path: "list",
+          name: "QuestionList",
+          component: () => import("@/views/question/List.vue"),
+          meta: {
+            title: "问答社区",
+            keepAlive:true,
+            isRoot:true
+          }
+        },{
+          path: "mylist",
+          name: "QuestionMyList",
+          component: () => import("@/views/question/MyList.vue"),
+          meta: {
+            title: "我的问答",
+          }
+        }
+      ]
+    },
   // 用户模块
   {
     path:'/user',
@@ -278,6 +299,49 @@ const routes=[
     ]
   },
 
+  //价格驱动
+  {
+    path: '/pricedriven',
+    name: "priceDriven",
+    component: () => import("@/layout/Index.vue"),
+    meta: {
+      title:"价格驱动"
+    },
+    children: [
+      {
+        path: "/pricedriven",
+        name: "priceDrivenDeteail",
+        component: () => import("@/views/priceDriven/detail.vue"),
+        meta: {
+          title: "价格驱动",
+          keepAlive:false,
+          isRoot:true
+        },
+      },
+    ]  
+  },
+  //视频社区模块
+  {
+    path:'/video',
+    name:"Video",
+    component: () => import("@/layout/Index.vue"),
+    meta:{
+      title:"视频社区"
+    },
+    children:[
+      {
+        path:"list",
+        name:"VideoList",
+        component:()=>import('@/views/video/List.vue'),
+        meta: {
+          title: "视频社区",
+          keepAlive:true,
+          isRoot:true
+        }
+      }
+    ]
+  },
+
   {
     path: '/:pathMatch(.*)',
     name: 'error',

+ 0 - 1
src/store/index.js

@@ -7,7 +7,6 @@ if(window.__wxjs_environment === 'miniprogram'){
   platform='xcx'
 }
 const breadcrumb=sessionStorage.getItem('breadcrumb')?JSON.parse(sessionStorage.getItem('breadcrumb')):[{name:'',path:location.pathname||''}]
-
 export default createStore({
   state: {
     platform:platform,//所处平台 web/xcx

+ 14 - 1
src/views/activity/Detail.vue

@@ -79,9 +79,22 @@ getDetail();
 // 处理分享数据
 const handleShareMsg=()=>{
     //向小程序发送消息
+
+    // let title=''
+    //     if(this.info.firstActivityTypeId==1){
+    //         title=this.info.reportName.split('】')[1]||this.info.activityName
+    //         if(this.info.is_new_report==1){
+    //             title=this.info.reportName
+    //         }
+    //     }else{
+    //         title=this.info.activityName
+    //     }
     let title=''
     if(info.value.firstActivityTypeId==1){
         title=info.value.reportName.split('】')[1]||info.value.activityName
+        if(info.value.is_new_report==1){
+            title=info.value.reportName
+        }
     }else{
         title=info.value.activityName
     }
@@ -327,7 +340,7 @@ const posterParams=computed(()=>{
             <div class="active-time"> 活动时间:{{ formatActivityTime(info.startTime, info.endTime) }}</div>
         </div>
         <div class="flex report-name">
-            <span>{{ info.reportName ? info.reportName.split("】")[1] : info.activityName }}</span>
+            <span>{{ info.is_new_report==1?info.reportName:info.reportName?info.reportName.split('】')[1]:info.activityName }}</span>
             <div class="global-main-btn" v-if="info.reportLink" @click="goDetail">查看相关报告</div>
         </div>
         <!-- 音频模块 -->

+ 615 - 0
src/views/priceDriven/detail.vue

@@ -0,0 +1,615 @@
+<script setup>
+import { ref, onMounted, computed, nextTick, onUnmounted } from "vue";
+import { ElMessageBox } from "element-plus";
+import {
+  ArrowDown,
+  ArrowUp,
+  CaretBottom,
+  CaretTop,
+} from "@element-plus/icons-vue";
+import { useRoute, useRouter } from "vue-router";
+import { useStore } from 'vuex';
+import moment from "moment";
+import * as priceApi from "@/api/priceDriven.js";
+import { apiGetWechatQRCode } from "@/api/common";
+import { apiApplyPermission } from "@/api/user";
+import SharePoster from '@/components/SharePoster.vue'
+
+const store=useStore()
+const router = useRouter();
+const route = useRoute();
+localStorage.setItem("hzyb-token", route.query.token);
+
+const noAuthInfo = ref(null); //无权限信息
+/* 分类 */
+const varietiesList = ref([]);
+const classifyList = ref([]);
+const select_classify_first = ref(""); //选中的品种
+const select_classify_sub = ref(""); //选中的分类
+const select_classify_subtitle = ref(""); //选中分类名称
+const isSlideClassify = ref(false);
+/* 获取分类 */
+const getClassify = async () => {
+  const { code, data } = await priceApi.tabList();
+
+  if (code === 200) {
+    const { permission_list } = data;
+    permission_list.forEach(_ => {
+      _.isShow = _.list ? _.list.some(sub_item => sub_item.pirce_driven_state) : false
+    })
+    varietiesList.value = permission_list.filter(_ => _.isShow);
+
+    // 分享进入的默认品种
+    if (route.query.default_classify_first) {
+      let index = varietiesList.value.findIndex(
+        (_) => _.id === Number(route.query.default_classify_first)
+      );
+      index === -1 ? errorLinkHandle() : changeClassify(varietiesList.value[index],'share');
+    } else {
+      changeClassify(varietiesList.value[0]);
+    }
+  } else if (code === 403) {
+    noAuthInfo.value = data;
+  }
+};
+getClassify();
+/* 选择一级分类 */
+const changeClassify = (item,type='') => {
+  const { id, list } = item;
+  select_classify_first.value = id;
+  classifyList.value = list.filter(_ => _.pirce_driven_state);
+  isSlideClassify.value = false;
+
+  //分享进入的默认品种
+  if (type === 'share') {
+    let index = classifyList.value.findIndex(
+      (_) => _.chart_permission_id === Number(route.query.default_classify_sub)
+    );
+    index === -1 ? errorLinkHandle() : changeSubClassify(classifyList.value[index]);
+  } else {
+    changeSubClassify(classifyList.value[0]);
+  }
+};
+/* 选择二级分类 */
+const changeSubClassify = ({ chart_permission_id, chart_permission_name }) => {
+  select_classify_sub.value = chart_permission_id;
+  select_classify_subtitle.value = chart_permission_name;
+  document.body.scrollTop = document.documentElement.scrollTop = 0;
+  getDetail();
+  getQrCodeHandle();
+};
+/* 过期link处理 */
+const errorLinkHandle = () => {
+  Toast('该价格驱动不存在')
+
+  setTimeout(() => {
+    wx.miniProgram.switchTab({ 
+      url:"/pages/report/report"
+    });
+  },1000)
+}
+
+//详情信息
+const showData = ref(false);
+const headerWidth = ref("");
+const info = ref({});
+/* 获取详情 */
+const getDetail = async () => {
+  showData.value = false;
+  const { code, data } = await priceApi.priceDrivenDetail({
+    chart_permission_id: select_classify_sub.value,
+  });
+
+  if (code !== 200) return;
+  showData.value = true;
+  info.value = data;
+  
+  nextTick(() => {
+    resetHeaderWidthHandle();
+  });
+
+  //向小程序发送分享数据
+  wx.miniProgram.postMessage({
+    data: {
+      title: `${select_classify_subtitle.value}价格驱动`,
+      path:'/pages/pricedriven/pricedriven',
+      params:{
+        default_classify_first: select_classify_first.value,
+        default_classify_sub: select_classify_sub.value,
+      }
+    },
+  });
+  info.value.price_driven_id && visitPriceDrivenLog();
+};
+const visitPriceDrivenLog = async() => {
+  await priceApi.priceDrivenLog({
+    price_driven_id: info.value.price_driven_id,
+    source_agent: store.state.platform === 'web' ? 4 : 2
+  })
+}
+
+//点击申请
+const handleGoApply = async () => {
+  if (noAuthInfo.value.customer_info.has_apply) {
+    const htmlStr = `<p>您已提交过申请,请耐心等待</p>`;
+    ElMessageBox({
+      title: "温馨提醒",
+      message: htmlStr,
+      center: true,
+      dangerouslyUseHTMLString: true,
+      confirmButtonText: "知道了",
+      confirmButtonClass: "self-elmessage-confirm-btn",
+    });
+  } else {
+    if (
+      !noAuthInfo.value.customer_info.status ||
+      noAuthInfo.value.customer_info.status !== "流失"
+    ) {
+      router.push({
+        path: "/apply/permission",
+        query: {
+          source: 6,
+          fromPage: "价格驱动",
+        },
+      });
+    } else {
+      //主动调一次申请权限接口
+      const res = await apiApplyPermission({
+        company_name: noAuthInfo.value.customer_info.company_name,
+        real_name: noAuthInfo.value.customer_info.name,
+        source: 6,
+        from_page: "价格驱动",
+      });
+      if (res.code !== 200) return;
+      const htmlStr = `<p>申请已提交</p><p>请等待销售人员与您联系</p>`;
+      ElMessageBox({
+        title: "温馨提醒",
+        message: htmlStr,
+        center: true,
+        dangerouslyUseHTMLString: true,
+        confirmButtonText: "知道了",
+        confirmButtonClass: "self-elmessage-confirm-btn",
+      });
+    }
+  }
+};
+
+
+/* 分享海报参数 */
+const code_scene = computed(() =>
+  JSON.stringify({
+    default_classify_first: select_classify_first.value,
+    default_classify_sub: select_classify_sub.value,
+  })
+);
+const posterParams = computed(() => ({
+  list_title: `${select_classify_subtitle.value}价格驱动`,
+  core_driven_type: info.value.core_driven_type ? "空" : "多",
+  main_variable: info.value.main_variable,
+  update_time: moment(info.value.modify_time).format("YYYY-MM-DD dd"),
+  core_driven_content: info.value.core_driven_content,
+}));
+console.log(moment(info.value.modify_time).format("YYYY-MM-DD ddd"));
+
+// 小程序码
+let qrCode = ref("");
+const getQrCodeHandle = async () => {
+  const res = await apiGetWechatQRCode({
+    CodeScene: JSON.stringify({
+      default_classify_first: select_classify_first.value,
+      default_classify_sub: select_classify_sub.value,
+    }),
+    CodePage:'pages/pricedriven/pricedriven'
+    // CodePage: "pages-report/reportDetail",
+  });
+  if (res.code === 200) {
+    qrCode.value = res.data;
+  }
+};
+
+
+/* 重绘固定头宽度 */
+const resetHeaderWidthHandle = () => {
+  headerWidth.value = document.getElementsByClassName("content-box")[0].offsetWidth;
+}
+
+let preViewImgs = ref([]);
+let preViewImgIndex = ref(0);
+let showPreViewImg = ref(false);
+onMounted(() => {
+  $(document).on("click", ".rich-section img", function (event) {
+    let imgArray = [];
+    let curImageSrc = $(this).attr("src");
+    let oParent = $(this).parent();
+    if (curImageSrc && !oParent.attr("href")) {
+      if (preViewImgs.value.length === 0) {
+        $(".rich-section img").each(function (index, el) {
+          let itemSrc = $(this).attr("src");
+          imgArray.push(itemSrc);
+        });
+        preViewImgs.value = imgArray;
+      }
+      preViewImgIndex.value = preViewImgs.value.indexOf(curImageSrc) || 0;
+      showPreViewImg.value = true;
+    }
+  });
+
+  window.addEventListener('resize',resetHeaderWidthHandle)
+});
+
+onUnmounted(() => {
+  window.removeEventListener('resize',resetHeaderWidthHandle)
+})
+</script>
+
+<template>
+  <div class="pricedriven-page hasrightaside-box">
+    <div class="content-box">
+      <div
+        class="classify-cont"
+        v-if="varietiesList.length"
+        :style="`width:${headerWidth}px`"
+      >
+        <ul class="classsify-frist">
+          <li
+            :class="[
+              'classify-frist-item',
+              { act: select_classify_first === item.id },
+            ]"
+            v-for="(item, index) in varietiesList"
+            :key="item.classify_name"
+            @click="changeClassify(item, index)"
+          >
+            {{ item.classify_name }}
+          </li>
+        </ul>
+        <ul
+          class="classsify-sub"
+          :style="isSlideClassify ? 'height: auto' : 'height: 25px'"
+        >
+          <li
+            :class="[
+              'classify-sub-item',
+              { act: select_classify_sub === item.chart_permission_id },
+            ]"
+            v-for="item in classifyList.slice(0,6)"
+            :key="item.chart_permission_id"
+            @click="changeSubClassify(item)"
+          >
+            {{ item.chart_permission_name }}
+          </li>
+
+          <el-popover
+            :width="500"
+            trigger="click"
+          >
+            <template #reference>
+              <img v-if="classifyList.length>6" style="width:16px;transform: rotate(90deg);cursor: pointer" src="@/assets/icon-more.png" alt="">
+            </template>
+            <template #default>
+              <div class="flex top-nav-filter-box">
+                <div 
+                    :class="['item',item.chart_permission_id == select_classify_sub&&'active']" 
+                    v-for="item in classifyList.slice(6)" 
+                    :key="item.chart_permission_id"
+                    @click="changeSubClassify(item)"
+                >{{item.chart_permission_name}}</div>
+              </div>
+            </template>
+          </el-popover>
+          <!-- <div
+            class="slide"
+            v-if="!isSlideClassify"
+            @click="isSlideClassify = !isSlideClassify"
+          >
+            展开 <el-icon><ArrowDown /></el-icon>
+          </div>
+          <div class="slide" v-else @click="isSlideClassify = !isSlideClassify">
+            收起 <el-icon><ArrowUp /></el-icon>
+          </div> -->
+        </ul>
+      </div>
+
+      <template v-if="showData">
+        <!-- 内容 -->
+        <div class="richtext-container" v-if="info.core_content">
+          <div class="section">
+            <h3>更新时间:</h3>
+            <p class="time">
+              {{ moment(info.modify_time).format('YYYY年MM月DD日 ddd') }}
+              <img
+                src="@/assets/pricedriven/new_ico.png"
+                alt=""
+                class="new_tag"
+                v-if="
+                  moment(info.modify_time).format('YYYY-MM-DD') ===
+                  moment().format('YYYY-MM-DD')
+                "
+              />
+            </p>
+          </div>
+          <div class="section">
+            <h3>关键变量:</h3>
+            <p>{{ info.main_variable }}</p>
+          </div>
+          <div class="section">
+            <h3 class="flex">
+              核心驱动:
+              <div :class="['tag flex', info.core_driven_type ? 'down' : 'up']">
+                <span style="margin-right: 5px">
+                  {{ info.core_driven_type ? "空" : "多" }}</span
+                >
+                <el-icon v-if="info.core_driven_type"><CaretBottom /></el-icon>
+                <el-icon v-else><CaretTop /></el-icon>
+              </div>
+            </h3>
+            <p
+              :class="info.core_driven_type ? 'down' : 'up'"
+              style="padding: 20px 17px"
+            >
+              {{ info.core_driven_content }}
+            </p>
+          </div>
+          <div class="rich-section">
+            <h3>核心内容:</h3>
+            <div v-html="info.core_content"></div>
+          </div>
+
+          <!-- 生成海报 -->
+          <Teleport to="body">
+            <SharePoster
+              :shareData="{
+                type: 'pricedriven',
+                code_page: 'pages/pricedriven/pricedriven',
+                code_scene: code_scene,
+                data: posterParams,
+              }"
+              v-if="info && info.core_content"
+            ></SharePoster>
+          </Teleport>
+        </div>
+
+        <!-- 无数据 -->
+        <div class="no-auth-wrap nodata" v-else>
+          <div class="apply-box">
+            <img
+              src="@/assets/pricedriven/nodata.png"
+              alt=""
+              class="nodata-img"
+            />
+            <div>暂无数据</div>
+          </div>
+        </div>
+      </template>
+
+      <!-- 无权限 -->
+      <div class="no-auth-wrap" v-if="noAuthInfo">
+        <div class="apply-box" v-if="noAuthInfo.type == 'apply'">
+          <img
+            src="@/assets/pricedriven/nodata.png"
+            alt=""
+            class="nodata-img"
+          />
+          <div>您暂无权限查看价格驱动,若想查看请申请开通</div>
+          <div class="global-main-btn btn" @click="handleGoApply">立即申请</div>
+        </div>
+        <div class="apply-box" v-else>
+          <img
+            src="@/assets/pricedriven/nodata.png"
+            alt=""
+            class="nodata-img"
+          />
+          <div style="margin-bottom: 10px;">您暂无权限查看价格驱动</div>
+          <div>若想查看请联系对口销售</div>
+          <div>{{ noAuthInfo.name }}:<span style="color: #e3b377">{{ noAuthInfo.mobile }}</span>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="right-aside-box" v-if="!noAuthInfo">
+      <div class="fix-top">
+        <div class="share-box">
+          <div class="label">分享</div>
+          <el-popover
+            :width="200"
+            popper-style="box-shadow: rgb(14 18 22 / 35%) 0px 10px 38px -10px, rgb(14 18 22 / 20%) 0px 10px 20px -15px; padding: 20px;"
+          >
+            <template #reference><div class="icon"></div></template>
+
+            <template #default>
+              <img
+                :src="qrCode"
+                class="share-xcx-img"
+                alt=""
+                style="width: 150px; display: block; margin: 0 auto"
+              />
+            </template>
+          </el-popover>
+        </div>
+      </div>
+    </div>
+
+    <!-- 图片预览 -->
+    <el-image-viewer
+      v-if="showPreViewImg"
+      :initial-index="preViewImgIndex"
+      @close="showPreViewImg = false"
+      :url-list="preViewImgs"
+    />
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.pricedriven-page {
+  display: flex;
+  .classify-cont {
+    box-shadow: 0px 2px 0px rgba(0, 0, 0, 0.04);
+    padding: 30px 20px 12px;
+    position: fixed;
+    top: 60px;
+    z-index: 9;
+    background: #fff;
+    .classsify-frist {
+      display: flex;
+      align-items: center;
+      .classify-frist-item {
+        width: 140px;
+        height: 40px;
+        text-align: center;
+        line-height: 40px;
+        background: #f5f5f5;
+        border-radius: 40px;
+        margin-right: 30px;
+        border: 1px solid transparent;
+        cursor: pointer;
+        &:last-child {
+          margin-right: 0;
+        }
+        &.act,
+        &:hover {
+          background: #fdf8f2;
+          color: #e3b377;
+          border-color: #f3a52f;
+        }
+      }
+      @media screen and (max-width: 1350px) {
+        .classify-frist-item {
+          width: 90px;
+          height: 30px;
+          line-height: 30px;
+          margin-right: 15px;
+          font-size: 14px;
+        }
+      }
+    }
+    .classsify-sub {
+      margin-top: 30px;
+      display: flex;
+      flex-wrap: wrap;
+      align-items: center;
+      position: relative;
+      height: 25px;
+      overflow-y: hidden;
+      .classify-sub-item {
+        flex-shrink: 0;
+        position: relative;
+        margin: 5px 30px 4px 0;
+        cursor: pointer;
+        &.act {
+          color: #e3b377;
+        }
+      }
+      .slide {
+        position: absolute;
+        right: 0;
+        top: 3px;
+        cursor: pointer;
+      }
+    }
+  }
+  .richtext-container {
+    margin-top: 130px;
+    .section {
+      padding: 20px;
+      border-bottom: 2px solid #f1f1f1;
+      > h3 {
+        font-size: 16px;
+      }
+      > p {
+        color: #666;
+      }
+      .time {
+        display: flex;
+        align-items: center;
+        .new_tag {
+          width: 24px;
+          height: 24px;
+          margin-left: 10px;
+        }
+      }
+      .tag {
+        font-size: 14px;
+        padding: 0 4px;
+        border: 1px solid transparent;
+        border-radius: 4px;
+        margin-left: 20px;
+        align-items: center;
+      }
+      .up {
+        background: #feefef;
+        color: #d64c4c;
+      }
+      .down {
+        background: #ebfff0;
+        color: #4fc08d;
+      }
+    }
+    .rich-section {
+      padding: 20px;
+      ::v-deep(img) {
+        width: 100% !important;
+      }
+      ::v-deep(span) {
+        font-size: 18px !important;
+        line-height: 1.8 !important;
+      }
+      ::v-deep(p) {
+        font-size: 18px !important;
+        line-height: 1.8 !important;
+      }
+      ::v-deep(iframe){
+        width: 100% !important;
+      }
+    }
+  }
+  .right-aside-box {
+    z-index: 99;
+  }
+  .no-auth-wrap {
+    text-align: center;
+    padding: 20px 0;
+    color: #666;
+    &.nodata {
+      margin-top: 130px;
+    }
+    img{
+        width: 400px;
+    }
+    .btn{
+        width: 218px;
+        margin-top: 10px;
+        margin-left: auto;
+        margin-right: auto;
+    }
+  }
+
+}
+.top-nav-filter-box{
+  flex-wrap: wrap;
+  .item{
+      margin: 5px 10px;
+      width: 113px;
+      height: 40px;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      background-color: #f6f6f6;
+      border-radius: 4px;
+      font-size: 16px;
+      cursor: pointer;
+      :hover{
+          color: #fff;
+          background-color: #F3A52F;
+      }
+  }
+  .active{
+      color: #fff;
+      background-color: #F3A52F;
+  }
+}
+</style>
+<style lang="scss">
+  p[data-f-id="pbf"] {
+    display: none !important;
+  }
+</style>

+ 6 - 1
src/views/report/ChapterDetail.vue

@@ -124,12 +124,14 @@ const getChapterReportDetail=async ()=>{
         }
 
         //向小程序发送分享数据
+        //处理分享标题
+        const shareTitle=`【${res.data.report_chapter_item.classify_name_first} | ${res.data.report_chapter_item.type_name}】${res.data.report_chapter_item.title}(${moment(res.data.report_chapter_item.publish_time).format('MMDD')})`
         const postData={
             path:'/pages-report/chapterDetail',
             params:{
                 chapterId:chapterId.value
             },
-            title:`FICC【${res.data.report_chapter_item.classify_name_first}】`,
+            title:shareTitle,
             shareImg:''
         }
         wx.miniProgram.postMessage({ data: postData })
@@ -516,6 +518,9 @@ const posterParams=computed(()=>{
                 font-size: 18px !important;
                 line-height: 1.8 !important;
             }
+            :deep(iframe){
+                width: 100% !important;
+            }
         }
     }
     .no-auth-wrap{

+ 35 - 15
src/views/report/Detail.vue

@@ -6,7 +6,7 @@ import 'moment/dist/locale/zh-cn'
 import AudioBox from './components/AudioBox.vue'
 import SharePoster from '@/components/SharePoster.vue'
 import Comment from '@/components/Comment.vue'
-import {apiReportDetail,apiReportMoreRecmd,apiReportDetailBanner} from '@/api/report'
+import {apiReportDetail,apiReportMoreRecmd,apiReportDetailBanner,apiRddpShareImg} from '@/api/report'
 import {apiGetWechatQRCode} from '@/api/common'
 import { useRoute , onBeforeRouteUpdate,useRouter} from 'vue-router';
 import { useStore } from 'vuex';
@@ -65,13 +65,42 @@ const getReportDetail=async ()=>{
         }
 
         //向小程序发送分享数据
+        //处理分享标题
+        let shareTitle=''
+        let shareImg=''
+        const shareTime=moment(res.data.report_info.publish_time).format('MMDD')
+        if(['晨报','周报'].includes(res.data.report_info.classify_name_first)){
+            shareTitle=`【${res.data.report_info.classify_name_first}】${res.data.report_info.title}(${shareTime})`
+        }else{
+            // 专栏类报告
+            if(res.data.report_show_type==2){
+                const abstract=res.data.report_info.abstract?`:${res.data.report_info.abstract}`:''
+                if(res.data.report_info.classify_name_second==res.data.report_info.title){
+                    shareTitle=`${res.data.report_info.title}(${shareTime})${abstract}`
+                }else{
+                    shareTitle=`【${res.data.report_info.classify_name_second}】${res.data.report_info.title}(${shareTime})`
+                }
+            }else{
+                shareTitle=`【${res.data.report_info.classify_name_second}】${res.data.report_info.title}(${shareTime})`
+            }
+        }
+        // 日度点评
+        if(res.data.report_info.classify_name_first==='日度点评'){
+            shareTitle=res.data.report_info.abstract||'FICC日度点评'
+            const rddpImgRes=await apiRddpShareImg({
+                title:`【第${res.data.report_info.stage}期】${res.data.report_info.title}(${shareTime})`
+            })
+            if(rddpImgRes.code===200){
+                shareImg=rddpImgRes.data
+            }
+        }
         const postData={
             path:'/pages-report/reportDetail',
             params:{
                 reportId:reportId.value
             },
-            title:`FICC【${res.data.report_info.classify_name_first}】`,
-            shareImg:''
+            title:shareTitle,
+            shareImg:shareImg
         }
         wx.miniProgram.postMessage({ data: postData })
         
@@ -449,18 +478,6 @@ const formatTitle=(e)=>{
             <div style="margin-bottom:10px">4、在任何情况下,本公司不对客户/接受人/接受机构因使用报告中内容所引致的一切损失负责任,客户/接受人/接受机构需自行承担全部风险。</div>
         </div>
     </el-dialog>
-    <!-- 生成海报 -->
-    <Teleport to="body">
-        <SharePoster  
-            :shareData="{
-                type:'report_detail',
-                code_page:'pages-report/reportDetail',
-                code_scene:code_scene,
-                data:posterParams
-            }"
-            v-if="info&&info.auth_ok"
-        ></SharePoster>
-    </Teleport>
 </template>
 
 <style lang="scss" scoped>
@@ -528,6 +545,9 @@ const formatTitle=(e)=>{
                 font-size: 18px !important;
                 line-height: 1.8 !important;
             }
+            :deep(iframe){
+                width: 100% !important;
+            }
         }
     }
     .no-auth-wrap{

+ 608 - 0
src/views/sandBox/List.vue

@@ -0,0 +1,608 @@
+<template>
+    <template v-if="sandBox.noAuthor ==1 && sandBox.isRequestFinished">
+        <div class="sandbox-noAuthor">
+            <img :src="$store.state.globalImgUrls.activityNoAuth" />
+            <span style="margin-top: 34px;">您在暂无权限查看沙盘推演</span>
+            <span style="margin-top: 10px;">若想查看可以申请开通</span>
+            <div class="apply-button" @click="toApply">立即申请</div>
+            <!-- <template v-else>
+                <img :src="$store.state.globalImgUrls.activityNoAuth" />
+                <span style="margin-top: 34px;">您已提交过申请</span>
+                <span style="margin-top: 10px;">请等待销售人员与您联系</span>
+                <div class="apply-button" @click="$router.back()">返回</div>
+            </template> -->
+        </div>
+    </template>
+    <template v-if="sandBox.noAuthor ==2 && sandBox.isRequestFinished">
+        <view class="sandbox-noAuthor">
+            <img :src="$store.state.globalImgUrls.activityNoAuth" />
+            <span style="margin-top: 34px;">您在暂无权限查看沙盘推演</span>
+            <span style="margin: 10px 0 4px 0;">若想查看请联系对口销售</span>
+            <div>
+                <span>{{sandBox.salesData.name}}:</span>
+                <span class="sales-mobile" @click="callToSales">{{sandBox.salesData.phone}}</span>
+            </div>
+        </view>
+    </template>
+    <div class="sandBox-contain" v-if="sandBox.noAuthor ==0 && sandBox.isRequestFinished">
+        <div class="classify-box">
+            <div class="firstClassify-box">
+                <div :class="['firstClassfy-item',item.id==sandBox.selectedFirstId?'firstClassfy-actice':'']" 
+                v-for="item in sandBox.classifyList" :key="item.id" @click="selectFirstClassify(item)">
+                    <span>{{item.classify_name}}</span>
+                </div>
+            </div>
+            <div class="sec-nav-box">
+                <span
+                    :class="['sec-item',item.chart_permission_id==sandBox.selectedSecondId?'active':'']"
+                    v-for="item in sandBox.secondClassifyList.slice(0,6)"
+                    :key="item.chart_permission_id"
+                    @click="selectSecondClassify(item)"
+                >{{item.chart_permission_name}}</span>
+                <el-popover
+                    :width="500"
+                    trigger="click"
+                >
+                    <template #reference>
+                        <img v-if="sandBox.secondClassifyList.length>6" style="width:16px;transform: rotate(90deg);cursor: pointer" src="@/assets/icon-more.png" alt="">
+                    </template>
+                    <template #default>
+                        <div class="flex top-nav-filter-box">
+                            <div 
+                                :class="['item',item.chart_permission_id == sandBox.selectedSecondId&&'active']" 
+                                v-for="item in sandBox.secondClassifyList.slice(6)" 
+                                :key="item.chart_permission_id"
+                                @click="selectSecondClassify(item)"
+                            >{{item.chart_permission_name}}</div>
+                        </div>
+                    </template>
+                </el-popover>
+            </div>
+        </div>
+        <div class="sandBox-noData" v-if="!sandBox.list">
+            <img :src="$store.state.globalImgUrls.activityNoAuth" alt="暂无数据" />
+            <span>暂无数据</span>
+        </div>
+        <div class="sandBox-content">
+            <div class="sandBox-item" v-for="(item,index) in sandBox.list" :key="item.sandbox_id">
+                <span>{{item.name}}</span>
+                <div class="sandBox-image">
+                    <img :src="item.pic_url" :alt="item.name" @click="previewImage(index)"/>
+                </div>
+            </div>
+            <template v-if="sandBox.list">
+                <div class="loading-more" @click="loadingMore" v-show="sandBox.isRequestFinished && sandBox.list.length < sandBox.total">加载更多</div>
+                <div class="more-loading" v-show="!sandBox.isRequestFinished">加载中······</div>
+                <div class="more-loading no-more" v-show="sandBox.isRequestFinished && sandBox.list.length >= sandBox.total && sandBox.total!=0">没有更多了~</div>
+            </template>
+        </div>
+    </div>
+    
+    <!-- 图片预览 -->
+    <el-image-viewer
+        v-if="showPreViewImg"
+        :initial-index="preViewImgIndex"
+        @close="cancelPreview"
+        :url-list="previewImageList"
+    />
+</template>
+<script setup>
+import {h, reactive,computed,ref,onMounted,onActivated} from 'vue'
+import {useRouter,onBeforeRouteUpdate,useRoute} from "vue-router"
+import {apiSandBoxPermission,apiSandBoxList,apiSandTableDetail} from "../../api/sandBox"
+import {apiReportIndexPageAuthList} from "../../api/report"
+import {apiApplyPermission} from "../../api/user"
+import { ElMessageBox } from 'element-plus'
+const router = useRouter()
+const route = useRoute()
+
+// -----------------------------------------------------数据
+let sandBox = reactive({
+    list:[],
+    classifyList:[],
+    // 请求参数
+    sandBoxParams:{
+        page_size:5,
+        curr_page:1,
+        chart_permission_id:0
+    },
+    // 沙盘图ID 手机小程序单个分享
+    sandbox_id:"",
+    total:0,
+    // 二级分类列表
+    secondClassifyList:[],
+    selectedFirstId:0,
+    selectedSecondId:0,
+    // 所有有权限的二级分类以及下面的沙盘图数
+    classifyDataList:[],
+    // 没有权限  1:立即申请 2:联系销售
+    noAuthor:0,
+    // 销售信息
+    salesData:{
+        phone:'',
+        name:''
+    },
+    // 申请权限时的用户信息
+    applyer:{
+        name:'',
+        company:'',
+        // 是否已经申请
+        hasApply:false,
+        // 状态
+        status:''
+    },
+    // 请求是否完成
+    isRequestFinished:false
+})
+// 是否预览图片
+let showPreViewImg = ref(false)
+// 预览图片索引
+let preViewImgIndex = ref(0)
+
+// ------------------------------------------------------计算属性
+// 预览图片的路径
+const previewImageList = computed(()=>{
+    return sandBox.list.map(item=> item.pic_url)
+})
+
+// ------------------------------------------------------方法
+// 获取沙盘图数据
+const getSandBoxList=()=>{
+    apiSandBoxList(sandBox.sandBoxParams).then(({data,code})=>{
+        // 403 无权限
+        if(code == 403){
+            sandBox.applyer.name = data.customer_info.name
+            sandBox.applyer.company = data.customer_info.company_name
+            sandBox.applyer.hasApply = data.customer_info.has_apply
+            sandBox.applyer.status = data.customer_info.status
+            if(data.type == 'apply'){
+                // 申请
+                sandBox.noAuthor =1
+            }else{
+                // 联系销售
+                sandBox.noAuthor =2
+                sandBox.salesData.name=data.name
+                sandBox.salesData.phone=data.mobile
+            }
+            return 
+        }
+        if(sandBox.sandBoxParams.curr_page == 1){
+            sandBox.list = data.list
+            sandBox.total = data.paging.totals
+        }else{
+            sandBox.list = [...sandBox.list, ...data.list]
+        }
+        sandBox.noAuthor=0
+    }).finally(()=>{
+        sandBox.isRequestFinished=true
+    })
+}
+
+// 获取单个沙盘图
+const getSandTableDetail=()=>{
+    sandBox.isRequestFinished=false
+    apiSandTableDetail({sandbox_id:sandBox.sandbox_id}).then(({data,code})=>{
+        // 403 无权限
+        if(code == 403){
+            sandBox.applyer.name = data.customer_info.name
+            sandBox.applyer.company = data.customer_info.company_name
+            sandBox.applyer.hasApply = data.customer_info.has_apply
+            sandBox.applyer.status = data.customer_info.status
+            if(data.type == 'apply'){
+                // 申请
+                sandBox.noAuthor =1
+            }else{
+                // 联系销售
+                sandBox.noAuthor =2
+                sandBox.salesData.name=data.name
+                sandBox.salesData.phone=data.mobile
+            }
+            return 
+        }
+        sandBox.list = [data]
+        sandBox.noAuthor=0
+    }).finally(()=>{
+        sandBox.isRequestFinished=true
+    })
+}
+
+//预览图片
+const previewImage=(i)=>{
+    preViewImgIndex.value = i
+    showPreViewImg.value=true
+    // 不让外层的页面可以滚动
+    $('body').css("overflow","hidden")
+}
+
+// 取消预览图片
+const cancelPreview = ()=>{
+    // 恢复外层的页面滚动
+    $('body').css("overflow","visible")
+    showPreViewImg.value=false
+
+}
+
+//加载更多
+const loadingMore = ()=>{
+    sandBox.sandBoxParams.curr_page++
+    getSandBoxList()
+}
+
+//联系销售
+const callToSales=()=>{
+    if(!sandBox.applyer.hasApply){
+        toApply('auto')
+    }
+    window.location.href=`tel:${sandBox.salesData.phone}`
+}
+
+//申请权限
+const toApply = (type='')=>{
+    if(type=='auto'){
+        apiApplyPermission({
+            company_name:sandBox.applyer.company,
+            real_name:sandBox.applyer.name,
+            source:7,
+            from_page:'沙盘推演',
+        }).then(res=>{
+            console.log('自动申请成功');
+        })
+    }else{
+        if(sandBox.applyer.hasApply){
+            ElMessageBox({
+                title:'温馨提醒',
+                message:h('p','您已提交过申请,请耐心等待'),
+                center: true,
+                dangerouslyUseHTMLString: true,
+                confirmButtonText:'知道了',
+                confirmButtonClass:'self-elmessage-confirm-btn'
+            })
+            return 
+        }
+        if(sandBox.applyer.status==='流失'){
+            apiApplyPermission({
+                company_name:sandBox.applyer.company,
+                real_name:sandBox.applyer.name,
+                source:7,
+                from_page:'沙盘推演',
+            }).then(res=>{
+                sandBox.applyer.hasApply=true
+                const htmlStr = `<p>申请已提交</p><p>请等待销售人员与您联系</p>`;
+                ElMessageBox({
+                    title: "温馨提醒",
+                    message: htmlStr,
+                    center: true,
+                    dangerouslyUseHTMLString: true,
+                    confirmButtonText: "知道了",
+                    confirmButtonClass: "self-elmessage-confirm-btn",
+                });
+            })
+        }else{
+            router.push({
+                path:'/apply/permission',
+                query:{
+                    source:7,
+                    fromPage:'沙盘推演',
+                    token:localStorage.getItem('token')||''
+                }
+            })
+        }
+    }
+}
+
+// 获取分类列表 过滤无权限的分类
+const getClassifyList= ()=>{
+    apiReportIndexPageAuthList().then( async ({data:{permission_list}})=>{
+        // 过滤掉'更多报告' 和 没有权限的分类
+        sandBox.classifyList = permission_list.filter(item => item.sort!=100000)
+        sandBox.classifyList.forEach(item=>{
+            item.list=item.list.filter(it=>it.auth_ok)
+        })
+        sandBox.classifyList=sandBox.classifyList.filter(item=>item.list.length!=0)
+        let classifyData= await apiSandBoxPermission()
+         sandBox.classifyDataList = classifyData.data
+        try {
+            if(sandBox.selectedFirstId == 0){
+                // 一级分类ID 和 二级分类ID 没有,不是通过分享进来的,或者路由参数中没有一级Id和二级Id
+                // 找出第一个有数据的二级分类
+                U:for (const item of sandBox.classifyList) {
+                    for (const it of item.list) {
+                        for (const classify of sandBox.classifyDataList) {
+                            if(classify.chart_permission_id==it.chart_permission_id){
+                                if(classify.total>0){
+                                    sandBox.selectedFirstId = item.id
+                                    sandBox.secondClassifyList = item.list
+                                    sandBox.selectedSecondId = it.chart_permission_id
+                                    break U
+                                }
+                                break
+                            }
+                        }
+                    }
+                }
+                sandBox.sandBoxParams.chart_permission_id = sandBox.selectedSecondId
+            }else{
+                for (const item of sandBox.classifyList) {
+                    if(item.id == sandBox.selectedFirstId){
+                        // 根据selectedFirstId找出需要展示的二级分类列表
+                        sandBox.secondClassifyList = item.list
+                        break
+                    }
+                }
+            }
+        } catch (error) {
+            console.log(error);
+        }
+
+        if(sandBox.sandbox_id){
+            getSandTableDetail()
+        }else{
+            getSandBoxList()
+        }
+    })
+}
+// 切换一级分类
+const selectFirstClassify = (item)=>{
+    if(sandBox.selectedFirstId === item.id) return 
+    sandBox.selectedFirstId = item.id
+    sandBox.secondClassifyList = item.list
+    sandBox.selectedSecondId = item.list[0].chart_permission_id
+    U:for (const it of item.list) {
+        for (const classify of sandBox.classifyDataList) {
+            if(classify.chart_permission_id==it.chart_permission_id){
+                if(classify.total>0){
+                    sandBox.selectedSecondId = it.chart_permission_id
+                    break U
+                }
+                break
+            }
+        }
+    }
+    sandBox.sandBoxParams.chart_permission_id = sandBox.selectedSecondId
+    sandBox.sandBoxParams.curr_page=1
+    sandBox.list=[]
+    getSandBoxList()
+}
+// 切换二级分类
+const selectSecondClassify = (item)=>{
+    console.log(item);
+    if(sandBox.selectedSecondId == item.chart_permission_id) return 
+    sandBox.sandBoxParams.chart_permission_id = sandBox.selectedSecondId = item.chart_permission_id
+    sandBox.sandBoxParams.curr_page=1
+    sandBox.list=[]
+    getSandBoxList()
+}
+
+// ---------------------------------------------------------created
+// 路由参数  分享
+// 需要是数字类型
+sandBox.sandBoxParams.chart_permission_id = sandBox.selectedSecondId = parseInt(route.query.chart_permission_id)  || 0 //二级分类ID 分享
+sandBox.selectedFirstId = route.query.firstClassifyId || 0 //一级分类ID 分享
+sandBox.sandbox_id = route.query.sandbox_id // 沙盘图ID 
+getClassifyList()
+
+onMounted(() => {
+  //向小程序发送消息
+  let postData = {
+    path: "/pages-sandTable/sandTable",
+    params:{
+        firstClassifyId:sandBox.selectedFirstId,
+        chart_permission_id:sandBox.selectedSecondId
+    },
+    title: "沙盘推演",
+    shareImg:''
+  };
+  wx.miniProgram.postMessage({ data: postData });
+});
+
+onActivated(()=>{
+    //向小程序发送消息
+    let postData = {
+        path: "/pages-sandTable/sandTable",
+        params:{
+            firstClassifyId:sandBox.selectedFirstId,
+            chart_permission_id:sandBox.selectedSecondId
+        },
+        title: "沙盘推演",
+        shareImg:''
+    };
+    wx.miniProgram.postMessage({ data: postData });
+})
+
+</script>
+<style lang="scss" scoped>
+.top-nav-filter-box{
+    flex-wrap: wrap;
+    .item{
+        margin: 5px 10px;
+        width: 113px;
+        height: 40px;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        background-color: #f6f6f6;
+        border-radius: 4px;
+        font-size: 16px;
+        cursor: pointer;
+        :hover{
+            color: #fff;
+            background-color: #F3A52F;
+        }
+    }
+    .active{
+        color: #fff;
+        background-color: #F3A52F;
+    }
+}
+    .sandbox-noAuthor{
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        margin-top: 50px;
+        img{
+            height: 360px;
+        }
+        span{
+            font-family: 'PingFang SC';
+            font-style: normal;
+            font-weight: 400;
+            font-size: 28rpx;
+            color: #999999;
+        }
+        .sales-mobile{
+            color: #E3B377;
+            cursor: pointer;
+        }
+        .apply-button{
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            background: #E6B77D;
+            width: 218px;
+            border: none;
+            height: 40px;
+            border-radius: 41px;
+            margin-top: 30px;
+            font-family: 'PingFang SC';
+            font-style: normal;
+            font-weight: 500;
+            font-size: 16px;
+            color: #FFFFFF;
+            cursor: pointer;
+        }
+    }
+    .sandBox-contain{
+        margin-top: 130px;
+        .classify-box{
+            padding: 30px 30px 12px 30px;
+            background: #FFFFFF;
+            box-shadow: 0px 4px 8px 1px rgba(0, 0, 0, 0.04);
+            position: fixed;
+            top: 60px;
+            width: 100%;
+            max-width: 1240px;
+            z-index: 10;
+            .firstClassify-box{
+                .firstClassfy-item{
+                    display: inline-block;
+                    width: 140px;
+                    line-height: 40px;
+                    text-align: center;
+                    border-radius: 20px;
+                    background-color: #F6F6F6;
+                    font-size: 16px;
+                    margin-right: 30px;
+                    cursor: pointer;
+                    span{
+                        font-family: 'PingFang SC';
+                        font-style: normal;
+                        font-weight: 400;
+                        font-size: 16px;
+                        color: #333333;
+                    }
+                }
+                .firstClassfy-actice{
+                    background: #FFFBF5;
+                    border: 1px solid #F3A52F;
+                    box-shadow: 0px 6px 7px #FFF7EB;
+                    border-radius: 20px;
+                    span{
+                        color: #F3A52F;
+                    }
+                }
+            }
+            .sec-nav-box{
+                margin-top: 30px;
+                .sec-item{
+                    color: #666;
+                    margin-right: 30px;
+                    cursor: pointer;
+                    display: inline-block;
+                }
+                .active{
+                    color: #F3A52F;
+                }
+            }
+        }
+        .sandBox-noData{
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+            justify-content: center;
+            margin-top: 50px;
+            img{
+                height: 360px;
+            }
+            span{
+                margin-top: 30px;
+                font-family: 'PingFang SC';
+                font-style: normal;
+                font-weight: 400;
+                font-size: 16px;
+                color: #333333;
+            }
+        }
+        .sandBox-content{
+            .sandBox-item{
+                padding: 30px 0;
+                position: relative;
+                span{
+                    font-family: 'PingFang SC';
+                    font-style: normal;
+                    font-weight: 600;
+                    font-size: 16px;
+                    color: #333333;
+                }
+                .sandBox-image{
+                    margin-top: 20px;
+                    width: 100%;
+                    // max-height: 558px;
+                    border: 2px solid #ECECEC;
+                    overflow: auto;
+                    padding-bottom: 24px;
+                    display:flex;
+                    justify-content:center;
+                    align-items:center;
+                    img{
+                        cursor: pointer;
+                        max-width:100%;
+                    }
+                    .sandBox-source{
+                        font-size: 20px;
+                        font-family: PingFang SC-Regular, PingFang SC;
+                        font-weight: 400;
+                        color: #666666;
+                        -webkit-background-clip: text;
+                        background-clip: text;
+                        position: absolute;
+                        bottom: 54px;
+                        right: 30px;
+                    }
+                }
+            }
+            .loading-more{
+                margin: 20px auto;  
+                width: 112px;
+                height: 30px;
+                background: #FFFFFF;
+                border-radius: 20px;
+                border: 1px solid #F3A52F;
+                color: #F3A52F;
+                font-size: 14px;
+                text-align: center;
+                display: flex;
+                justify-content: center;
+                align-items: center;
+                cursor: pointer;
+            }
+            .more-loading{
+                margin: 20px auto;  
+                width: 112px;
+                height: 30px;
+                font-size: 16px;
+                font-family: 'PingFang SC';
+                color: #666666;
+            }
+        }
+    }
+</style>

+ 337 - 0
src/views/video/List.vue

@@ -0,0 +1,337 @@
+<script setup>
+import {ref,reactive,onMounted,onActivated} from 'vue'
+import { useElementSize } from '@vueuse/core'
+
+import {apiFICCPermissionList,apiGetWechatQRCode} from '@/api/common'
+import {apiVideoList,apiVideoPlayLog} from '@/api/video'
+
+import SelfList from '@/components/SelfList.vue'
+
+
+//监听列表页面版心宽度
+const listPageEl=ref('')
+const {width}=useElementSize(listPageEl)
+
+// 获取品种权限数据
+let permissionState=reactive({
+    firstNavList:[],
+    sFirst:'',
+    secNavList:[],
+    sSecond:0//选择的品种id 
+})
+const getFICCPermissionList=async ()=>{
+    const res=await apiFICCPermissionList()
+    if(res.code===200){
+        permissionState.firstNavList=res.data
+        handelChangeFirstNav(res.data[0])
+    }
+}
+getFICCPermissionList()
+
+//点击一级分类
+const handelChangeFirstNav=(item)=>{
+    permissionState.sFirst=item.ClassifyName
+    permissionState.secNavList=item.Items
+    handleChangeSecNav(item.Items[0])
+}
+
+//点击二级分类
+const handleChangeSecNav=(item)=>{
+    permissionState.sSecond=item.PermissionId
+    refreshList()
+}
+
+//获取视频列表数据
+let listState = reactive({
+    loading:false,
+    finished:false,
+    page:1,
+    list:[],
+})
+const getVideoList=async ()=>{
+    listState.loading=true
+    const res=await apiVideoList({
+        page_index:Number(listState.page),
+        page_size:20,
+        video_id:0,
+        chart_permission_id:Number(permissionState.sSecond)
+    })
+    listState.loading=false
+    if(res.code===200){
+        let arr=res.data||[]
+        listState.list=[...listState.list,...arr]
+        if(arr.length===0||arr.length<20){
+            listState.finished=true
+        }
+    }
+}
+// getVideoList()
+
+//刷新列表
+const refreshList=()=>{
+    listState.finished=false
+    listState.page=1
+    listState.list=[]
+    curVideoId.value=0
+    getVideoList()
+}
+
+// 加载下一页
+const onLoad=()=>{
+    listState.page++
+    getVideoList()
+}
+
+//当前正在播放哪个
+let curVideoId=ref(0)
+const handelClickPlay=(item)=>{
+    curVideoId.value=item.community_video_id
+    //记录播放
+    apiVideoPlayLog({video_id:Number(item.community_video_id)}).then(res=>{
+        if(res.code===200){
+            console.log('视频埋点成功');
+        }
+    })
+}
+
+
+//获取视频单个对应的小程序二维码
+const handelGetQRCodeImg=async (item)=>{
+    const res=await apiGetWechatQRCode({
+        CodeScene:JSON.stringify({videoId:item.community_video_id}),
+        CodePage:'pages/video/videoList'
+    })
+    if(res.code===200){
+        item.QRCodeImg=res.data
+    }
+}
+
+onMounted(() => {
+  //向小程序发送消息
+  let postData = {
+    path: "/pages/video/videoList",
+    params:{},
+    title: "FICC视频社区",
+    shareImg:''
+  };
+  wx.miniProgram.postMessage({ data: postData });
+});
+
+onActivated(()=>{
+    curVideoId.value=0
+    //向小程序发送消息
+    let postData = {
+        path: "/pages/video/videoList",
+        params:{},
+        title: "FICC视频社区",
+        shareImg:''
+    };
+    wx.miniProgram.postMessage({ data: postData });
+})
+
+</script>
+
+<template>
+    <div class="video-list-page" ref="listPageEl">
+        <div class="top-nav-box" :style="{width:width+'px'}">
+            <div class="first-nav-box">
+                <span 
+                    v-for="item in permissionState.firstNavList" 
+                    :key="item.ClassifyName"
+                    :class="item.ClassifyName==permissionState.sFirst?'active':''"
+                    @click="handelChangeFirstNav(item)"
+                >{{item.ClassifyName}}</span>
+            </div>
+            <div class="sec-nav-box">
+                <span
+                    :class="['sec-item',item.PermissionId==permissionState.sSecond?'active':'']"
+                    v-for="item in permissionState.secNavList.slice(0,6)"
+                    :key="item.PermissionId"
+                    @click="handleChangeSecNav(item)"
+                >{{item.PermissionName}}</span>
+                <el-popover
+                    :width="500"
+                    trigger="click"
+                >
+                    <template #reference>
+                        <img v-if="permissionState.secNavList.length>6" style="width:16px;transform: rotate(90deg);cursor: pointer" src="@/assets/icon-more.png" alt="">
+                    </template>
+                    <template #default>
+                        <div class="flex top-nav-filter-box">
+                            <div 
+                                :class="['item',item.PermissionId == permissionState.sSecond&&'active']" 
+                                v-for="item in permissionState.secNavList.slice(6)" 
+                                :key="item.PermissionId"
+                                @click="handleChangeSecNav(item)"
+                            >{{item.PermissionName}}</div>
+                        </div>
+                    </template>
+                </el-popover>
+            </div>
+        </div>
+        <SelfList 
+            :finished="listState.finished" 
+            :isEmpty="listState.list.length===0&&listState.finished"
+            :loading="listState.loading"
+            @listOnload="onLoad"
+        >
+            <div class="flex list-wrap">
+                <div class="video-item" v-for="item in listState.list" :key="item.community_video_id">
+                    <el-popover
+                        :width="200"
+                        trigger="click"
+                    >
+                        <template #reference>
+                            <div class="icon-wechat" @click="handelGetQRCodeImg(item)"></div>
+                        </template>
+                        <template #default>
+                            <img style="width:100%" :src="item.QRCodeImg" alt="">
+                        </template>
+                    </el-popover>
+                    <div class="title">{{item.title}}</div>
+                    <video 
+                        :src="item.video_url" 
+                        controls
+                        :poster="item.cover_img_url"
+                        controlslist="nodownload"
+                        disablePictureInPicture
+                        autoplay
+                        v-if="item.community_video_id==curVideoId"
+                        @ended="curVideoId=0"
+                    ></video>
+                    <div v-else class="poster-img" :style="'background-image:url('+item.cover_img_url+')'" @click="handelClickPlay(item)"></div>
+                    <div class="time">发布时间:{{item.publish_time}}</div>
+                </div>
+            </div>
+        </SelfList>
+    </div>
+</template>
+
+<style lang="scss" scoped>
+.icon-wechat{
+    cursor: pointer;
+    width: 24px;
+    height: 24px;
+    background-image: url('@/assets/icon-wechat.png');
+    background-size: cover;
+    position: absolute;
+    top: 30px;
+    right: 30px;
+}
+.top-nav-filter-box{
+    flex-wrap: wrap;
+    .item{
+        margin: 5px 10px;
+        width: 113px;
+        height: 40px;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        background-color: #f6f6f6;
+        border-radius: 4px;
+        font-size: 16px;
+        cursor: pointer;
+        :hover{
+            color: #fff;
+            background-color: #F3A52F;
+        }
+    }
+    .active{
+        color: #fff;
+        background-color: #F3A52F;
+    }
+}
+.video-list-page{
+    .top-nav-box{
+        padding: 30px 30px 12px 30px;
+        background: #FFFFFF;
+        box-shadow: 0px 4px 8px 1px rgba(0, 0, 0, 0.04);
+        position: fixed;
+        top: 60px;
+        width: 100%;
+        max-width: 1240px;
+        z-index: 10;
+        .first-nav-box{
+            span{
+                display: inline-block;
+                width: 140px;
+                line-height: 40px;
+                text-align: center;
+                border-radius: 20px;
+                background-color: #F6F6F6;
+                font-size: 16px;
+                margin-right: 30px;
+                cursor: pointer;
+            }
+            .active{
+                background-color: #FFFBF5;
+                box-shadow: 0px 6px 7px 1px #FFF7EB;
+                color: #F3A52F;
+                border: 1px solid #F3A52F;
+            }
+        }
+        .sec-nav-box{
+            margin-top: 30px;
+            .sec-item{
+                color: #666;
+                margin-right: 30px;
+                cursor: pointer;
+                display: inline-block;
+            }
+            .active{
+                color: #F3A52F;
+            }
+        }
+    }
+
+    .list-wrap{
+        padding-top: 150px;
+        border-left: 1px solid #E2E2E2;
+        border-right: 1px solid #E2E2E2;
+        flex-wrap: wrap;
+        .video-item{
+            width: 50%;
+            padding: 30px;
+            border-bottom: 1px solid #E2E2E2;
+            position: relative;
+            &:nth-of-type(odd){
+                border-right: 1px solid #E2E2E2;
+            }
+            .title{
+                font-size: 16px;
+                color: #666;
+            }
+            video{
+                width: 100%;
+                height: 200px;
+                object-fit: contain;
+                margin: 19px 0;
+            }
+            .poster-img{
+                width: 100%;
+                height: 200px;
+                margin: 19px 0;
+                position: relative;
+                background-size: cover;
+                cursor: pointer;
+                &::after{
+                    content:'';
+                    display: block;
+                    position: absolute;
+                    width: 80px;
+                    height: 80px;
+                    top: 50%;
+                    left: 50%;
+                    transform: translate(-50%,-50%);
+                    background-image: url('@/assets/video-play-btn.png');
+                    background-size: cover;
+                }
+            }
+            .time{
+                color: #999;
+                font-size: 14px;
+            }
+        }
+    }
+}
+</style>