任一存 2 роки тому
батько
коміт
eb334a6c2d

+ 4 - 1
README.md

@@ -1,4 +1,7 @@
+## 最初的测试环境(oss)
+https://culture.4dage.com/demo/ShangHaiGongYe/index.html#/history
+
 ## 部署测试环境
 文件存放位置:
 
-访问url:
+访问url:https://sit-shgybwg.4dage.com/web/index.html#/general

+ 281 - 0
public/chart4app.html

@@ -0,0 +1,281 @@
+<!DOCTYPE html>
+<html lang="zh-CN" style="height: 100%">
+<head>
+  <meta charset="utf-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <title>chart</title>
+</head>
+
+<style>
+  *{
+    margin: 0;
+    padding: 0;
+    box-sizing: border-box;
+  }
+  body {
+    background-color: #04060a;
+  }
+</style>
+
+<body style="height: 100%; margin: 0">
+  <div id="container" style="height: 100%"></div>
+  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
+  <script type="text/javascript" src="https://cdn.staticfile.org/echarts/5.4.2/echarts.min.js"></script>
+  <script type="text/javascript">
+    const testData = [
+      {
+        name: '开埠通商',
+        desc: '开埠通商的描述开埠通商的描述开埠通商的描述开埠通商的描述开埠通商的描述开埠通商的描述开埠通商的描述',
+        corps: [
+          {
+            name: '李鸿章',
+            brand: '创办江南制造局、上海机器织布局。',
+            desc: '',
+            story: '江南制造总局:中国民族工业从此起步;上海机器织布局:开创中国近代纺织业之先河。',
+            importance: 30,
+            id: '1'
+          },
+          {
+            name: '徐寿',
+            brand: '上海江南制造总局',
+            desc: '清代著名科学家,上海江南制造总局襄办。',
+            story: '',
+            importance: 40,
+            id: '2'
+          },
+          {
+            name: '刘永康',
+            brand: '正泰橡皮物品制造厂',
+            desc: '创办正泰橡皮物品制造厂,首创“回力”商标。',
+            story: '',
+            importance: 50,
+            id: '3'
+          },
+        ]
+      },
+      {
+        name: '曲折发展',
+        desc: '曲折发展的描述曲折发展的描述曲折发展的描述曲折发展的描述曲折发展的描述曲折发展的描述曲折发展的描述曲折发展的描述',
+        corps: [
+          {
+            name: '黄佐卿',
+            brand: '裕晋纱厂',
+            desc: '1895年投资的裕晋纱厂在上海杨树浦路落成并开工投产',
+            story: '',
+            importance: 30,
+            id: '1'
+          },
+          {
+            name: '孙多森、孙多鑫',
+            brand: '阜丰面粉厂',
+            desc: '1900年集资创办上海第一家民族机器面粉厂“阜丰面粉厂”',
+            story: '孙氏通、孚、丰集团:官办企业转化为民族资本企业的典型。',
+            importance: 30,
+            id: '2'
+          },
+          {
+            name: '严裕棠',
+            brand: '大隆机器厂',
+            desc: '1902年开办的大隆机器厂',
+            story: '',
+            importance: 30,
+            id: '3'
+          },
+          {
+            name: '张士德、刘致祥',
+            brand: '中国精益眼镜公司',
+            desc: '1911年集资开设中国精益眼镜公司',
+            story: '',
+            importance: 30,
+            id: '4'
+          },
+          {
+            name: '荣宗敬、荣德生',
+            brand: '第一家面粉厂、申新纺织公司',
+            desc: '第一家面粉厂,1915年,荣氏兄弟又创建申新纺织公司,人称“棉纱大王”。1919年至1921年,又发起和参与了上海华商面粉交易所、纱布交易所,成为上海民族工业的重要台柱之一。',
+            story: '荣氏企业:上海民族工业的重要台柱之一。',
+            importance: 30,
+            id: '5'
+          },
+        ]
+      },
+    ]
+
+    function randomColor() {
+      return '#' + Math.floor(
+          (
+            Math.random() * (1 - 0.3) + 0.3
+          ) * 0xffffff
+        ).toString(16)
+    }
+    
+    const dataForRender = {}
+
+    let myChart = null
+    
+    var dom = document.getElementById('container');
+    myChart = echarts.init(dom, null, {
+      renderer: 'canvas',
+      useDirtyRect: false
+    });
+    
+    function showChart(timeIdx) {
+      myChart.clear()
+      
+      dataForRender.nodes = [
+        {
+          name: testData[timeIdx].name,
+          id: '0',
+          symbolSize: 100,
+          itemStyle: {
+            color: randomColor()
+          },
+        },
+      ]
+      dataForRender.edges = []
+
+      for (const iterator of testData[timeIdx].corps) {
+        const newNode = {
+          name: iterator.brand,
+          id: iterator.id,
+          symbolSize: iterator.importance,
+          itemStyle: {
+            color: randomColor()
+          }
+        }
+        dataForRender.nodes.push(newNode)
+      }
+      // 除了代表时代的那个节点,其他节点的尺寸归一化到[10, 70]
+      let vMax = Number.NEGATIVE_INFINITY
+      let vMin = Number.POSITIVE_INFINITY
+      const vMaxNew = 50
+      const vMinNew = 30
+      for (let index = 1; index < dataForRender.nodes.length; index++) {
+        const size = dataForRender.nodes[index].symbolSize
+        if (size > vMax) {
+          vMax = size
+        }
+        if (size < vMin) {
+          vMin = size
+        }
+      }
+      let scale = null
+      if (vMax === vMin) {
+        scale = 1
+        for (const iterator of dataForRender.nodes.slice(1)) {
+          iterator.symbolSize = vMinNew + (vMaxNew - vMinNew) / 2
+        }
+      } else {
+        scale = (vMaxNew - vMinNew) / (vMax - vMin)
+        for (const iterator of dataForRender.nodes.slice(1)) {
+          iterator.symbolSize = vMinNew + (iterator.symbolSize - vMin) * scale
+        }
+      }
+      // console.log(dataForRender.nodes);
+      for (let i = 0; i < dataForRender.nodes.length; i++) {
+        for (let j = i + 1; j < dataForRender.nodes.length; j++) {
+          const hasCenterNode = (i === 0 || j === 0)
+          const newEdge = {
+            source: dataForRender.nodes[i].id,
+            target: dataForRender.nodes[j].id,
+            value: dataForRender.nodes[i].symbolSize * dataForRender.nodes[j].symbolSize * (hasCenterNode ? 10 : 1), // 值越大,连接的两个节点间斥力越小。
+          }
+          dataForRender.edges.push(newEdge)
+        }
+      }
+      // console.log(dataForRender);
+      
+      myChart.setOption({
+        animationDurationUpdate: 1500,
+        animationEasingUpdate: 'quinticInOut',
+        series: [
+          {
+            type: 'graph',
+            layout: 'force',
+            draggable: false,
+            // 力引导布局是模拟弹簧电荷模型在每两个节点之间添加一个斥力,每条边的两个节点之间添加一个引力
+            force: {
+              initLayout: 'circular', // 进行力引导布局前的初始化布局,初始化布局会影响到力引导的效果。默认不进行任何布局,使用节点中提供的 x, y 作为节点的位置。如果不存在的话会随机生成一个位置。也可以选择使用环形布局 'circular'。
+              repulsion: 100, // 节点之间的斥力因子。傻逼文档把edgeLength当数组用的用法写到这上边了。
+              gravity: 0.1, // 节点受到的向中心的引力因子。该值越大节点越往中心点靠拢。
+              edgeLength: [50, 400], // 把各个边的两个节点之间的距离归一化到这个范围内。与repulsion共同作用。
+              layoutAnimation: true,
+              friction: 0.1, // 这个参数能减缓节点的移动速度。取值范围 0 到 1。但是仍然是个试验性的参数,参见 #11024。
+            },
+            data: dataForRender.nodes,
+            // 或者叫edges
+            links: dataForRender.edges,
+            selectedMode: 'single',
+            select: {
+              itemStyle: {
+                shadowBlur: 50,
+                shadowColor: 'rgba(255, 255, 125, 0.7)',
+              },
+              label: {
+                position: 'right',
+                show: true,
+              }
+            },
+            // 高亮状态的图形样式
+            emphasis: {
+              scale: false,
+              label: {
+                position: 'right',
+                show: true
+              }
+            },
+            // 图表是否可以移动、缩放
+            roam: true,
+            lineStyle: {
+              width: 0.5,
+              curveness: 0.3,
+              opacity: 0.7,
+            }
+          }
+        ]
+      }, true)
+
+      setTimeout(() => {
+        // 等myChart上注册了select回调后再执行
+        // 一开始自动选中0号节点,也就是表示时代的那个
+        myChart.dispatchAction({
+          type: 'select',
+          seriesIndex: 0,
+          name: testData[timeIdx].name,
+        })
+      }, 0);
+    }
+
+    showChart(0)
+
+    // 用户选中节点后,向父窗口post message
+    function onSelect(params) {
+      if (params.dataType === 'node') { // 用户选中节点触发的
+        if (window.vuplex) {
+          window.vuplex.postMessage({ type: 'select-id', message: dataForRender.nodes[params.dataIndexInside].id });
+        } else {
+          console.error('不存在window.vuplex!');
+        }
+      } else if (params.type === 'select') { // 程序里调用dispatchAction触发的,且type为select
+        if (window.vuplex) {
+          window.vuplex.postMessage({ type: 'select-id', message: '0' });
+        } else {
+          console.error('不存在window.vuplex!');
+        }
+      }
+    }
+    myChart.on('select', onSelect)
+
+    window.addEventListener('resize', myChart.resize);
+
+    // 切换时代
+    window.changeTime = function (idx) {
+      if (Number.isInteger(idx) && idx >= 0) {
+        showChart(idx)
+      } else {
+        console.error('[page using echart] changeTime: invalid param!', idx);
+      }
+    }
+  </script>
+</body>
+</html>

