zhuf 1 vuosi sitten
vanhempi
commit
0ab6ffb904

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 688 - 8
package-lock.json


+ 2 - 0
package.json

@@ -14,6 +14,7 @@
     "@types/video.js": "^7.3.56",
     "@vueuse/core": "^10.1.2",
     "agora-rtc-sdk-ng": "^4.20.0",
+    "aliyun-webrtc-sdk": "^1.17.3",
     "amfe-flexible": "^2.2.1",
     "autoprefixer": "^10.4.14",
     "axios": "^1.4.0",
@@ -34,6 +35,7 @@
     "@types/file-saver": "^2.0.5",
     "@types/node": "^18.16.11",
     "@types/qrcode": "^1.5.5",
+    "@vitejs/plugin-basic-ssl": "^1.1.0",
     "@vitejs/plugin-legacy": "^4.0.5",
     "@vitejs/plugin-vue": "^4.2.3",
     "@vue-macros/volar": "^0.9.8",

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 185 - 169
pnpm-lock.yaml


+ 1 - 0
shims.d.ts

@@ -8,4 +8,5 @@ declare module 'vite-plugin-windicss'
 declare interface Window {
   Aliplayer: any
   GLOBAL_CONFIG: any
+  AliRtcEngine: any
 }

+ 3 - 0
src/components/video-player.vue

