Browse Source

增加视频页面

caner 1 year ago
parent
commit
65b116bd9b

+ 1 - 0
package.json

@@ -15,6 +15,7 @@
     "@kuyoonjo/tauri-plugin-mqtt": "^0.1.0",
     "@kuyoonjo/tauri-plugin-mqtt": "^0.1.0",
     "@tauri-apps/api": "^2",
     "@tauri-apps/api": "^2",
     "buffer": "^6.0.3",
     "buffer": "^6.0.3",
+    "echarts": "^5.6.0",
     "naive-ui": "^2.40.1",
     "naive-ui": "^2.40.1",
     "pinia": "^2.2.6",
     "pinia": "^2.2.6",
     "pinia-plugin-persist": "^1.0.0",
     "pinia-plugin-persist": "^1.0.0",

+ 21 - 2
src/App.vue

@@ -14,16 +14,35 @@
   </n-config-provider>
   </n-config-provider>
 </template>
 </template>
 <script setup lang='ts'>
 <script setup lang='ts'>
-import { computed } from 'vue'
+import { computed, provide, watch } from 'vue'
 import { zhCN, dateZhCN } from 'naive-ui'
 import { zhCN, dateZhCN } from 'naive-ui'
+import { useRouter } from 'vue-router'
 import loading from '@/components/loading.vue'
 import loading from '@/components/loading.vue'
 import useStore from './store/index'
 import useStore from './store/index'
 import Theme from '@/assets/naive-theme'
 import Theme from '@/assets/naive-theme'
 import GlobalNotif from '@/components/notifaiction.vue'
 import GlobalNotif from '@/components/notifaiction.vue'
+import MqttService from '@/services/mqtt.service'
 
 
+const mqtt = new MqttService()
 const store = useStore()
 const store = useStore()
+const router = useRouter()
 const show = computed(() => store.loading)
 const show = computed(() => store.loading)
 const themeOverrides = Theme
 const themeOverrides = Theme