+ 64 - 0
public/record.html

@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+  <meta charset="utf-8">
+  <title>wgchen</title>
+  <link rel="shortcut icon" href="#" />
+  <meta name="keywords" content="https://wgchen.blog.csdn.net">
+  <meta name="keywords" content="技术文章">
+  <meta name="keywords" content="博客">
+  <meta name="keywords" content="willem">
+  <meta name="keywords" content="ycc">
+</head>
+
+<body>
+
+  <video class="video" width="600px" controls></video>
+  <button class="record-btn">record</button>
+
+  <script>
+    let btn = document.querySelector(".record-btn")
+    console.log(navigator);
+    console.log(navigator.mediaDevices);
+    btn.addEventListener("click", async function () {
+      let stream = await navigator.mediaDevices.getDisplayMedia({
+        video: true
+      })
+
+      // 需要更好的浏览器支持
+      const mime = MediaRecorder.isTypeSupported("video/webm; codecs=vp9") ?
+        "video/webm; codecs=vp9" :
+        "video/webm"
+      let mediaRecorder = new MediaRecorder(stream, {
+        mimeType: mime
+      })
+
+      let chunks = []
+      mediaRecorder.addEventListener('dataavailable', function (e) {
+        chunks.push(e.data)
+      })
+
+      mediaRecorder.addEventListener('stop', function () {
+        let blob = new Blob(chunks, {
+          type: chunks[0].type
+        })
+        let url = URL.createObjectURL(blob)
+
+        let video = document.querySelector("video")
+        video.src = url
+
+        let a = document.createElement('a')
+        a.href = url
+        a.download = 'video.webm'
+        a.click()
+      })
+
+      // 必须手动启动
+      mediaRecorder.start()
+    })
+  </script>
+
+</body>
+
+</html>

