init commit
This commit is contained in:
commit
1eed1428f2
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
32
README.md
Normal file
32
README.md
Normal file
@ -0,0 +1,32 @@
|
||||
# BLive Coyote
|
||||
郊狼B站直播玩法:送礼物控制郊狼主机输出
|
||||
|
||||
## 项目简介
|
||||
> [!TIP]
|
||||
> 本项目仍在开发中,目前可体验基础功能。
|
||||
>
|
||||
> 本项目为个人学习项目,如有错误敬请批评指正,欢迎 PR。
|
||||
|
||||
本项目以 [Bilibili 直播&互玩 JavaScript Demo](https://open-live.bilibili.com/document/a7bd5377-ad7d-a273-25ae-28caf37a7a85) 和 [DG-LAB SOCKET控制-控制端开源](https://github.com/DG-LAB-OPENSOURCE/DG-LAB-OPENSOURCE/tree/main/socket) 为基础,实现了直播间送礼物控制郊狼主机输出的功能。
|
||||
|
||||
- 郊狼 WebSocket 后端请参考:[DG-LAB-OPENSOURCE](https://github.com/DG-LAB-OPENSOURCE/DG-LAB-OPENSOURCE/tree/main/socket/BackEnd(Node))
|
||||
- 哔哩哔哩直播互动玩法服务端请参考:[JavaScript Demo](https://open-live.bilibili.com/document/a7bd5377-ad7d-a273-25ae-28caf37a7a85)
|
||||
|
||||
## 目录结构
|
||||
```
|
||||
Client
|
||||
├─public
|
||||
│ └─css
|
||||
└─src
|
||||
├─assets
|
||||
├─socket
|
||||
└─types
|
||||
```
|
||||
|
||||
## 项目启动
|
||||
```bash
|
||||
cd Client
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
主播身份码及 app_id 获取请参考 [开放平台-直播&互玩接入文档:常见问题](https://open-live.bilibili.com/document/5dffc297-6fd2-41ff-bd45-6e8b89e2a68e)
|
17
index.html
Normal file
17
index.html
Normal file
@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/notyf.min.css">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite App</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
3283
package-lock.json
generated
Normal file
3283
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
25
package.json
Normal file
25
package.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "poros-demo-client",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"dev": "vite --port 8888 --host 0.0.0.0 ",
|
||||
"build": "vue-tsc --noEmit && vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"vue": "^3.2.25"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@originjs/vite-plugin-commonjs": "1.0.3",
|
||||
"@types/node": "17.0.25",
|
||||
"@vitejs/plugin-vue": "2.3.0",
|
||||
"axios": "0.26.1",
|
||||
"typescript": "4.5.4",
|
||||
"vite": "2.9.0",
|
||||
"vue-tsc": "0.29.8",
|
||||
"ws": "8.17.0",
|
||||
"qrcode": "1.5.3",
|
||||
"notyf": "3.0.0"
|
||||
}
|
||||
}
|
1
public/css/notyf.min.css
vendored
Normal file
1
public/css/notyf.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
611
public/css/style.css
Normal file
611
public/css/style.css
Normal file
@ -0,0 +1,611 @@
|
||||
/* Copyright 2013 The Chromium Authors. All rights reserved.
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file. */
|
||||
|
||||
html,
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
background-color: #171717;
|
||||
}
|
||||
|
||||
.icon {
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.icon-offline {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
button {
|
||||
cursor: pointer;
|
||||
background-color: #fce9a7;
|
||||
color: #000;
|
||||
padding: 2px 10px;
|
||||
border-radius: 3px;
|
||||
-webkit-border-radius: 3px;
|
||||
-moz-border-radius: 3px;
|
||||
-ms-border-radius: 3px;
|
||||
-o-border-radius: 3px;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #ffe668;
|
||||
}
|
||||
|
||||
select {
|
||||
background-color: #fce9a7;
|
||||
border-radius: 3px;
|
||||
-webkit-border-radius: 3px;
|
||||
-moz-border-radius: 3px;
|
||||
-ms-border-radius: 3px;
|
||||
-o-border-radius: 3px;
|
||||
}
|
||||
|
||||
|
||||
/* Offline page */
|
||||
|
||||
.offline .interstitial-wrapper {
|
||||
color: #2b2b2b;
|
||||
font-size: 1em;
|
||||
line-height: 1.55;
|
||||
margin: 0 auto;
|
||||
max-width: 600px;
|
||||
padding-top: 200px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.offline .runner-container {
|
||||
height: 150px;
|
||||
max-width: 600px;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
top: 180px;
|
||||
width: 44px;
|
||||
transition: top 0.1s ease-in-out;
|
||||
-webkit-transition: top 0.1s ease-in-out;
|
||||
-moz-transition: top 0.1s ease-in-out;
|
||||
-ms-transition: top 0.1s ease-in-out;
|
||||
-o-transition: top 0.1s ease-in-out;
|
||||
}
|
||||
|
||||
.offline .runner-canvas {
|
||||
height: 150px;
|
||||
max-width: 600px;
|
||||
opacity: 1;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.offline .controller {
|
||||
background: rgba(247, 247, 247, .1);
|
||||
height: 100vh;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100vw;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
#offline-resources {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (max-width: 420px) {
|
||||
|
||||
.suggested-left>#control-buttons,
|
||||
.suggested-right>#control-buttons {
|
||||
float: none;
|
||||
}
|
||||
|
||||
.snackbar {
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-height: 350px) {
|
||||
h1 {
|
||||
margin: 0 0 15px;
|
||||
}
|
||||
|
||||
.icon-offline {
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
|
||||
.interstitial-wrapper {
|
||||
margin-top: 5%;
|
||||
}
|
||||
|
||||
.nav-wrapper {
|
||||
margin-top: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 600px) and (max-width: 736px) and (orientation: landscape) {
|
||||
.offline .interstitial-wrapper {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 420px) and (max-width: 736px) and (min-height: 240px) and (max-height: 420px) and (orientation:landscape) {
|
||||
.interstitial-wrapper {
|
||||
margin-bottom: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-height: 240px) and (orientation: landscape) {
|
||||
.offline .interstitial-wrapper {
|
||||
margin-bottom: 90px;
|
||||
}
|
||||
|
||||
.icon-offline {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-height: 320px) and (orientation: landscape) {
|
||||
.icon-offline {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.offline .runner-container {
|
||||
top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 240px) {
|
||||
.interstitial-wrapper {
|
||||
overflow: inherit;
|
||||
padding: 0 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.arcade-mode,
|
||||
.arcade-mode .runner-container,
|
||||
.arcade-mode .runner-canvas {
|
||||
image-rendering: pixelated;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.arcade-mode #buttons,
|
||||
.arcade-mode #main-content {
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.arcade-mode .interstitial-wrapper {
|
||||
height: 100vh;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.arcade-mode .runner-container {
|
||||
left: 0;
|
||||
margin: auto;
|
||||
right: 0;
|
||||
transform-origin: top center;
|
||||
transition: transform 250ms cubic-bezier(0.4, 0, 1, 1) 400ms;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
#qrcode-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.85);
|
||||
z-index: 100;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1002;
|
||||
}
|
||||
|
||||
#qrcode-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1002;
|
||||
}
|
||||
|
||||
#qrcode {
|
||||
position: relative;
|
||||
z-index: 1002;
|
||||
border: 3px solid #fff;
|
||||
}
|
||||
|
||||
#qrcode-text {
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
#qrcode-text p {
|
||||
white-space: pre-wrap;
|
||||
/* 自动换行 */
|
||||
}
|
||||
|
||||
.close-qrcode {
|
||||
color: #000000;
|
||||
cursor: pointer;
|
||||
background-color: #ffe99d;
|
||||
padding: 5px 30px;
|
||||
border-radius: 3px;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.header-container {
|
||||
position: fixed;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 18%;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
min-height: 140px;
|
||||
min-width: 500px;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 145px;
|
||||
padding: 10px;
|
||||
background-color: #ffe99d;
|
||||
border-radius: 5px;
|
||||
-webkit-border-radius: 5px;
|
||||
-moz-border-radius: 5px;
|
||||
-ms-border-radius: 5px;
|
||||
-o-border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.status-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
margin-left: 20px;
|
||||
padding: 12px 20px;
|
||||
width: 15%;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border: 1px solid #ffe99d;
|
||||
border-radius: 5px;
|
||||
-webkit-border-radius: 5px;
|
||||
-moz-border-radius: 5px;
|
||||
-ms-border-radius: 5px;
|
||||
-o-border-radius: 5px;
|
||||
right: 0;
|
||||
color: #ffe99d;
|
||||
}
|
||||
|
||||
.red {
|
||||
color: rgb(255, 67, 67);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.red-background {
|
||||
background-color: rgb(255, 70, 70);
|
||||
}
|
||||
|
||||
.connect-btn {
|
||||
width: 100%;
|
||||
border: none;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.connect-btn button:first-child {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.connect-btn button {
|
||||
padding: 5px 10px;
|
||||
margin-top: 15px;
|
||||
width: 100%;
|
||||
border-radius: 3px;
|
||||
-webkit-border-radius: 3px;
|
||||
-moz-border-radius: 3px;
|
||||
-ms-border-radius: 3px;
|
||||
-o-border-radius: 3px;
|
||||
}
|
||||
|
||||
.dg-controller {
|
||||
width: 85%;
|
||||
z-index: 1000;
|
||||
color: #ffe99d;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
border: 1px solid #ffe99d;
|
||||
border-radius: 5px;
|
||||
-webkit-border-radius: 5px;
|
||||
-moz-border-radius: 5px;
|
||||
-ms-border-radius: 5px;
|
||||
-o-border-radius: 5px;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 1020px */
|
||||
@media screen and (max-width: 1020px) {
|
||||
.dg-controller {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.status-container {
|
||||
width: 20%;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 792px) {
|
||||
.dg-controller {
|
||||
width: 72%;
|
||||
}
|
||||
|
||||
.status-container {
|
||||
width: 28%;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
.dg-controller {
|
||||
width: 65%;
|
||||
}
|
||||
|
||||
.status-container {
|
||||
width: 35%;
|
||||
}
|
||||
}
|
||||
|
||||
.dg-controller {
|
||||
scrollbar-width: thin;
|
||||
/* Firefox */
|
||||
scrollbar-color: #ffe99d transparent;
|
||||
/* Firefox */
|
||||
}
|
||||
|
||||
.dg-controller::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
/* Chrome, Safari */
|
||||
}
|
||||
|
||||
.dg-controller::-webkit-scrollbar-track {
|
||||
background-color: transparent;
|
||||
/* Chrome, Safari */
|
||||
}
|
||||
|
||||
.dg-controller::-webkit-scrollbar-thumb {
|
||||
background-color: #ffe99d;
|
||||
/* Chrome, Safari */
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
/* Firefox */
|
||||
.dg-controller {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #ffe99d transparent;
|
||||
}
|
||||
|
||||
.btn-container {
|
||||
padding: 10px 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 85%;
|
||||
}
|
||||
|
||||
.inputTime {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.inputTime input,
|
||||
.btn-container input {
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.btn-container>* {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
#custom-msg {
|
||||
margin-top: 10px;
|
||||
min-width: 200px;
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
.intro-game {
|
||||
color: #ffe99d;
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
left: 20px;
|
||||
bottom: 10px;
|
||||
}
|
||||
|
||||
.game-title {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.game-tips {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tips-hide {
|
||||
font-weight: bold;
|
||||
margin-left: 15px;
|
||||
border-radius: 3px;
|
||||
padding: 2px 12px;
|
||||
-webkit-border-radius: 3px;
|
||||
-moz-border-radius: 3px;
|
||||
-ms-border-radius: 3px;
|
||||
-o-border-radius: 3px;
|
||||
}
|
||||
|
||||
.toggle-container {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 46px;
|
||||
height: 22px;
|
||||
background-color: #3b3b3b;
|
||||
border-radius: 13px;
|
||||
cursor: pointer;
|
||||
-webkit-flex-shrink: 0;
|
||||
/* Safari 和 Chrome */
|
||||
-ms-flex-negative: 0;
|
||||
/* IE 10+ */
|
||||
flex-shrink: 0;
|
||||
-moz-box-flex: 0;
|
||||
/* Firefox */
|
||||
}
|
||||
|
||||
.toggle-switch {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
left: 5px;
|
||||
bottom: 3px;
|
||||
background-color: #ddd;
|
||||
border-radius: 50%;
|
||||
transition: .4s;
|
||||
-webkit-transition: .4s;
|
||||
-moz-transition: .4s;
|
||||
-ms-transition: .4s;
|
||||
-o-transition: .4s;
|
||||
}
|
||||
|
||||
.toggle-container.on {
|
||||
background-color: #fce9a7;
|
||||
}
|
||||
|
||||
.toggle-container.on .toggle-switch {
|
||||
transform: translateX(20px);
|
||||
-webkit-transform: translateX(20px);
|
||||
-moz-transform: translateX(20px);
|
||||
-ms-transform: translateX(20px);
|
||||
-o-transform: translateX(20px);
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
.question-img {
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
position: absolute;
|
||||
background-color: #333;
|
||||
color: #ffe99d;
|
||||
padding: 5px;
|
||||
border-radius: 4px;
|
||||
display: none;
|
||||
/* 默认隐藏 */
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
select {
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
.information {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
padding: 10px 30px 30px 30px;
|
||||
color: #ffe99d;
|
||||
background-color: rgba(0, 0, 0);
|
||||
border: 1px solid #ffe99d;
|
||||
border-radius: 10px;
|
||||
transform: translate(-50%, -50%);
|
||||
-webkit-transform: translate(-50%, -50%);
|
||||
-moz-transform: translate(-50%, -50%);
|
||||
-ms-transform: translate(-50%, -50%);
|
||||
-o-transform: translate(-50%, -50%);
|
||||
-webkit-border-radius: 10px;
|
||||
-moz-border-radius: 10px;
|
||||
-ms-border-radius: 10px;
|
||||
-o-border-radius: 10px;
|
||||
z-index: 1003;
|
||||
}
|
||||
|
||||
.information a {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.information a:hover {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.information-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.85);
|
||||
z-index: 100;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
visibility: hidden;
|
||||
z-index: 1002;
|
||||
}
|
||||
|
||||
.information {
|
||||
color: #dddddd;
|
||||
}
|
||||
|
||||
.information h2 {
|
||||
color: #ffe99d;
|
||||
}
|
||||
|
||||
.information .notify{
|
||||
color: #ffe99d;
|
||||
}
|
||||
|
||||
.info-close {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.info-close-btn {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
background-color: #000;
|
||||
border: 1px solid #ffe99d;
|
||||
color: #ffe99d;
|
||||
cursor: pointer;
|
||||
padding: 10px 30px;
|
||||
border-radius: 5px;
|
||||
-webkit-border-radius: 5px;
|
||||
-moz-border-radius: 5px;
|
||||
-ms-border-radius: 5px;
|
||||
-o-border-radius: 5px;
|
||||
}
|
||||
|
||||
.info-close-btn:hover {
|
||||
background-color: #ffe99d;
|
||||
color: #000;
|
||||
}
|
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
229
src/App.vue
Normal file
229
src/App.vue
Normal file
@ -0,0 +1,229 @@
|
||||
<script setup lang="ts">
|
||||
// This starter template is using Vue 3 <script setup> SFCs
|
||||
// Check out https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup
|
||||
import { ref } from "vue"
|
||||
import axios from "axios"
|
||||
import { createSocket, destroySocket } from "./socket/index"
|
||||
import { createCoyoteSocket, closeCoyoteSocket, qrcodeSrc, qrcodeShow } from "./socket/coyote"
|
||||
import { Notyf } from 'notyf'
|
||||
|
||||
const notyf = new Notyf({ duration: 3000 })
|
||||
|
||||
|
||||
// API
|
||||
const api = axios.create({
|
||||
baseURL: "http://localhost:3000",
|
||||
})
|
||||
|
||||
// 替换你的主播身份码
|
||||
const codeId = ref("")
|
||||
// 替换你的app应用 [这里测试为互动游戏]
|
||||
const appId = ref("")
|
||||
// [向 node server请求接口后自动返回]
|
||||
const gameId = ref("")
|
||||
// v2改为server response 服务器返回websocket信息,而非手动获取
|
||||
const authBody = ref("")
|
||||
const wssLinks = ref([])
|
||||
// heartBeat Timer
|
||||
const heartBeatTimer = ref<NodeJS.Timer>()
|
||||
// be ready
|
||||
clearInterval(heartBeatTimer.value!)
|
||||
|
||||
/**
|
||||
* 测试请求鉴权接口
|
||||
*/
|
||||
const getAuth = () => {
|
||||
api.post("/getAuth", {})
|
||||
.then(({ data }) => {
|
||||
console.log("-----鉴权成功-----")
|
||||
notyf.success({ message: "鉴权成功" })
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("-----鉴权失败-----")
|
||||
notyf.error({ message: "鉴权失败" })
|
||||
})
|
||||
}
|
||||
|
||||
const heartBeatThis = (game_id) => {
|
||||
// 心跳 是否成功
|
||||
api.post("/gameHeartBeat", {
|
||||
game_id,
|
||||
})
|
||||
.then(({ data }) => {
|
||||
console.log("-----心跳成功-----")
|
||||
console.log("返回:", data)
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("-----心跳失败-----")
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @comment 注意所有的接口基于鉴权成功后才能正确返回
|
||||
* 测试请求游戏开启接口
|
||||
*/
|
||||
const gameStart = () => {
|
||||
api.post("/gameStart", {
|
||||
code: codeId.value,
|
||||
app_id: Number(appId.value),
|
||||
})
|
||||
.then(({ data }) => {
|
||||
if (data.code === 0) {
|
||||
const res = data.data
|
||||
const { game_info, websocket_info } = res
|
||||
const { auth_body, wss_link } = websocket_info
|
||||
authBody.value = auth_body
|
||||
wssLinks.value = wss_link
|
||||
console.log("-----游戏开始成功-----")
|
||||
console.log("返回GameId:", game_info)
|
||||
notyf.success({ message: "游戏开始成功" })
|
||||
gameId.value = game_info.game_id
|
||||
// v2改为20s请求心跳一次,不然60s会自动关闭
|
||||
heartBeatTimer.value = setInterval(() => {
|
||||
heartBeatThis(game_info.game_id)
|
||||
}, 20000)
|
||||
handleCreateSocket()
|
||||
} else {
|
||||
console.log("-----游戏开始失败-----")
|
||||
console.log("原因:", data)
|
||||
notyf.error({ message: "游戏开始失败" })
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("-----游戏开始失败-----")
|
||||
console.log(err)
|
||||
notyf.error({ message: "游戏开始失败" })
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @comment 基于gameStart成功后才会关闭正常,否则获取不到game_id
|
||||
* 测试请求游戏关闭接口
|
||||
*/
|
||||
const gameEnd = () => {
|
||||
api.post("/gameEnd", {
|
||||
game_id: gameId.value,
|
||||
app_id: Number(appId.value),
|
||||
})
|
||||
.then(({ data }) => {
|
||||
if (data.code === 0) {
|
||||
console.log("-----游戏关闭成功-----")
|
||||
console.log("返回:", data)
|
||||
// 清空长链
|
||||
authBody.value = ""
|
||||
wssLinks.value = []
|
||||
clearInterval(heartBeatTimer.value!) // 这里会报个错误,加了个非空断言
|
||||
handleDestroySocket()
|
||||
console.log("-----心跳关闭成功-----")
|
||||
notyf.success({ message: "游戏关闭成功" })
|
||||
} else {
|
||||
console.log("-----游戏关闭失败-----")
|
||||
console.log("原因:", data)
|
||||
notyf.error({ message: "游戏关闭失败" })
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("-----游戏关闭失败-----")
|
||||
console.log(err)
|
||||
notyf.error({ message: "游戏关闭失败" })
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试创建长长连接接口
|
||||
*/
|
||||
const handleCreateSocket = () => {
|
||||
if (authBody.value && wssLinks.value) {
|
||||
createSocket(authBody.value, wssLinks.value)
|
||||
console.log("-----长连接创建成功-----")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试销毁长长连接接口
|
||||
*/
|
||||
const handleDestroySocket = () => {
|
||||
destroySocket()
|
||||
console.log("-----长连接销毁成功-----")
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试按钮
|
||||
*/
|
||||
const test = () => {
|
||||
let map = {
|
||||
"123": "AAWWWWW",
|
||||
"123456": "aaaaaaaa",
|
||||
}
|
||||
notyf.success({ message: '测试吐司', duration: 3000, ripple: true })
|
||||
notyf.success({ message: map[123], duration: 3000, ripple: true })
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示二维码
|
||||
*/
|
||||
const showqrcode = () => {
|
||||
qrcodeShow.value = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏二维码
|
||||
*/
|
||||
const hideqrcode = () => {
|
||||
qrcodeShow.value = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="qrcode-overlay" v-show="qrcodeShow">
|
||||
<div id="qrcode-container">
|
||||
<div id="qrcode-text">
|
||||
<p>使用DG-LAB APP扫码建立WebSocket链接</p>
|
||||
<p>请先开始游戏与直播间建立连接</p>
|
||||
</div>
|
||||
<div id="qrcode-img">
|
||||
<img id="qrcode" :src="qrcodeSrc">
|
||||
</div>
|
||||
<div class="close-qrcode" @click="hideqrcode">关闭</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h2>游戏设置</h2>
|
||||
<div class="form">
|
||||
<label>主播身份码</label>
|
||||
<input type="password" placeholder="填写主播身份码" v-model="codeId"/>
|
||||
<label>app_id</label>
|
||||
<input type="text" placeholder="填写 app_id" v-model="appId" />
|
||||
<button @click="gameStart">游戏开始</button>
|
||||
<button @click="gameEnd">游戏结束</button>
|
||||
<button @click="createCoyoteSocket">连接郊狼</button>
|
||||
<button @click="closeCoyoteSocket">断开郊狼</button>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<h2>调试选项</h2>
|
||||
<div class="form">
|
||||
<button @click="getAuth">鉴权</button>
|
||||
<button @click="test">测试</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
#app {
|
||||
color: #fff;
|
||||
margin: 60px 50px;
|
||||
}
|
||||
.form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.form input,
|
||||
.form button {
|
||||
width: 300px;
|
||||
height: 50px;
|
||||
margin: 10px 0;
|
||||
font-size: 18px;
|
||||
}
|
||||
</style>
|
8
src/assets/danmaku-websocket.min.js
vendored
Normal file
8
src/assets/danmaku-websocket.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
19
src/assets/dataMap.ts
Normal file
19
src/assets/dataMap.ts
Normal file
@ -0,0 +1,19 @@
|
||||
// Desc: 用于存放数据映射表
|
||||
|
||||
// 礼物对应的波形
|
||||
const waveData = {
|
||||
"31037": `["0A0A0A0A00000000","0A0A0A0A0A0A0A0A","0A0A0A0A14141414","0A0A0A0A1E1E1E1E","0A0A0A0A28282828","0A0A0A0A32323232","0A0A0A0A3C3C3C3C","0A0A0A0A46464646","0A0A0A0A50505050","0A0A0A0A5A5A5A5A","0A0A0A0A64646464"]`,
|
||||
"31164": `["0A0A0A0A00000000","0D0D0D0D0F0F0F0F","101010101E1E1E1E","1313131332323232","1616161641414141","1A1A1A1A50505050","1D1D1D1D64646464","202020205A5A5A5A","2323232350505050","262626264B4B4B4B","2A2A2A2A41414141"]`,
|
||||
"32609": `["4A4A4A4A64646464","4545454564646464","4040404064646464","3B3B3B3B64646464","3636363664646464","3232323264646464","2D2D2D2D64646464","2828282864646464","2323232364646464","1E1E1E1E64646464","1A1A1A1A64646464"]`
|
||||
}
|
||||
|
||||
// 礼物id和礼物名的对应关系
|
||||
const giftData = {
|
||||
"31036": "小花花",
|
||||
"31039": "牛哇牛哇",
|
||||
"32609": "棒棒糖",
|
||||
"31037": "打call",
|
||||
"31164": "粉丝团灯牌",
|
||||
}
|
||||
|
||||
export { waveData }
|
1
src/assets/notyf.min.css
vendored
Normal file
1
src/assets/notyf.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
9
src/env.d.ts
vendored
Normal file
9
src/env.d.ts
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
declare module '*.vue' {
|
||||
import type { DefineComponent } from 'vue'
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
|
||||
const component: DefineComponent<{}, {}, any>
|
||||
export default component
|
||||
}
|
||||
|
5
src/main.ts
Normal file
5
src/main.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { createApp } from "vue"
|
||||
import App from "./App.vue"
|
||||
|
||||
createApp(App).mount("#app")
|
||||
|
171
src/socket/coyote.ts
Normal file
171
src/socket/coyote.ts
Normal file
@ -0,0 +1,171 @@
|
||||
import {ref} from "vue";
|
||||
import { Notyf } from 'notyf'
|
||||
import { waveData } from "../assets/dataMap";
|
||||
|
||||
const notyf = new Notyf({ duration: 4000 })
|
||||
const QRCode = require('qrcode')
|
||||
|
||||
let channelAStrength = 0; // A通道强度
|
||||
let channelBStrength = 0; // B通道强度
|
||||
|
||||
let connectionId = ""; // 从接口获取的连接标识符
|
||||
let targetWSId = ""; // 发送目标
|
||||
let fangdou = 500; //500毫秒防抖
|
||||
let fangdouSetTimeOut; // 防抖定时器
|
||||
let followAStrength = false; //跟随AB软上限
|
||||
let followBStrength = false;
|
||||
let wsConn; // 全局ws链接
|
||||
const feedBackMsg = {
|
||||
"feedback-0": "A通道:○",
|
||||
"feedback-1": "A通道:△",
|
||||
"feedback-2": "A通道:□",
|
||||
"feedback-3": "A通道:☆",
|
||||
"feedback-4": "A通道:⬡",
|
||||
"feedback-5": "B通道:○",
|
||||
"feedback-6": "B通道:△",
|
||||
"feedback-7": "B通道:□",
|
||||
"feedback-8": "B通道:☆",
|
||||
"feedback-9": "B通道:⬡",
|
||||
}
|
||||
|
||||
const qrcodeSrc = ref("")
|
||||
const qrcodeShow = ref(false)
|
||||
QRCode.toDataURL("https://www.dungeon-lab.com/app-download.php#DGLAB-SOCKET#ws://39.108.168.199:9999/", function (err, url) {
|
||||
//console.log(url)
|
||||
qrcodeSrc.value = url
|
||||
})
|
||||
|
||||
function createCoyoteSocket() {
|
||||
wsConn = new WebSocket('ws://coyote.babyfang.cn:9999/');
|
||||
wsConn.onopen = function (event) {
|
||||
console.log("WebSocket连接已建立");
|
||||
}
|
||||
|
||||
wsConn.onmessage = function (event) {
|
||||
let msg = {} as any
|
||||
try {
|
||||
msg = JSON.parse(event.data);
|
||||
} catch (e) {
|
||||
console.log(event.data);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(event.data)
|
||||
|
||||
switch (msg.type) {
|
||||
case 'bind':
|
||||
if (!msg.targetId) {
|
||||
connectionId = msg.clientId;
|
||||
console.log(`收到clientId:${connectionId}`);
|
||||
QRCode.toDataURL("https://www.dungeon-lab.com/app-download.php#DGLAB-SOCKET#ws://coyote.babyfang.cn:9999/" + connectionId, function (err, url) {
|
||||
//console.log(url)
|
||||
qrcodeSrc.value = url
|
||||
})
|
||||
qrcodeShow.value = true
|
||||
} else {
|
||||
if (msg.clientId != connectionId) {
|
||||
console.log("错误的clientId")
|
||||
return;
|
||||
}
|
||||
targetWSId = msg.targetId;
|
||||
console.log("收到targetId: " + msg.targetId + ", msg: " + msg.message);
|
||||
qrcodeShow.value = false
|
||||
notyf.success({message: "郊狼连接成功"})
|
||||
}
|
||||
break;
|
||||
case 'break':
|
||||
if (msg.targetId != targetWSId)
|
||||
return;
|
||||
console.log("收到断开连接指令")
|
||||
notyf.error({ message: "收到断开连接指令" })
|
||||
//location.reload();
|
||||
break;
|
||||
case 'error':
|
||||
if (msg.targetId != targetWSId)
|
||||
return;
|
||||
console.log("对方已断开,code:" + msg.message)
|
||||
notyf.error({ message: "对方已断开(" + msg.message + ")" })
|
||||
break;
|
||||
case 'msg':
|
||||
const result: { type: string; numbers: number[] }[] = []
|
||||
if(msg.message.includes("strength")) {
|
||||
const numbers = msg.message.match(/\d+/g).map(Number)
|
||||
result.push({ type: "strength", numbers: numbers })
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sendWsMsg(messageObj) {
|
||||
messageObj.clientId = connectionId;
|
||||
messageObj.targetId = targetWSId;
|
||||
if (!messageObj.hasOwnProperty('type'))
|
||||
messageObj.type = "msg";
|
||||
wsConn.send(JSON.stringify((messageObj)));
|
||||
}
|
||||
|
||||
function addOrIncrease(type, channelIndex, strength) {
|
||||
// 1 减少一 2 增加一 3 设置到
|
||||
// channel:1-A 2-B
|
||||
// 获取当前频道元素和当前值
|
||||
let channelStrength = channelIndex === 1 ? channelAStrength : channelBStrength;
|
||||
|
||||
// 如果是设置操作
|
||||
if (type === 3) {
|
||||
channelStrength = strength; //固定为0
|
||||
}
|
||||
// 减少一
|
||||
else if (type === 1) {
|
||||
channelStrength = Math.max(channelStrength - strength, 0);
|
||||
}
|
||||
// 增加一
|
||||
else if (type === 2) {
|
||||
channelStrength = Math.min(channelStrength + strength, 200);
|
||||
}
|
||||
|
||||
// 构造消息对象并发送
|
||||
const data = { type, strength: channelStrength, message: "set channel", channel: channelIndex };
|
||||
console.log(data)
|
||||
sendWsMsg(data);
|
||||
}
|
||||
|
||||
function clearAB(channelIndex) {
|
||||
const data = { type: 4, message: "clear-" + channelIndex }
|
||||
sendWsMsg(data);
|
||||
}
|
||||
|
||||
function sendWaveData(timeA, timeB, waveA, waveB) {
|
||||
if (fangdouSetTimeOut) {
|
||||
return
|
||||
}
|
||||
|
||||
const msg1 = `A:${waveData[waveA]}`
|
||||
const msg2 = `B:${waveData[waveB]}`
|
||||
const data = {
|
||||
type: "clientMsg", message: msg1, message2: msg2, time1: timeA, time2: timeB
|
||||
}
|
||||
|
||||
sendWsMsg(data)
|
||||
|
||||
fangdouSetTimeOut = setTimeout(() => {
|
||||
clearTimeout(fangdouSetTimeOut)
|
||||
fangdouSetTimeOut = null
|
||||
}, fangdou)
|
||||
}
|
||||
|
||||
function closeCoyoteSocket() {
|
||||
try {
|
||||
wsConn.close()
|
||||
}
|
||||
catch (e) {
|
||||
notyf.error( {message: "郊狼连接断开失败"} )
|
||||
console.log(e)
|
||||
return
|
||||
}
|
||||
wsConn = null
|
||||
notyf.success( {message: "郊狼连接已断开"} )
|
||||
}
|
||||
|
||||
|
||||
export { createCoyoteSocket, closeCoyoteSocket, sendWsMsg, sendWaveData, addOrIncrease, clearAB, qrcodeSrc, qrcodeShow, channelAStrength, channelBStrength }
|
133
src/socket/index.ts
Normal file
133
src/socket/index.ts
Normal file
@ -0,0 +1,133 @@
|
||||
import DanmakuWebSocket from "../assets/danmaku-websocket.min.js"
|
||||
import { Notyf } from 'notyf'
|
||||
import { closeCoyoteSocket, addOrIncrease, sendWaveData } from "./coyote"
|
||||
|
||||
let ws: DanmakuWebSocket
|
||||
const notyf = new Notyf({ duration: 4000 })
|
||||
|
||||
/**
|
||||
* 创建socket长连接
|
||||
* @param authBody
|
||||
* @param wssLinks
|
||||
*/
|
||||
function createSocket(authBody: string, wssLinks: string[]) {
|
||||
const opt = {
|
||||
...getWebSocketConfig(authBody, wssLinks),
|
||||
// 收到消息,
|
||||
onReceivedMessage: (res) => {
|
||||
console.log("收到"+ res.cmd +"消息:")
|
||||
//console.log(res.data.uname + "(大航海" +res.data.guard_level + "级):" + res.data.msg)
|
||||
|
||||
// if (res.data.msg == "#UPA1") {
|
||||
// try {
|
||||
// addOrIncrease(2, 1, 1)
|
||||
// notyf.success("A通道强度增加成功")
|
||||
// }
|
||||
// catch (e) {
|
||||
// console.log(e)
|
||||
// notyf.error("A通道强度增加失败")
|
||||
// }
|
||||
// }
|
||||
|
||||
if (res.cmd == "LIVE_OPEN_PLATFORM_SEND_GIFT") {
|
||||
if (res.data.gift_id == 31036) {
|
||||
// 小花花:减强度1
|
||||
try {
|
||||
addOrIncrease(1, 1, 1)
|
||||
addOrIncrease(1, 2, 1)
|
||||
notyf.success("收到花花,强度-1")
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e)
|
||||
notyf.error("强度操作失败!")
|
||||
}
|
||||
} else if (res.data.gift_id == 31039) {
|
||||
// 牛哇牛哇:加强度1
|
||||
try {
|
||||
addOrIncrease(2, 1, 1)
|
||||
addOrIncrease(2, 2, 1)
|
||||
notyf.success("收到牛牛,强度+1")
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e)
|
||||
notyf.error("强度操作失败!")
|
||||
}
|
||||
} else {
|
||||
// 其他礼物,发送波形数据
|
||||
try {
|
||||
sendWaveData(5, 5, res.data.gift_id, res.data.gift_id)
|
||||
notyf.success("收到礼物" + res.data.gift_name)
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e)
|
||||
notyf.error("发送波形数据失败!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(res)
|
||||
},
|
||||
// 收到心跳处理回调
|
||||
onHeartBeatReply: (data) => console.log("收到心跳处理回调:", data),
|
||||
onError: (data) => console.log("error", data),
|
||||
onListConnectError: () => {
|
||||
console.log("list connect error")
|
||||
destroySocket()
|
||||
},
|
||||
}
|
||||
|
||||
if (!ws) {
|
||||
ws = new DanmakuWebSocket(opt)
|
||||
}
|
||||
|
||||
return ws
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取websocket配置信息
|
||||
* @param authBody
|
||||
* @param wssLinks
|
||||
*/
|
||||
function getWebSocketConfig(authBody: string, wssLinks: string[]) {
|
||||
const url = wssLinks[0]
|
||||
const urlList = wssLinks
|
||||
const auth_body = JSON.parse(authBody)
|
||||
return {
|
||||
url,
|
||||
urlList,
|
||||
customAuthParam: [
|
||||
{
|
||||
key: "key",
|
||||
value: auth_body.key,
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
key: "group",
|
||||
value: auth_body.group,
|
||||
type: "string",
|
||||
},
|
||||
],
|
||||
rid: auth_body.roomid,
|
||||
protover: auth_body.protoover,
|
||||
uid: auth_body.uid,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁websocket
|
||||
*/
|
||||
function destroySocket() {
|
||||
console.log("destroy1")
|
||||
ws && ws.destroy()
|
||||
ws = undefined
|
||||
console.log("destroy2")
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取websocket实例
|
||||
*/
|
||||
function getWsClient() {
|
||||
return ws
|
||||
}
|
||||
|
||||
export { createSocket, destroySocket, getWebSocketConfig, getWsClient }
|
19
src/types/index.d.ts
vendored
Normal file
19
src/types/index.d.ts
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
declare interface ISocketData {
|
||||
// ip地址
|
||||
ip: number[]
|
||||
|
||||
// host地址 可能是ip 也可能是域名。
|
||||
host: string[]
|
||||
|
||||
// 长连使用的请求json体 第三方无需关注内容,建立长连时使用即可。
|
||||
auth_body: string
|
||||
|
||||
// tcp 端口号
|
||||
tcp_port: number[]
|
||||
|
||||
// ws 端口号
|
||||
ws_port: number[]
|
||||
|
||||
// wss 端口号
|
||||
wss_port: number[]
|
||||
}
|
20
tsconfig.json
Normal file
20
tsconfig.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2016",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"strict": true,
|
||||
"jsx": "preserve",
|
||||
"sourceMap": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"esModuleInterop": true,
|
||||
"lib": ["esnext", "dom"],
|
||||
"types": ["node"],
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"noImplicitAny": false,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
8
tsconfig.node.json
Normal file
8
tsconfig.node.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node"
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
17
vite.config.ts
Normal file
17
vite.config.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { defineConfig } from "vite"
|
||||
import vue from "@vitejs/plugin-vue"
|
||||
import { viteCommonjs } from "@originjs/vite-plugin-commonjs"
|
||||
import * as path from "path"
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
server: {
|
||||
// https: true
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "./src")
|
||||
}
|
||||
},
|
||||
plugins: [vue(), viteCommonjs()]
|
||||
})
|
Loading…
x
Reference in New Issue
Block a user