@@ -18,6 +18,9 @@ const player = ref()
 
 function getSourceType(src: string) {
   const ext = src.split('.').pop()
+  // if (ext?.includes('/'))
+  //   return 'application/x-mpegURL'
+  // else
   if (ext === 'm3u8')
     return 'application/x-mpegURL'
   else

+ 8 - 0
src/pages/detail/util.ts

@@ -91,3 +91,11 @@ export const TypeMap: {
   //   idx: 6,
   // },
 }
+
+// 正则表达式匹配
+// 'LiteLiveClass://push_screen://rtmp://push.txhlwxx.com/swh/24' to 'rtmp://push.txhlwxx.com/swh/24'
+export function splitRtmpPath(path: string) {
+  const reg = /rtmp:\/\/.*\/.*\/.*$/
+  const res = path.match(reg)
+  return res?.[0] ?? null
+}

+ 345 - 0
src/pages/detail/zbkc_ks/[tmk_id]/-.vue

@@ -0,0 +1,345 @@
+<script setup lang='ts'>
+import { showFailToast, showSuccessToast } from 'vant'
+import QRCode from 'qrcode'
+import type { IAgoraRTCClient, ICameraVideoTrack, IMicrophoneAudioTrack } from 'agora-rtc-sdk-ng'
+import AgoraRTC from 'agora-rtc-sdk-ng'
+import { TypeMap, formatNumber, getFullPath, splitRtmpPath } from '~/pages/detail/util'
+import { user } from '~/store'
+
+const props = defineProps<{
+  // type: string
+  tmk_id: string
+  tmz_id: string
+}>()
+let truthPid = props.tmk_id === '-' ? undefined : props.tmk_id
+
+const _type_ = 'zbkc_ks'
+const theType = TypeMap[_type_]
+
+const router = useRouter()
+const route = useRoute()
+
+const showShare = ref(false)
+function openShare() {
+  showShare.value = true
+}
+
+const qrshow = ref(false)
+const qrsrc = ref('')
+function handleShareSelect(option: any) {
+  const link = location.href
+  switch (option.icon) {
+    case 'link':
+      navigator.clipboard.writeText(link).then(() => {
+        showShare.value = false
+        showSuccessToast('已复制到剪贴板')
+      }).catch(() => {
+        showFailToast('复制失败')
+      })
+
+      break
+    case 'qrcode':
+      QRCode.toDataURL(link, (err, url) => {
+        if (err)
+          showFailToast('生成二维码失败')
+        qrsrc.value = url
+        showShare.value = false
+        qrshow.value = true
+      })
+      break
+    default:
+      break
+  }
+}
+
+const loading = ref(true)
+const detail = ref<any>()
+const rows = ref<any[]>([])
+
+const isRtcUser = ref(false)
+const isRtcing = ref(false)
+const rtcPlayerRef = ref()
+
+function fixTruthPid(pid: string) {
+  const routePid = truthPid
+  if (routePid !== pid) {
+    console.info('纠正pid', pid)
+    truthPid = pid
+  }
+  return truthPid
+}
+
+const rtc: {
+  localAudioTrack?: IMicrophoneAudioTrack
+  localVideoTrack?: ICameraVideoTrack
+  client?: IAgoraRTCClient
+} = {
+  localAudioTrack: undefined,
+  localVideoTrack: undefined,
+  client: undefined,
+}
+
+async function initRTC() {
+  const options = {
+    appId: detail.value.rtc_appid,
+    channel: detail.value.channelname,
+    token: detail.value.rtc_token,
+    uid: detail.value.uidStr,
+  }
+  console.log('rtc options : ', options)
+
+  console.group('AgoraRTC')
+  rtc.client = AgoraRTC.createClient({ mode: 'live', codec: 'vp8', role: 'host' })
+  // rtc.client.setClientRole('host')
+
+  await rtc.client.join(options.appId, options.channel, options?.token || null, options?.uid)
+  rtc.localAudioTrack = await AgoraRTC.createMicrophoneAudioTrack()
+  rtc.localVideoTrack = await AgoraRTC.createCameraVideoTrack({
+    encoderConfig: '1080p_1',
+  })
+  rtc.localVideoTrack.play(rtcPlayerRef.value, { mirror: false })
+
+  await rtc.client.publish([rtc.localAudioTrack, rtc.localVideoTrack])
+  console.groupEnd()
+  isRtcing.value = true
+}
+
+function init() {
+  loading.value = true
+  return request({
+    url: `/txwx/${_type_}/detail`,
+    data: {
+      [theType.id]: props.tmz_id,
+      live: 1,
+    },
+  }).then((res) => {
+    if (res?.code === '1') {
+      detail.value = res.data.one_info
+      isRtcUser.value = user.value?.user_id === detail.value.tzk_zjr_user_id
+      // isRtcUser.value = true
+      const otherData: any = {}
+      if (Array.isArray(theType.pid)) {
+        const t: string[] = []
+        theType.pid.forEach((item: string) => {
+          otherData[item] = detail.value[item]
+          t.push(detail.value[item])
+        })
+        fixTruthPid(t.join('-'))
+      }
+      else {
+        otherData[theType.pid] = detail.value[theType.pid]
+        fixTruthPid(detail.value[theType.pid])
+      }
+      return request({
+        url: `/txwx/${_type_}/index`,
+        data: {
+          // [theType.pid]: props.tmk_id,
+          ...otherData,
+          limit: 4,
+        },
+      }).then((res) => {
+        if (res?.code === '1')
+          rows.value = res.data.page_data
+      })
+    }
+  }).finally(() => {
+    loading.value = false
+  })
+}
+
+watch(
+  () => route.params,
+  () => {
+    init()
+  },
+  { immediate: true },
+)
+
+function handleGoodjob() {
+  if (detail.value.is_like)
+    return
+  request({
+    url: '/txwx/dz/add',
+    data: {
+      txwx_dz: {
+        td_kclx: theType?.idx,
+        td_dz_id: detail.value[theType.id],
+        td_keyword: detail.value[theType.title],
+      },
+    },
+  }).then((res) => {
+    if (res?.code === '1') {
+      showSuccessToast('点赞成功')
+      detail.value.is_like = 1
+      detail.value.tmz_dzl++
+    }
+  })
+}
+
+async function handleStartRtc() {
+  initRTC()
+  const rtmpUrl = splitRtmpPath(detail.value.push_rtmp_address)
+  const url = `https://api.sd-rtn.com/cn/v1/projects/${detail.value.rtc_appid}/rtmp-converters`
+  const options = {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      // 'X-Request-ID': '',
+      'Accept': 'application/json',
+      'Authorization': 'Basic YmFhOWRkMmIxODgwNDg4ZDg5MmVmMWU4MWExNDljM2Q6MjRlNjgzNmU5MzdhNDhlNmIwMDdlYTI2NWUwNjA0NTE=',
+    },
+    body: JSON.stringify({
+      converter: {
+        name: detail.value.channelname,
+        transcodeOptions: {
+          rtcChannel: detail.value.channelname,
+          audioOptions: { codecProfile: 'HE-AAC', sampleRate: 48000, bitrate: 128, audioChannels: 1, rtcStreamUids: [detail.value.uidStr], volumes: [{ volume: 50, rtcStreamUid: detail.value.uidStr }] },
+          videoOptions: {
+            canvas: { width: 16 * 40, height: 9 * 40, color: 0 },
+            layout: [
+              { rtcStreamUid: detail.value.uidStr, region: { xPos: 0, yPos: 0, zIndex: 1, width: 16 * 40, height: 9 * 40 }, fillMode: 'fill' },
+            ],
+            codec: 'H.264',
+            codecProfile: 'main',
+            frameRate: 15,
+            gop: 30,
+            bitrate: 400,
+          },
+        },
+        rtmpUrl,
+        idleTimeout: 5,
+        jitterBufferSizeMs: 1000,
+      },
+    }),
+  }
+
+  try {
+    const response = await fetch(url, options)
+    const data = await response.json()
+    console.log(data)
+  }
+  catch (error) {
+    console.error(error)
+  }
+}
+function handleEndRtc() {
+  rtc.client?.leave()
+  rtc.localAudioTrack?.close()
+  rtc.localVideoTrack?.close()
+  isRtcing.value = false
+}
+
+function handleNavRightClick() {
+  if (isRtcUser.value) {
+    if (isRtcing.value)
+      handleEndRtc()
+    else
+      handleStartRtc()
+  }
+  else { openShare() }
+}
+</script>
+
+<template>
+  <van-nav-bar left-arrow @click-left="() => router.back()" @click-right="handleNavRightClick">
+    <template #right>
+      <template v-if="!loading">
+        <span v-if="isRtcUser">{{ isRtcing ? '结束直播' : '开始直播' }}</span>
+        <van-icon v-else name="share-o" size="18" />
+      </template>
+    </template>
+  </van-nav-bar>
+  <van-share-sheet
+    v-model:show="showShare" title="立即分享给好友"
+    :options="[{ name: '复制链接', icon: 'link' }, { name: '二维码', icon: 'qrcode' }]" @select="handleShareSelect"
+  />
+  <van-dialog v-model:show="qrshow" title="请截图保存并分享二维码">
+    <div class="flex flex-col items-center py-32px">
+      <img :src="qrsrc">
+      <div>
+        {{ detail[theType.title] }}
+      </div>
+    </div>
+  </van-dialog>
+
+  <div v-if="loading" class="flex justify-center pt-32px">
+    <van-loading type="spinner" />
+  </div>
+  <template v-else>
+    <div v-if="isRtcUser" ref="rtcPlayerRef" class="w-100vw h-56.25vw bg-dark-900" />
+    <video-player v-else :src="getFullPath(detail[theType.video])" :poster="getFullPath(detail[theType.img])" />
+
+    <div class="px-14px py-20px">
+      <div class="text-14px font-bold">
+        {{ detail[theType.title] }}
+      </div>
+      <div class="flex justify-between items-center text-14px px-2px py-12px">
+        <div class="text-gray-400">
+          {{ detail[theType.teacher] }}
+        </div>
+        <div class="flex  text-hex-333  text-12px leading-12px">
+          <div class="flex items-center">
+            <van-icon name="eye-o" size="14" />
+            <div class="ml-2px">
+              {{ formatNumber(detail[theType.lll]) }}
+            </div>
+          </div>
+          <div
+            class="flex items-center ml-6px" :class="detail.is_like === 1 ? 'text-red-500' : ''"
+            @click="handleGoodjob"
+          >
+            <van-icon :name="detail.is_like === 1 ? 'good-job' : 'good-job-o'" size="14" />
+            <div class="ml-2px">
+              {{ formatNumber(detail[theType.dzl]) }}
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <div class="flex items-center justify-between px-14px ">
+      <div class="text-16px font-bold">
+        相关章节
+      </div>
+      <van-icon
+        name="arrow" size="18"
+        @click="router.push({ name: 'detail-type-tmk_id-list', params: { type: _type_, tmk_id: truthPid } })"
+      />
+    </div>
+
+    <div class="px-14px py-20px grid grid-cols-2 gap-10px">
+      <div
+        v-for="item in rows" :key="item[theType.id]"
+        class="relative w-44vw rounded-8px overflow-hidden shadow flex flex-col"
+        @click="router.replace(`/detail/${_type_}/${truthPid}/${item[theType.id]}`)"
+      >
+        <div class="absolute top-0 right-0 flex text-12px px-6px py-4px text-white bg-hex-00000050">
+          <div class="flex items-center">
+            <van-icon name="eye-o" size="12" />
+            <div class="ml-4px w-24px tracking-tighter">
+              {{ formatNumber(item[theType.lll]) }}
+            </div>
+          </div>
+          <div class="flex items-center ml-4px">
+            <van-icon name="good-job-o" size="12" />
+            <div class="ml-4px w-24px tracking-tighter">
+              {{ formatNumber(item[theType.dzl]) }}
+            </div>
+          </div>
+        </div>
+        <img :src="getFullPath(item[theType.img])" alt="" class="w-full h-24.75vw">
+        <div class="p-8px  flex-auto flex flex-col justify-between">
+          <div class="text-14px font-bold pb-8px leading-16px">
+            {{ item[theType.title] }}
+          </div>
+          <div class="flex justify-between text-12px ">
+            <div class="whitespace-nowrap mr-8px">
+              {{ item[theType.teacher] }}
+            </div>
+            <div>{{ item[theType.school] }}</div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </template>
+</template>