+ 6 - 4
src/App.vue

@@ -23,6 +23,7 @@
     <router-view />
 
     <nav
+      v-show="!$route.meta.hideNavBar"
       :style="{
         bottom: isShowNavBar ? '0' : '-112px',
       }"
@@ -30,7 +31,7 @@
       <button
         class="tab-item"
         :class="{
-          active: $route.name === 'GeneralView'
+          active: $route.meta.belongNavGroup === 0
         }"
         @click="$router.push({name: 'GeneralView'})"
       >
@@ -45,7 +46,7 @@
       <button
         class="tab-item"
         :class="{
-          active: $route.name === 'HistoryView'
+          active: $route.meta.belongNavGroup === 1
         }"
         @click="$router.push({name: 'HistoryView'})"
       >
@@ -60,7 +61,7 @@
       <button
         class="tab-item"
         :class="{
-          active: $route.name === 'TreasureView'
+          active: $route.meta.belongNavGroup === 2
         }"
         @click="$router.push({name: 'TreasureView'})"
       >
@@ -75,7 +76,7 @@
       <button
         class="tab-item"
         :class="{
-          active: $route.name === 'MetaverseView'
+          active: $route.meta.belongNavGroup === 3
         }"
         @click="$router.push({name: 'MetaverseView'})"
       >
