Przeglądaj źródła

消息等处理

wolyshaw 3 lat temu
rodzic
commit
8da64fe736

+ 3 - 0
src/components/live-broadcast/action-bar.module.less

@@ -4,4 +4,7 @@
   padding: 14px 22px;
   display: flex;
   align-items: center;
+  button{
+    overflow: hidden;
+  }
 }

+ 16 - 2
src/components/live-broadcast/action-bar.tsx

@@ -8,7 +8,14 @@ export default defineComponent({
   render() {
     return (
       <div class={styles['action-bar']}>
-        <ElButton onClick={RuntimeUtils.shareScreenVideo}>屏幕共享</ElButton>
+        <ElButton onClick={RuntimeUtils.shareScreenVideo} type="primary" color="transparent">
+          <SvgIcon
+            name="screen-share"
+            style={{
+              width: '52px'
+            }}
+          />
+        </ElButton>
         <ElDropdown
           placement="top"
           onCommand={RuntimeUtils.setSelectCamera}
@@ -21,7 +28,14 @@ export default defineComponent({
             )
           }}
         >
-          <ElButton type="primary">视频设备</ElButton>
+          <ElButton type="primary" color="transparent">
+            <SvgIcon
+              name="camera"
+              style={{
+                width: '52px'
+              }}
+            />
+          </ElButton>
         </ElDropdown>
         <ElDropdown
           placement="top"

+ 4 - 1
src/components/live-broadcast/event.ts

@@ -1,7 +1,10 @@
 import mitt from 'mitt'
 
 export const LIVE_EVENT_MESSAGE = {
-
+  'RC:Chatroom:Welcome': 'Welcome',
+  'RC:TxtMsg': 'Text',
+  'RC:Chatroom:Barrage': 'Barrage',
+  'RC:Chatroom:Like': 'Like',
 }
 
 export default mitt()

+ 5 - 1
src/components/live-broadcast/header.tsx

@@ -9,7 +9,11 @@ export default defineComponent({
     return (
       <div class={styles.header}>
         <h3 class={styles.title}>直播内容:如何在校管乐团中锻炼孩子的自我管理及团队协作意识</h3>
-        <ElButton type="danger" onClick={RuntimeUtils.closeLive}>关闭直播</ElButton>
+        {runtime.videoStatus === 'liveing' ? (
+          <ElButton type="danger" color="#EA4132" onClick={RuntimeUtils.closeLive}>关闭直播</ElButton>
+        ) : (
+          <ElButton type="primary" color="#01A79E" onClick={RuntimeUtils.startLive}>开始直播</ElButton>
+        )}
       </div>
     )
   }

+ 19 - 23
src/components/live-broadcast/index.tsx

@@ -2,6 +2,7 @@ import { defineComponent, ref } from 'vue'
 import * as RTC from '@rongcloud/plugin-rtc'
 import Header from './header'
 import ActionBar from './action-bar'
+import { state } from '/src/state'
 import runtime, * as RuntimeUtils from './runtime'
 import { removeMedia } from './helpers'
 import styles from './index.module.less'
@@ -10,6 +11,10 @@ const videoRef = ref<HTMLVideoElement | null>(null)
 
 const mediaStreams = new MediaStream()
 
+
+let microphoneAudioTrack: RTC.RCLocalTrack
+let cameraVideoTrack: RTC.RCLocalTrack
+
 export default defineComponent({
   name: 'LiveBroadcast',
   async mounted() {
@@ -17,39 +22,30 @@ export default defineComponent({
   },
   methods: {
     async initializeRoom () {
+      if (!state.user) throw Error('请先登录')
       try {
-        await RuntimeUtils.connectIM()
+        await RuntimeUtils.connectIM(state.user?.imToken)
         runtime.videoRef = videoRef.value
         await RuntimeUtils.getMicrophones()
         await RuntimeUtils.getCameras()
         RuntimeUtils.setSelectCamera(runtime.cameras[0])
         RuntimeUtils.setSelectMicrophone(runtime.microphones[0])
-        const createTracks = await runtime.rtcClient?.createMicrophoneAndCameraTracks('RongCloudRTC', {
-          audio: {
-            micphoneId: runtime.selectedMicrophone?.deviceId,
-          },
-          video: {
-            cameraId: runtime.selectedCamera?.deviceId,
-            faceMode: 'user',
-            frameRate: RTC.RCFrameRate.FPS_15,
-            resolution: RTC.RCResolution.W640_H360,
-          }
-        })
-        if (createTracks?.code === RTC.RCRTCCode.SUCCESS) {
-          for (const track of createTracks.tracks) {
-            // @ts-ignore
-            await mediaStreams.addTrack(track._msTrack)
-          }
-        } else {
-          runtime.videoStatus = 'error'
-        }
+        microphoneAudioTrack = await RuntimeUtils.getTrack('microphone')
+        cameraVideoTrack = await RuntimeUtils.getTrack('camera')
         runtime.videoStatus = 'stream'
         runtime.mediaStreams = mediaStreams
-        runtime.mediaStreamTrack = mediaStreams.getTracks()
+        // runtime.mediaStreamTrack = mediaStreams.getTracks()
         RuntimeUtils.setVideoSrcObject(runtime.videoRef, mediaStreams)
-        const join = await RuntimeUtils.joinRoom('1', RTC.RCLivingType.VIDEO, {
-
+        const join = await RuntimeUtils.joinRoom('w_3fi4PXQcooe5_VUseReE', RTC.RCLivingType.VIDEO, {
+          onMessageReceive(name, content) {
+            console.log(name, content)
+          }
         })
+        if (join.code === RTC.RCRTCCode.SUCCESS && join.room) {
+          runtime.joinedRoom = join.room
+          RuntimeUtils.setTrack([cameraVideoTrack])
+          RuntimeUtils.setTrack([microphoneAudioTrack])
+        }
       } catch (error) {
         runtime.videoStatus = 'error'
         console.log(error)

+ 132 - 25
src/components/live-broadcast/runtime.ts

@@ -1,6 +1,7 @@
 import { reactive, ref, Ref } from 'vue'
 import * as RongIMLib from '@rongcloud/imlib-next'
-import { installer, device, RCRTCCode, RCRTCClient, RCLocalTrack, RCLivingType, IRoomEventListener } from '@rongcloud/plugin-rtc'
+import * as RTC from '@rongcloud/plugin-rtc'
+import request from '/src/helpers/request'
 import event, { LIVE_EVENT_MESSAGE } from './event'
 import { removeMedia } from './helpers'
 
@@ -9,14 +10,16 @@ type imConnectStatus = 'connecting' | 'connected' | 'disconnect'
 type VideoStatus = 'init' | 'stream' | 'liveing' | 'stopped' | 'error' | 'loading'
 
 const runtime = reactive({
-  // IM连接状态
+  /** IM连接状态 */
   imConnectStatus: 'connecting' as imConnectStatus,
   // 屏幕分享状态
   screenShareStatus: false,
   // 视频节点
   videoRef: ref<HTMLVideoElement | null>(null),
   // RTC实例
-  rtcClient: null as RCRTCClient | null,
+  rtcClient: null as RTC.RCRTCClient | null,
+  /** 加入房间实例 */
+  joinedRoom: null as RTC.RCLivingRoom | null,
   // Tracks
   mediaStreamTrack: [] as MediaStreamTrack[],
   // 媒体流
@@ -31,6 +34,12 @@ const runtime = reactive({
   selectedCamera: null as MediaDeviceInfo | null,
   // 麦克风设备
   selectedMicrophone: null as MediaDeviceInfo | null,
+  // 点赞数量
+  likeCount: 0,
+  // 上一次点赞数量
+  lastLikeCount: 0,
+  /** 当前活跃的数据流 */
+  // activeTracks null as RTC.RCLiveStream | null,
 })
 
 export default runtime
@@ -42,34 +51,58 @@ RongIMLib.init({
   appkey: RONG_IM_TOKEN,
 })
 
+type MessageProps = {
+  messageType: 'RC:Chatroom:Welcome' | 'RC:TxtMsg' | 'RC:Chatroom:Barrage' | 'RC:Chatroom:Like',
+  content: any,
+}
+
+type MessageEvent = {
+  messages: MessageProps[],
+}
+
+const Events = RongIMLib.Events
 /**
  * 监听消息通知
  */
- const Events = RongIMLib.Events;
- RongIMLib.addEventListener(Events.MESSAGES, (event) => {
-   console.log('received messages', event.messages)
+ const { MESSAGES, ...RestMessage } = Events
+ RongIMLib.addEventListener(Events.MESSAGES, (evt: MessageEvent) => {
+   const { messages } = evt
+   for (const message of messages) {
+     if (LIVE_EVENT_MESSAGE[message.messageType]) {
+      event.emit(LIVE_EVENT_MESSAGE[message.messageType], {...message.content, $EventMessage: message})
+     }
+   }
  })
 
+ for (const Message of Object.values(RestMessage)) {
+   RongIMLib.addEventListener(Message, () => {
+    event.emit(Message, {$EventMessage: null})
+   })
+ }
+
  /**
   * 监听 IM 连接状态变化
   */
  RongIMLib.addEventListener(Events.CONNECTING, () => {
+   console.log('connecting')
    runtime.imConnectStatus = 'connecting'
  })
 
  RongIMLib.addEventListener(Events.CONNECTED, () => {
+  console.log('connected')
    runtime.imConnectStatus = 'connected'
  })
 
  RongIMLib.addEventListener(Events.DISCONNECT, () => {
+  console.log('disconnect')
    runtime.imConnectStatus = 'disconnect'
  })
 
- export const connectIM = async () => {
+ export const connectIM = async (imToken: string) => {
    try {
-    const user = await RongIMLib.connect('hXAfqkZDs146m6MjMukBMEHvmRoZQV7Hgh8ZG4iscyE=@n56a.cn.rongnav.com;n56a.cn.rongcfg.com')
-    runtime.rtcClient = RongIMLib.installPlugin(installer, {})
-    console.log('connect success', user.data?.userId);
+    const user = await RongIMLib.connect(imToken)
+    runtime.rtcClient = RongIMLib.installPlugin(RTC.installer, {})
+    console.log('connect success', user.data?.userId)
     return user
    } catch (error) {
      throw error
@@ -94,20 +127,15 @@ export const setVideoSrcObject = (video: HTMLVideoElement | null, mediaStreams:
  */
 export const shareScreenVideo = async () => {
   if (runtime.rtcClient) {
-    const { code, track: screenTrack } = await runtime.rtcClient.createScreenVideoTrack()
-    if (code !== RCRTCCode.SUCCESS) {
-      runtime.screenShareStatus = false
-    } else {
-      runtime.screenShareStatus = true
-      console.log(runtime.videoRef)
-      // @ts-ignore
-      // setVideoSrcObject(runtime.videoRef, screenTrack?._msStream as MediaStream)
-    }
-    screenTrack?.on(RCLocalTrack.EVENT_LOCAL_TRACK_END, (track: RCLocalTrack) => {
-      console.log('screen track end', track)
+    const screenTrack = await getTrack('screen')
+    console.log(screenTrack)
+    setTrack([screenTrack as RTC.RCLocalTrack])
+    screenTrack?.on(RTC.RCLocalTrack.EVENT_LOCAL_TRACK_END, (track: RTC.RCLocalTrack) => {
       runtime.screenShareStatus = false
+      console.log('end')
       // setVideoSrcObject(runtime.videoRef, this.mediaStreams)
     })
+
   }
 }
 
@@ -117,7 +145,7 @@ export const shareScreenVideo = async () => {
  * @returns {Promise<void>}
  */
 export const getMicrophones = async () => {
-  const microphones = await device.getMicrophones()
+  const microphones = await RTC.device.getMicrophones()
   runtime.microphones = microphones
   return microphones
 }
@@ -128,7 +156,7 @@ export const getMicrophones = async () => {
  * @returns {Promise<void>}
  */
 export const getCameras = async () => {
-  const cameras = await device.getCameras()
+  const cameras = await RTC.device.getCameras()
   runtime.cameras = cameras
   return cameras
 }
@@ -151,17 +179,96 @@ export const setSelectMicrophone = (microphone: MediaDeviceInfo) => {
   runtime.selectedMicrophone = microphone
 }
 
-export const joinRoom = async (roomId: string, type: RCLivingType, listenEvents: IRoomEventListener | null) => {
+type TrackResult = {
+  code: RTC.RCRTCCode,
+  track: RTC.RCMicphoneAudioTrack | RTC.RCCameraVideoTrack | RTC.RCScreenVideoTrack | undefined
+}
+
+export const getTrack = async (trackType: 'microphone' | 'camera' | 'screen'): Promise<RTC.RCLocalTrack> => {
+  let res: TrackResult | undefined
+  let Track: RTC.RCLocalTrack | null = null
+  if (trackType === 'microphone') {
+    res = await runtime.rtcClient?.createMicrophoneAudioTrack('RongCloudRTC', {
+      micphoneId: runtime.selectedMicrophone?.deviceId,
+    }) as TrackResult
+  } else if (trackType === 'camera') {
+    res = await runtime.rtcClient?.createCameraVideoTrack('RongCloudRTC', {
+      cameraId: runtime.selectedCamera?.deviceId,
+      faceMode: 'user',
+      frameRate: RTC.RCFrameRate.FPS_15,
+      resolution: RTC.RCResolution.W1280_H720,
+    }) as TrackResult
+  } else {
+    res = await runtime?.rtcClient?.createScreenVideoTrack() as TrackResult
+  }
+
+  Track = res?.track as RTC.RCLocalTrack
+  if (res.code !== RTC.RCRTCCode.SUCCESS || !Track) {
+    throw new Error('获取数据流失败')
+  }
+  return Track
+}
+
+/**
+ * 添加视频流,会同步修改当先视频与推送的流
+ * @param track
+ */
+export const setTrack = (tracks: [RTC.RCLocalTrack]) => {
+  for (const track of tracks) {
+    // @ts-ignore
+    runtime.mediaStreams?.addTrack(track._msTrack)
+  }
+  runtime.joinedRoom?.publish(tracks)
+}
+/**
+ * 删除视频流,会同步修改当先视频与推送的流
+ * @param track
+ */
+export const removeTrack = (tracks: [RTC.RCLocalTrack]) => {
+  for (const track of tracks) {
+    // @ts-ignore
+    runtime.mediaStreams?.removeTrack(track._msTrack)
+  }
+  runtime.joinedRoom?.unpublish(tracks)
+}
+
+export const joinRoom = async (roomId: string, type: RTC.RCLivingType, listenEvents: RTC.IRoomEventListener | null) => {
+  await RongIMLib.joinChatRoom(roomId, {count: 0})
   const join = await runtime.rtcClient?.joinLivingRoom(roomId, type)
-  if (join?.code != RCRTCCode.SUCCESS) throw Error('加入房间失败')
+  if (join?.code != RTC.RCRTCCode.SUCCESS) throw Error('加入房间失败')
   join.room?.registerRoomEventListener(listenEvents)
   return join
 }
 
 /**
+ * 开始直播
+ */
+
+export const startLive = async () => {
+  if (runtime.videoStatus !== 'stream') throw Error('当前无视频流')
+
+}
+
+/**
  * 关闭直播
  */
 export const closeLive = async () => {
   removeMedia(runtime.mediaStreams, runtime.mediaStreamTrack)
   runtime.videoStatus = 'stopped'
 }
+
+/**
+ * 同步点赞数量
+ */
+export const loopSyncLike = async () => {
+  if (runtime.likeCount !== runtime.lastLikeCount) {
+    try {
+      await request.post('/api/live/like', {})
+      runtime.lastLikeCount = runtime.likeCount
+    } catch (error) {}
+  }
+  setTimeout(() => {
+    loopSyncLike()
+  }, 1000 * 60 * 5)
+}
+

+ 7 - 0
src/components/svg-icon/install.ts

@@ -0,0 +1,7 @@
+import SvgIcon from './index'
+
+export default {
+  install (app: any) {
+    app.component('SvgIcon', SvgIcon)
+  }
+}

+ 2 - 0
src/env.d.ts

@@ -6,3 +6,5 @@ declare module '*.vue' {
   const component: DefineComponent<{}, {}, any>
   export default component
 }
+
+declare const SvgIcon: any

+ 14 - 0
src/icons/camera.svg

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="52px" height="32px" viewBox="0 0 52 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>摄像头备份</title>
+    <g id="后台直播界面" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="图标状态" transform="translate(-22.000000, -26.000000)">
+            <g id="摄像头备份" transform="translate(22.000000, 26.000000)">
+                <rect id="矩形" fill-opacity="0.33" fill="#5E626D" x="0" y="0" width="52" height="32" rx="16"></rect>
+                <g id="编组" transform="translate(19.000000, 7.000000)" fill="#FFFFFF" fill-rule="nonzero">
+                    <path d="M8,0 C12.4109091,0 16,3.63078412 16,8.0929332 C16,11.2418199 14.2109091,13.9750242 11.6036364,15.3177154 L11.6036364,15.3177154 L13.0763636,17.889061 C13.2072727,18.1171345 13.2072727,18.3967086 13.0763636,18.6247822 C12.9454546,18.8528558 12.7054546,18.9926428 12.4472727,18.9926428 L12.4472727,18.9926428 L3.66909091,19 C3.41090909,19 3.17090908,18.860213 3.03999999,18.6321394 C2.90909091,18.4040658 2.90909091,18.1244918 3.03999999,17.8964182 L3.03999999,17.8964182 L4.48363636,15.3618587 C1.83272727,14.0412391 0,11.278606 0,8.0929332 C0,3.63078412 3.58909091,0 8,0 Z M8,4.41432721 C5.99636363,4.41432721 4.36363636,6.06602128 4.36363636,8.0929332 C4.36363636,10.1198451 5.99636363,11.7715392 8,11.7715392 C10.0036364,11.7715392 11.6363636,10.1198451 11.6363636,8.0929332 C11.6363636,6.0660213 10.0036364,4.41432721 8,4.41432721 Z M8,5.88576959 C9.20498491,5.88576959 10.1818182,6.8739504 10.1818182,8.0929332 C10.1818182,9.311916 9.20498491,10.3000968 8,10.3000968 C6.79501509,10.3000968 5.81818181,9.311916 5.81818181,8.0929332 C5.81818181,6.8739504 6.79501509,5.88576959 8,5.88576959 Z M8,2.51248791 C7.59833837,2.51248791 7.27272726,2.8418815 7.27272726,3.2482091 C7.27272726,3.65285576 7.59999999,3.98393031 8,3.98393031 C8.40000001,3.98393031 8.72727274,3.65285574 8.72727274,3.2482091 C8.72727274,2.84356245 8.40000001,2.51248791 8,2.51248791 Z" id="形状结合"></path>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 14 - 0
src/icons/screen-share-disabled.svg

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="52px" height="32px" viewBox="0 0 52 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>屏幕分享备份 3</title>
+    <g id="后台直播界面" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="图标状态" transform="translate(-218.000000, -117.000000)">
+            <g id="屏幕分享备份-3" transform="translate(218.000000, 117.000000)">
+                <rect id="矩形备份-14" fill-opacity="0.33" fill="#5E626D" x="0" y="0" width="52" height="32" rx="16"></rect>
+                <g id="编组" transform="translate(17.000000, 9.000000)" fill="#A4A6A9" fill-rule="nonzero">
+                    <path d="M1.80000001,0 C1.32261033,0 0.864773283,0.188137206 0.527207799,0.523023596 C0.189642315,0.857909985 0,1.3121134 0,1.78571428 L0,10.7142857 C0,11.1878866 0.189642315,11.64209 0.527207799,11.9769764 C0.864773283,12.3118628 1.32261033,12.5 1.80000001,12.5 L16.2,12.5 C16.6773897,12.5 17.1352267,12.3118628 17.4727922,11.9769764 C17.8103577,11.64209 18,11.1878866 18,10.7142857 L18,1.78571428 C18,1.3121134 17.8103577,0.857909985 17.4727922,0.523023596 C17.1352267,0.188137206 16.6773897,0 16.2,0 L1.80000001,0 Z M5.4,14.4196428 C5.4,14.2657226 5.46163375,14.1181064 5.57134253,14.0092684 C5.68105132,13.9004303 5.82984836,13.8392857 5.98500001,13.8392857 L12.015,13.8392857 C12.3380866,13.8392857 12.6,14.0991205 12.6,14.4196429 C12.6,14.7401653 12.3380866,15 12.015,15 L5.98500001,15 C5.82984836,15 5.68105131,14.9388554 5.57134253,14.8300173 C5.46163374,14.7211792 5.4,14.5735631 5.4,14.4196428 Z M9.1540358,3.16204122 C9.15545308,2.95729299 9.28103216,2.77253192 9.47453576,2.69049841 C9.66803936,2.60846489 9.89321706,2.64452684 10.049229,2.78253487 L12.4257154,4.89055784 C12.5412713,4.99279754 12.6048538,5.13896765 12.5997104,5.29055613 C12.5945671,5.44214461 12.5212138,5.58394766 12.3989773,5.67860382 L10.0011003,7.53880708 C9.8401883,7.66338604 9.61978965,7.68732168 9.43443428,7.60034789 C9.2490789,7.51337411 9.13170132,7.33094467 9.13264529,7.13130436 L9.14013198,5.71282161 C7.66467809,5.71453815 6.46952481,6.8746283 6.46952588,8.30507811 C6.46952588,8.59141086 6.23010435,8.82352941 5.93476294,8.82352941 C5.63942152,8.82352941 5.4,8.59141086 5.4,8.30507811 C5.4,7.34256527 5.79438652,6.41947445 6.49639906,5.73887509 C7.19841159,5.05827573 8.15054491,4.675919 9.14334056,4.675919 L9.1454796,4.675919 L9.1540358,3.16204122 L9.1540358,3.16204122 Z" id="形状"></path>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 14 - 0
src/icons/screen-share.svg

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="52px" height="32px" viewBox="0 0 52 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>屏幕分享备份</title>
+    <g id="后台直播界面" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="图标状态" transform="translate(-218.000000, -26.000000)">
+            <g id="屏幕分享备份" transform="translate(218.000000, 26.000000)">
+                <rect id="矩形备份-14" fill-opacity="0.33" fill="#5E626D" x="0" y="0" width="52" height="32" rx="16"></rect>
+                <g id="编组" transform="translate(17.000000, 9.000000)" fill="#FFFFFF" fill-rule="nonzero">
+                    <path d="M1.80000001,0 C1.32261033,0 0.864773283,0.188137206 0.527207799,0.523023596 C0.189642315,0.857909985 0,1.3121134 0,1.78571428 L0,10.7142857 C0,11.1878866 0.189642315,11.64209 0.527207799,11.9769764 C0.864773283,12.3118628 1.32261033,12.5 1.80000001,12.5 L16.2,12.5 C16.6773897,12.5 17.1352267,12.3118628 17.4727922,11.9769764 C17.8103577,11.64209 18,11.1878866 18,10.7142857 L18,1.78571428 C18,1.3121134 17.8103577,0.857909985 17.4727922,0.523023596 C17.1352267,0.188137206 16.6773897,0 16.2,0 L1.80000001,0 Z M5.4,14.4196428 C5.4,14.2657226 5.46163375,14.1181064 5.57134253,14.0092684 C5.68105132,13.9004303 5.82984836,13.8392857 5.98500001,13.8392857 L12.015,13.8392857 C12.3380866,13.8392857 12.6,14.0991205 12.6,14.4196429 C12.6,14.7401653 12.3380866,15 12.015,15 L5.98500001,15 C5.82984836,15 5.68105131,14.9388554 5.57134253,14.8300173 C5.46163374,14.7211792 5.4,14.5735631 5.4,14.4196428 Z M9.1540358,3.16204122 C9.15545308,2.95729299 9.28103216,2.77253192 9.47453576,2.69049841 C9.66803936,2.60846489 9.89321706,2.64452684 10.049229,2.78253487 L12.4257154,4.89055784 C12.5412713,4.99279754 12.6048538,5.13896765 12.5997104,5.29055613 C12.5945671,5.44214461 12.5212138,5.58394766 12.3989773,5.67860382 L10.0011003,7.53880708 C9.8401883,7.66338604 9.61978965,7.68732168 9.43443428,7.60034789 C9.2490789,7.51337411 9.13170132,7.33094467 9.13264529,7.13130436 L9.14013198,5.71282161 C7.66467809,5.71453815 6.46952481,6.8746283 6.46952588,8.30507811 C6.46952588,8.59141086 6.23010435,8.82352941 5.93476294,8.82352941 C5.63942152,8.82352941 5.4,8.59141086 5.4,8.30507811 C5.4,7.34256527 5.79438652,6.41947445 6.49639906,5.73887509 C7.19841159,5.05827573 8.15054491,4.675919 9.14334056,4.675919 L9.1454796,4.675919 L9.1540358,3.16204122 L9.1540358,3.16204122 Z" id="形状"></path>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 2 - 0
src/main.ts

@@ -5,10 +5,12 @@ import 'element-plus/dist/index.css'
 import App from './App.vue'
 import router from './router'
 import 'element-plus/dist/index.css'
+import SvgIcon from './components/svg-icon/install'
 import './permission'
 // import './icons' // icon
 import './base.css'
 
 createApp(App)
   .use(ElementPlus)
+  .use(SvgIcon)
   .use(router).mount('#app')

+ 1 - 1
src/state.ts

@@ -2,5 +2,5 @@ import { reactive } from 'vue'
 
 
 export const state = reactive({
-  user: null as any
+  user: null as null | any
 })