+ 309 - 0
src/pages/detail/zbkc_ks/[tmk_id]/[tmz_id].vue

@@ -0,0 +1,309 @@
+<script setup lang='ts'>
+import { showFailToast, showSuccessToast } from 'vant'
+import QRCode from 'qrcode'
+import 'aliyun-webrtc-sdk'
+import { TypeMap, formatNumber, getFullPath } from '~/pages/detail/util'
+import { user } from '~/store'
+
+const props = defineProps<{
+  // type: string
+  tmk_id: string
+  tmz_id: string
+}>()
+let truthPid = props.tmk_id === '-' ? undefined : props.tmk_id
+
+const _type_ = 'zbkc_ks'
+const theType = TypeMap[_type_]
+
+const router = useRouter()
+const route = useRoute()
+
+const showShare = ref(false)
+function openShare() {
+  showShare.value = true
+}
+
+const qrshow = ref(false)
+const qrsrc = ref('')
+function handleShareSelect(option: any) {
+  const link = location.href
+  switch (option.icon) {
+    case 'link':
+      navigator.clipboard.writeText(link).then(() => {
+        showShare.value = false
+        showSuccessToast('已复制到剪贴板')
+      }).catch(() => {
+        showFailToast('复制失败')
+      })
+
+      break
+    case 'qrcode':
+      QRCode.toDataURL(link, (err, url) => {
+        if (err)
+          showFailToast('生成二维码失败')
+        qrsrc.value = url
+        showShare.value = false
+        qrshow.value = true
+      })
+      break
+    default:
+      break
+  }
+}
+
+const loading = ref(true)
+const detail = ref<any>()
+const rows = ref<any[]>([])
+
+const isRtcUser = ref(false)
+const isRtcing = ref(false)
+const rtcPlayerRef = ref()
+
+function fixTruthPid(pid: string) {
+  const routePid = truthPid
+  if (routePid !== pid) {
+    console.info('纠正pid', pid)
+    truthPid = pid
+  }
+  return truthPid
+}
+
+const aliWebrtc = new AliRtcEngine()
+
+async function initRTC() {
+  const response = await fetch(`https://push.txhlwxx.com/openapi/rtcsdk/login?user_id=${user.value?.user_id}&tzk_id=${props.tmz_id}`)
+  const data = await response.json()
+
+  const options = {
+    ...data.data,
+    channel: props.tmz_id,
+  }
+  console.log('rtc options : ', options)
+  aliWebrtc.startPreview(
+    rtcPlayerRef.value,
+  ).catch((error: any) => {
+    console.error('预览失败 : ', error)
+  })
+
+  await aliWebrtc.joinChannel(options, user.value?.user_realname).then(() => {
+  }, (error: any) => {
+    console.error('入会失败 : ', error)
+  })
+
+  isRtcing.value = true
+
+  return aliWebrtc
+}
+
+function init() {
+  loading.value = true
+  return request({
+    url: `/txwx/${_type_}/detail`,
+    data: {
+      [theType.id]: props.tmz_id,
+      live: 1,
+    },
+  }).then((res) => {
+    if (res?.code === '1') {
+      detail.value = res.data.one_info
+      isRtcUser.value = user.value?.user_id === detail.value.tzk_zjr_user_id
+      // isRtcUser.value = true
+      const otherData: any = {}
+      if (Array.isArray(theType.pid)) {
+        const t: string[] = []
+        theType.pid.forEach((item: string) => {
+          otherData[item] = detail.value[item]
+          t.push(detail.value[item])
+        })
+        fixTruthPid(t.join('-'))
+      }
+      else {
+        otherData[theType.pid] = detail.value[theType.pid]
+        fixTruthPid(detail.value[theType.pid])
+      }
+      return request({
+        url: `/txwx/${_type_}/index`,
+        data: {
+          // [theType.pid]: props.tmk_id,
+          ...otherData,
+          limit: 4,
+        },
+      }).then((res) => {
+        if (res?.code === '1')
+          rows.value = res.data.page_data
+      })
+    }
+  }).finally(() => {
+    loading.value = false
+  })
+}
+
+watch(
+  () => route.params,
+  () => {
+    init()
+  },
+  { immediate: true },
+)
+
+function handleGoodjob() {
+  if (detail.value.is_like)
+    return
+  request({
+    url: '/txwx/dz/add',
+    data: {
+      txwx_dz: {
+        td_kclx: theType?.idx,
+        td_dz_id: detail.value[theType.id],
+        td_keyword: detail.value[theType.title],
+      },
+    },
+  }).then((res) => {
+    if (res?.code === '1') {
+      showSuccessToast('点赞成功')
+      detail.value.is_like = 1
+      detail.value.tmz_dzl++
+    }
+  })
+}
+
+async function handleStartRtc() {
+  try {
+    aliWebrtc.isSupport()
+  }
+  catch (error) {
+    return showFailToast('当前浏览器不支持直播')
+  }
+
+  await initRTC()
+  await fetch(`https://push.txhlwxx.com/openapi/rtcsdk/push?user_id=${user.value?.user_id}&stream=zbkc_${truthPid}_${props.tmz_id}`)
+    .then(res => res.json())
+    .then((res) => {
+      console.log('推流res : ', res)
+      if (res?.code !== '1')
+        showFailToast(res.msg)
+    })
+    .catch((error) => {
+      console.error('推流失败 : ', error)
+    })
+}
+function handleEndRtc() {
+  fetch(`https://push.txhlwxx.com/openapi/rtcsdk/push?user_id=${user.value?.user_id}&stream=zbkc_${truthPid}_${props.tmz_id}&do=stop`)
+  aliWebrtc.leaveChannel()
+  isRtcing.value = false
+}
+
+function handleNavRightClick() {
+  if (isRtcUser.value) {
+    if (isRtcing.value)
+      handleEndRtc()
+    else
+      handleStartRtc()
+  }
+  else { openShare() }
+}
+</script>
+
+<template>
+  <van-nav-bar left-arrow @click-left="() => router.back()" @click-right="handleNavRightClick">
+    <template #right>
+      <template v-if="!loading">
+        <span v-if="isRtcUser">{{ isRtcing ? '结束直播' : '开始直播' }}</span>
+        <van-icon v-else name="share-o" size="18" />
+      </template>
+    </template>
+  </van-nav-bar>
+  <van-share-sheet
+    v-model:show="showShare" title="立即分享给好友"
+    :options="[{ name: '复制链接', icon: 'link' }, { name: '二维码', icon: 'qrcode' }]" @select="handleShareSelect"
+  />
+  <van-dialog v-model:show="qrshow" title="请截图保存并分享二维码">
+    <div class="flex flex-col items-center py-32px">
+      <img :src="qrsrc">
+      <div>
+        {{ detail[theType.title] }}
+      </div>
+    </div>
+  </van-dialog>
+
+  <div v-if="loading" class="flex justify-center pt-32px">
+    <van-loading type="spinner" />
+  </div>
+  <template v-else>
+    <video v-if="isRtcUser" ref="rtcPlayerRef" class="w-100vw h-56.25vw bg-dark-900" />
+    <video-player v-else :src="getFullPath(detail[theType.video])" :poster="getFullPath(detail[theType.img])" />
+
+    <div class="px-14px py-20px">
+      <div class="text-14px font-bold">
+        {{ detail[theType.title] }}
+      </div>
+      <div class="flex justify-between items-center text-14px px-2px py-12px">
+        <div class="text-gray-400">
+          {{ detail[theType.teacher] }}
+        </div>
+        <div class="flex  text-hex-333  text-12px leading-12px">
+          <div class="flex items-center">
+            <van-icon name="eye-o" size="14" />
+            <div class="ml-2px">
+              {{ formatNumber(detail[theType.lll]) }}
+            </div>
+          </div>
+          <div
+            class="flex items-center ml-6px" :class="detail.is_like === 1 ? 'text-red-500' : ''"
+            @click="handleGoodjob"
+          >
+            <van-icon :name="detail.is_like === 1 ? 'good-job' : 'good-job-o'" size="14" />
+            <div class="ml-2px">
+              {{ formatNumber(detail[theType.dzl]) }}
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <div class="flex items-center justify-between px-14px ">
+      <div class="text-16px font-bold">
+        相关章节
+      </div>
+      <van-icon
+        name="arrow" size="18"
+        @click="router.push({ name: 'detail-type-tmk_id-list', params: { type: _type_, tmk_id: truthPid } })"
+      />
+    </div>
+
+    <div class="px-14px py-20px grid grid-cols-2 gap-10px">
+      <div
+        v-for="item in rows" :key="item[theType.id]"
+        class="relative w-44vw rounded-8px overflow-hidden shadow flex flex-col"
+        @click="router.replace(`/detail/${_type_}/${truthPid}/${item[theType.id]}`)"
+      >
+        <div class="absolute top-0 right-0 flex text-12px px-6px py-4px text-white bg-hex-00000050">
+          <div class="flex items-center">
+            <van-icon name="eye-o" size="12" />
+            <div class="ml-4px w-24px tracking-tighter">
+              {{ formatNumber(item[theType.lll]) }}
+            </div>
+          </div>
+          <div class="flex items-center ml-4px">
+            <van-icon name="good-job-o" size="12" />
+            <div class="ml-4px w-24px tracking-tighter">
+              {{ formatNumber(item[theType.dzl]) }}
+            </div>
+          </div>
+        </div>
+        <img :src="getFullPath(item[theType.img])" alt="" class="w-full h-24.75vw">
+        <div class="p-8px  flex-auto flex flex-col justify-between">
+          <div class="text-14px font-bold pb-8px leading-16px">
+            {{ item[theType.title] }}
+          </div>
+          <div class="flex justify-between text-12px ">
+            <div class="whitespace-nowrap mr-8px">
+              {{ item[theType.teacher] }}
+            </div>
+            <div>{{ item[theType.school] }}</div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </template>
+</template>