@@ -89,6 +90,7 @@
       </button>
     </nav>
     <button
+      v-show="!$route.meta.hideNavBar"
       class="show-hide"
       :style="{
         backgroundImage: isShowNavBar ? `url(${require(`@/assets/images/arrow-down.png`)})` : `url(${require(`@/assets/images/arrow-up.png`)})`,

src/assets/images/icon_musicon_off.png → src/assets/images/icon_music_off.png


BIN
src/assets/mock/02.mp3


+ 23 - 0
src/router/index.js

@@ -3,6 +3,7 @@ import GeneralView from '../views/General.vue'
 import HistoryView from '../views/History.vue'
 import TreasureView from '../views/Treasure.vue'
 import TreasureDetail from '../views/TreasureDetail.vue'
+import RecordView from '../views/Record.vue'
 import MetaverseView from '../views/Metaverse.vue'
 import TestView from '../views/Test.vue'
 
@@ -16,27 +17,49 @@ const routes = [
     component: GeneralView,
     meta: {
       isShow3DMap: true,
+      belongNavGroup: 0,
     }
   },
   {
     path: '/history',
     name: 'HistoryView',
     component: HistoryView,
+    meta: {
+      belongNavGroup: 1,
+    }
   },
   {
     path: '/treasure',
     name: 'TreasureView',
     component: TreasureView,
+    meta: {
+      belongNavGroup: 2,
+    }
   },
   {
     path: '/Treasure-detail',
     name: 'TreasureDetail',
     component: TreasureDetail,
+    meta: {
+      belongNavGroup: 2,
+    }
+  },
+  {
+    path: '/record',
+    name: 'RecordView',
+    component: RecordView,
+    meta: {
+      hideNavBar: true,
+      belongNavGroup: 2,
+    }
   },
   {
     path: '/metaverse',
     name: 'MetaverseView',
     component: MetaverseView,
+    meta: {
+      belongNavGroup: 3,
+    }
   },
   {
     path: '/test',

+ 87 - 0
src/views/Record.vue

@@ -0,0 +1,87 @@
+<template>
+  <div class="record-root">
+    <!-- <iframe
+      :src="url"
+      frameborder="0"
+    /> -->
+    <button
+      v-show="!isRecording"
+      @click="beginRecord"
+    >
+      开始录制
+    </button>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'NameView',
+  setup() {
+    return {
+    }
+  },
+  data() {
+    return {
+      isRecording: false
+    }
+  },
+  computed: {
+    url() {
+      console.error(decodeURI(this.$route.query.url))
+      return decodeURI(this.$route.query.url)
+    }
+  },
+  methods: {
+    async beginRecord() {
+      this.isRecording = false
+
+      let stream = await navigator.mediaDevices.getDisplayMedia({
+        video: true
+      })
+
+      // 需要更好的浏览器支持
+      const mime = MediaRecorder.isTypeSupported("video/webm; codecs=vp9") ?
+        "video/webm; codecs=vp9" :
+        "video/webm"
+      let mediaRecorder = new MediaRecorder(stream, {
+        mimeType: mime
+      })
+
+      let chunks = []
+      mediaRecorder.addEventListener('dataavailable', function (e) {
+        chunks.push(e.data)
+      })
+
+      mediaRecorder.addEventListener('stop', function () {
+        let blob = new Blob(chunks, {
+          type: chunks[0].type
+        })
+        let url = URL.createObjectURL(blob)
+
+        let a = document.createElement('a')
+        a.href = url
+        a.download = 'xxxxx'
+        a.click()
+      })
+
+      // 必须手动启动
+      mediaRecorder.start()
+    }
+  }
+}
+</script>
+
+<style lang="less" scoped>
+.record-root {
+  position: relative;
+  width: 100%;
+  height: 100%;
+  >iframe {
+    position: absolute;
+    left: 0;
+    top: 0;
+    width: 100%;
+    height: 100%;
+  }
+}
+</style>