+
+watch(() => store.mqtt_message, (val) => {
+  console.log('顶级监听', val)
+
+  if (val.type === 'leave' || val.type === 'disconnect') {
+    router.push('/')
+  }
+
+  if (val.type === 'connect') {
+    router.push('/room')
+  }
+})
+
+provide('MQTT', mqtt)
+
 </script>
 </script>
 <style lang='scss'>
 <style lang='scss'>
 * {
 * {
@@ -43,7 +62,7 @@ body {
   min-width: 1300px;
   min-width: 1300px;
   min-height: 760px;
   min-height: 760px;
   overflow: hidden;
   overflow: hidden;
-  border-radius: 13px;
+  border-radius: 10px;
   background: transparent;
   background: transparent;
   position: relative;
   position: relative;
   border: none;
   border: none;

+ 17 - 0
src/components/audio.vue

@@ -0,0 +1,17 @@
+<template>
+  <Icon
+    name="audio"
+    :size="size"
+    :color="(!!(state % 2)) ? '#00CED1' : 'rgba(255, 255, 255, 0.5)'"
+  />
+</template>
+
+<script setup lang='ts'>
+const props = withDefaults(defineProps<{
+    state: number,
+    size?: number
+}>(), {
+  state: 0,
+  size: 22
+})
+</script>

+ 124 - 0
src/components/battery.vue

@@ -0,0 +1,124 @@
+<template>
+  <div
+    class="electric"
+    :class="bgClass"
+  >
+    <div class="electric-panel">
+      <div
+        class="electric-panel-remainder"
+        :style="{ width: quantity + '%' }"
+      />
+    </div>
+    <div class="electric-berText">
+      {{ quantity }}%
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { computed } from 'vue'
+
+const props = withDefaults(defineProps<{
+    quantity: number
+}>(), {
+  quantity: 0
+})
+const bgClass = computed(() => {
+  if (props.quantity >= 50) {
+    return 'success'
+  } if (props.quantity >= 20) {
+    return 'warning'
+  } if (props.quantity >= 1) {
+    return 'danger'
+  }
+  return 'danger'
+})
+</script>
+
+  <style scoped lang="scss">
+  .electric {
+    display: inline-flex;
+    justify-content: center;
+    align-items: center;
+    position: relative;
+    color: white;
+
+    &-panel {
+      box-sizing: border-box;
+      width: 25px;
+      height: 15px;
+      position: relative;
+      border: 2px solid #ccc;
+      padding: 1px;
+      border-radius: 3px;
+      margin-right: 5px;
+
+      &::before {
+        content: "";
+        border-radius: 0 1px 1px 0;
+        height: 8px;
+        background: #ccc;
+        width: 3px;
+        position: absolute;
+        top: 50%;
+        right: -4px;
+        transform: translateY(-50%);
+        overflow: hidden;
+      }
+
+      &-remainder {
+        border-radius: 1px;
+        position: relative;
+        height: 100%;
+        width: 0%;
+        left: 0;
+        top: 0;
+        background: #fff;
+      }
+    }
+
+    &-berText {
+      font-size: 15px;
+    }
+  }
+
+  .success {
+    color: #40d7c1;
+
+    &>div:first-child::before,
+    &>div:first-child>div {
+      background-color: #40d7c1;
+    }
+
+    &>div:first-child {
+      border-color: #40d7c1;
+    }
+  }
+
+  .warning {
+    color: #f90;
+
+    &>div:first-child::before,
+    &>div:first-child>div {
+      background-color: #f90;
+    }
+
+    &>div:first-child {
+      border-color: #f90;
+    }
+  }
+
+  .danger {
+    color: #ed4014;
+
+    &>div:first-child::before,
+    &>div:first-child>div {
+      background-color: #ed4014;
+    }
+
+    &>div:first-child {
+      border-color: #ed4014;
+    }
+
+  }
+  </style>

+ 127 - 0
src/components/gauge.vue

@@ -0,0 +1,127 @@
+<script setup lang="ts">
+import * as echarts from 'echarts'
+import { GaugeChart } from 'echarts/charts'
+import { LabelLayout, UniversalTransition } from 'echarts/features'
+import { CanvasRenderer } from 'echarts/renderers'
+import {
+  onUnmounted,
+  onMounted, ref, watch
+} from 'vue'
+
+echarts.use([ GaugeChart, LabelLayout, UniversalTransition, CanvasRenderer ])
+const props = withDefaults(defineProps<{
+  value: number,
+  gears?: string
+}>(), {
+  value: 0,
+  gears: '前进'
+})
+const chartDom = ref()
+const chart = ref(null as any)
+const option = ref({
+  series: {
+    name: 'Pressure',
+    type: 'gauge',
+    itemStyle: {
+      color: '#FFFFFF'
+    },
+    startAngle: 180,
+    max: 60,
+    endAngle: 0,
+    axisLine: {
+      lineStyle: {
+        width: 1
+      }
+    },
+    axisTick: {
+      distance: 0,
+      length: 10,
+      lineStyle: {
+        color: '#FFFFFF'
+      }
+    },
+    splitLine: {
+      length: 15,
+      distance: 0,
+      lineStyle: {
+        color: '#FFFFFF'
+      }
+    },
+    axisLabel: {
+      distance: 8,
+      color: '#FFFFFF'
+    },
+    progress: {
+      show: true
+    },
+    radius: '190%',
+    center: [ '50%', '98%' ],
+    detail: {
+      offsetCenter: [ 0, -25 ],
+      valueAnimation: true,
+      formatter: (value: number) => `{value|${value.toFixed(0)}}{unit|km/h}\n{num|${props.gears}}`,
+      rich: {
+        value: {
+          fontSize: 20,
+          fontWeight: 'bolder',
+          color: '#FFFFFF'
+        },
+        unit: {
+          fontSize: 20,
+          color: '#FFFFFF',
+          padding: [ 0, 0, 0, 10 ]
+        },
+        num: {
+          fontSize: 20,
+          color: '#FFFFFF',
+          padding: [ 0, 0, 0, 10 ]
+        }
+      }
+    },
+    pointer: {
+      show: false
+    },
+    data: [ 0 ]
+  }
+})
+
+function resetChart() {
+  if (!chart.value) return
+  chart.value.resize()
+}
+
+watch(() => props.value, (v: number) => {
+  if (!chart.value) return
+  if (v >= 60) v = 60
+  if (v <= 0) v = 0
+  option.value.series.data[0] = v
+  chart.value.setOption(option.value)
+})
+
+watch(() => props.gears, () => {
+  if (!chart.value) return
+  chart.value.setOption(option.value)
+})
+
+onMounted(() => {
+  chart.value = echarts.init(chartDom.value)
+  chart.value.setOption(option.value)
+  window.addEventListener('resize', resetChart, false)
+})
+
+onUnmounted(() => {
+  window.removeEventListener('resize', resetChart, false)
+})
+</script>
+<template>
+  <div
+    id="charts"
+    ref="chartDom"
+  />
+</template>
+<style scoped>
+#charts {
+  width: 100%;
+  height: 100%;
+}
+</style>

+ 110 - 0
src/components/signal.vue

@@ -0,0 +1,110 @@
+<template>
+  <div class="signal-box">
+    <ul>
+      <li
+        v-for="(item, idex) in list"
+        :key="idex"
+        :class="item.class"
+      />
+    </ul>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, watch } from 'vue'
+
+const props = withDefaults(defineProps<{
+    signalValue: number
+}>(), {
+  signalValue: 0
+})
+
+const list = ref([
+  {
+    id: 1,
+    class: 'signal-default'
+  },
+  {
+    id: 2,
+    class: 'signal-default'
+  },
+  {
+    id: 3,
+    class: 'signal-default'
+  },
+  {
+    id: 4,
+    class: 'signal-default'
+  },
+  {
+    id: 5,
+    class: 'signal-default'
+  }
+])
+
+watch(() => props.signalValue, (v: number) => {
+  list.value.forEach((el: { id: number, class: string }) => {
+    if (el.id <= v) {
+      if (v <= 2) {
+        el.class = 'signal-red'
+      } else if (v <= 4) {
+        el.class = 'signal-yellow'
+      } else {
+        el.class = 'signal-green'
+      }
+    } else {
+      el.class = 'signal-default'
+    }
+  })
+}, { immediate: true })
+</script>
+
+  <style scoped lang="scss">
+  .signal-box {
+    display: inline-flex;
+    align-items: flex-start;
+    justify-content: center;
+    height: 23px;
+    width: 23px;
+    position: relative;
+    z-index: 3;
+
+    ul {
+      height: 21px;
+      margin: 0;
+      padding: 0;
+      display: flex;
+      align-items: flex-end;
+    }
+
+    li {
+      width: 4px;
+      height: 5px;
+      border-radius: 10px;
+      list-style: none;
+      margin: 0 0.5px;
+
+      @for $i from 1 through 5 {
+        &:nth-child(#{$i}) {
+          height:#{ $i * 5 - ($i - 1) }px;
+        }
+      }
+    }
+
+    .signal-default {
+      background: rgba(0, 0, 0, 0.3);
+    }
+
+    .signal-red {
+      background-color: #ed4014;
+    }
+
+    .signal-yellow {
+      background-color: #f90;
+    }
+
+    .signal-green {
+      background-color: #00CED1;
+    }
+  }
+  </style>

+ 18 - 4
src/components/topBar.vue

@@ -3,11 +3,16 @@
     class="topBar"
     class="topBar"
     data-tauri-drag-region
     data-tauri-drag-region
   >
   >
-    <div class="topBar-icon" />
-    <div class="topBar-content">
+    <div
+      class="topBar-content"
+      data-tauri-drag-region
+    >
       <slot />
       <slot />
     </div>
     </div>
-    <div class="topBar-btns">
+    <div
+      v-if="showBtn"
+      class="topBar-btns"
+    >
       <Icon
       <Icon
         name="min"
         name="min"
         :size="20"
         :size="20"
@@ -28,7 +33,11 @@
 import { Window } from '@tauri-apps/api/window'
 import { Window } from '@tauri-apps/api/window'
 
 
 const appWindow = new Window('main')
 const appWindow = new Window('main')
-
+const props = withDefaults(defineProps<{
+  showBtn?: boolean
+}>(), {
+  showBtn: true
+})
 </script>
 </script>
 <style lang="scss" scoped>
 <style lang="scss" scoped>
 .topBar {
 .topBar {
@@ -44,6 +53,11 @@ const appWindow = new Window('main')
   z-index: 9;
   z-index: 9;
   cursor: grabbing;
   cursor: grabbing;
 
 
+  &-content {
+    flex: 1;
+    width: 100%;
+  }
+
   &-btns {
   &-btns {
     width: 70px;
     width: 70px;
     display: flex;
     display: flex;

+ 0 - 29
src/pages/home/index.vue

@@ -1,29 +0,0 @@
-<template>
-  <div>
-    <n-button @click="test">
-      测试
-    </n-button>
-    <div>{{ a }}</div>
-  </div>
-</template>
-
-<script setup lang='ts'>
-import { invoke } from '@tauri-apps/api/core'
-import { listen } from '@tauri-apps/api/event'
-import { ref } from 'vue'
-
-const a = ref<string>('')
-async function test() {
-  const res = await invoke('greet', { name: 'test111' })
-  a.value = res as string
-  await invoke('init_process')
-  await listen('event', (event) => {
-    console.log(9, event)
-  })
-}
-
-</script>
-
-  <style>
-
-  </style>

+ 5 - 9
src/pages/login/index.vue

@@ -58,28 +58,24 @@
 </template>
 </template>
 <script setup lang="ts">
 <script setup lang="ts">
 import {
 import {
-  computed, onUnmounted, ref, watch
+  computed, onUnmounted, ref, watch, inject
 } from 'vue'
 } from 'vue'
 import topBar from '@/components/topBar.vue'
 import topBar from '@/components/topBar.vue'
 import useStore from '@/store/index'
 import useStore from '@/store/index'
-import MqttService from '@/services/mqtt.service'
 
 
-const err = ref('')
+const dic = { leave: '对方离开房间', disconnect: '连接断开' }
+const store = useStore()
+const mqtt = inject('MQTT')
 const name = ref('test123')
 const name = ref('test123')
 const room = ref('test')
 const room = ref('test')
 const url = ref('mqtts://caner.top:49659')
 const url = ref('mqtts://caner.top:49659')
 const disabled = computed(() => !name.value || !room.value || !url.value || !url.value.startsWith('mqtts:'))
 const disabled = computed(() => !name.value || !room.value || !url.value || !url.value.startsWith('mqtts:'))
-const store = useStore()
-const mqtt = new MqttService()
+const err = computed(() => dic[store.mqtt_message.type])
 
 
 async function login() {
 async function login() {
   if (disabled.value) return
   if (disabled.value) return
   await mqtt.connect(url.value, room.value, name.value)
   await mqtt.connect(url.value, room.value, name.value)
 }
 }
-
-watch(() => store.mqtt_message, (val) => {
-  console.log('监听', val)
-})
 </script>
 </script>
 <style scoped lang="scss">
 <style scoped lang="scss">
 .login {
 .login {

+ 80 - 0
src/pages/room/index.vue

@@ -0,0 +1,80 @@
+<template>
+  <div class="room">
+    <topBar
+      class="room-bar"
+    >
+      <signal :signal-value="signalValue" />
+      <audiod :state="audioValue" />
+      <battery :quantity="batteryValue" />
+    </topBar>
+    <video
+      ref="remoteVideo"
+      autoplay
+      playsinline
+    />
+    <div class="room-gauge">
+      <gauge
+        :value="SpeedValue"
+        :gears="conctrlNum % 2 ? '倒档' : '前进'"
+      />
+    </div>
+  </div>
+</template>
+
+<script setup lang='ts'>
+import { ref } from 'vue'
+import topBar from '@/components/topBar.vue'
+import signal from '@/components/signal.vue'
+import battery from '@/components/battery.vue'
+import gauge from '@/components/gauge.vue'
+import audiod from '@/components/audio.vue'
+
+const signalValue = ref(5)
+const batteryValue = ref(50)
+const SpeedValue = ref(60)
+const conctrlNum = ref(1)
+const audioValue = ref(3)
+
+async function test() {
+  console.log(123)
+}
+
+</script>
+<style lang="scss" scoped>
+.room {
+  width: 100%;
+  height: 100%;
+  position: relative;
+
+&-bar{
+  :deep(.topBar-content){
+    display: flex;
+    align-items: flex-end;
+    &>*{
+      margin: 0 5px 0 10px;
+    }
+  }
+}
+
+  video {
+    width: 100%;
+    height: 100%;
+    background: black;
+    object-fit: fill;
+    font-size: 0;
+    z-index: 1;
+  }
+
+  &-gauge {
+    width: 30vw;
+    height: 15vw;
+    max-width: 460px;
+    max-height: 230px;
+    position: absolute;
+    bottom: 0;
+    left: 50%;
+    transform: translateX(-50%);
+    z-index: 2;
+  }
+}
+</style>

+ 3 - 3
src/pages/home/route.ts → src/pages/room/route.ts

@@ -1,10 +1,10 @@
 import { RouteRecordRaw } from 'vue-router'
 import { RouteRecordRaw } from 'vue-router'
-import home from './index.vue'
+import index from './index.vue'
 
 
 export default {
 export default {
-  path: '/home',
+  path: '/room',
   meta: {
   meta: {
     authorize: true
     authorize: true
   },
   },
-  component: home
+  component: index
 } as RouteRecordRaw
 } as RouteRecordRaw

+ 3 - 2
src/services/mqtt.service.ts

@@ -68,17 +68,18 @@ export default class MqttService extends Service {
       // 断开连接
       // 断开连接
       if (event.disconnect) {
       if (event.disconnect) {
         console.log('已断开服务器,插件未自动断开已提is')
         console.log('已断开服务器,插件未自动断开已提is')
-        this.store.setMqttMessage({ type: 'disconnect' })
+        this.store.setMqttMessage({ type: 'disconnect', data: '服务器断开' })
       }
       }
 
 
       // 收到消息
       // 收到消息
       if (event.message) {
       if (event.message) {
-        console.log('收到消息', event.message)
         try {
         try {
           const { payload, topic } = event.message
           const { payload, topic } = event.message
           this.client_channel = topic
           this.client_channel = topic
           const data = JSON.parse(Buffer.from(payload).toString())
           const data = JSON.parse(Buffer.from(payload).toString())
           this.store.setMqttMessage({ ...data })
           this.store.setMqttMessage({ ...data })
+          // 离开就退出mqtt
+          if (data.type === 'leave') this.disconnect()
         } catch (error) {
         } catch (error) {
           this.throw('mqtt消息解析失败')
           this.throw('mqtt消息解析失败')
         }
         }