+ 1 - 1
src/request/request.ts

@@ -4,7 +4,7 @@ import { showFailToast } from 'vant'
 import { user } from '~/store'
 
 // const token = user?.token
-const token = user.value?.token ?? '1a84JbyyAnC_bLLBTSqqDttK2cM6qAsBUOqM_aAk593putHUdkOZWPnSPUVFbcyOGrb_atG6OXbOyrkDRIwHwV9brYcC2ZAal8'
+const token = user.value?.token // ?? '1a84JbyyAnC_bLLBTSqqDttK2cM6qAsBUOqM_aAk593putHUdkOZWPnSPUVFbcyOGrb_atG6OXbOyrkDRIwHwV9brYcC2ZAal8'
 
 const service = axios.create({
   baseURL: window.GLOBAL_CONFIG.api,

+ 3 - 0
vite.config.ts

@@ -11,6 +11,7 @@ import WindiCSS from 'vite-plugin-windicss'
 import VueMacros from 'unplugin-vue-macros/vite'
 import { VantResolver } from 'unplugin-vue-components/resolvers'
 import legacyPlugin from '@vitejs/plugin-legacy'
+import basicSsl from '@vitejs/plugin-basic-ssl'
 
 export default defineConfig({
   base: '',
@@ -20,9 +21,11 @@ export default defineConfig({
     },
   },
   server: {
+    https: true,
     hmr: { overlay: false }, // 禁用或配置 HMR 连接 设置 server.hmr.overlay 为 false 可以禁用服务器错误遮罩层
   },
   plugins: [
+    basicSsl(),
     legacyPlugin({
       targets: ['chrome 52'], // 需要兼容的目标列表,可以设置多个
       additionalLegacyPolyfills: ['regenerator-runtime/runtime'], // 面向IE11时需要此插件