+ 50 - 7
src/views/TreasureDetail.vue

@@ -118,15 +118,21 @@
       </button>
       <button
         v-if="hasMusic"
+        @click="isMusicOn = !isMusicOn"
       >
         <img
           class=""
-          src="@/assets/images/icon_music_on.png"
+          :src="require(`@/assets/images/icon_music_${isMusicOn ? 'on' : 'off'}.png`)"
           alt=""
           draggable="false"
         >
       </button>
-      <button v-if="canCaptureScreen">
+      <button
+        v-if="canRecord"
+        @click="$router.push({name: 'RecordView', query: {
+          url: encodeURI(modelUlrList[activeSwiperItemIndex])
+        }})"
+      >
         <img
           class=""
           src="@/assets/images/icon_record.png"
@@ -143,6 +149,13 @@
         >
       </button>
     </menu>
+
+    <audio
+      v-if="hasMusic"
+      ref="bgAudio"
+      :src="musicUrl"
+      style="display: none;"
+    />
   </div>
 </template>
 
@@ -168,10 +181,30 @@ export default {
 
     const isShowShare = ref(false)
 
+    /**
+     * 背景音乐相关
+     */
+    const bgAudio = ref(null)
     const hasMusic = ref(true)
+    const musicUrl = ref(require('@/assets/mock/02.mp3'))
+    const isMusicOn = ref(false)
+    watch(isMusicOn, (newV) => {
+      console.log('change!', newV)
+      if (newV) {
+        bgAudio.value.play()
+      } else {
+        bgAudio.value.pause()
+      }
+    })
 
-    const canCaptureScreen = ref(true)
+    /**
+     * 录屏相关
+     */
+    const canRecord = ref(navigator?.mediaDevices?.getDisplayMedia)
 
+    /**
+     * 全屏相关
+     */
     const fullScreenStatus = ref(false)
     watch(fullScreenStatus, (newVal) => {
       if (newVal) {
@@ -187,7 +220,7 @@ export default {
     })
 
     /**
-     * swiper 相关
+     * 展示数据
      */
     const modelUlrList = reactive([
       'https://4dscene.4dage.com/culturalrelics/YHTLSJNG/Model2.html?m=yht08',
@@ -199,7 +232,12 @@ export default {
     ])
     const imageUlrList = reactive([
     ])
+
+    /**
+     * swiper 相关
+     */
     let swiper = null
+    const activeSwiperItemIndex = ref(0)
     function initSwiper() {
       swiper = new Swiper('.swiper-root', {
         // pagination: {
@@ -214,10 +252,10 @@ export default {
 
         on: {
           afterInit: function (e) {
-            // ...
+            activeSwiperItemIndex.value = e.activeIndex
           },
           slideChange: function(e) {
-            // ...
+            activeSwiperItemIndex.value = e.activeIndex
           }
         }
       })
@@ -235,15 +273,20 @@ export default {
 
       isShowShare,
 
+      bgAudio,
       hasMusic,
+      musicUrl,
+      isMusicOn,
 
-      canCaptureScreen,
+      canRecord,
 
       fullScreenStatus,
 
       modelUlrList,
       videoUrlList,
       imageUlrList,
+
+      activeSwiperItemIndex,
     }
   },
 }