首次提交

This commit is contained in:
yj 2025-03-10 14:35:52 +08:00
parent 4f3932e0b2
commit 7945370fdf
52 changed files with 24152 additions and 2 deletions

3
.env.development Normal file
View File

@ -0,0 +1,3 @@
VITE_BASE_URL_API = 'http://192.168.2.9:5192'
VITE_ENV = 'development'

3
.env.production Normal file
View File

@ -0,0 +1,3 @@
VITE_BASE_URL_API = https://meeting-api.23544.com/pc
VITE_ENV = 'production'

3
.env.test Normal file
View File

@ -0,0 +1,3 @@
VITE_BASE_URL_API = https://meeting-api.23544.com/pc
VITE_ENV = 'test'

15
.gitignore vendored Normal file
View File

@ -0,0 +1,15 @@
# Windows
[Dd]esktop.ini
Thumbs.db
$RECYCLE.BIN/
# macOS
.DS_Store
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
# Node.js
node_modules/
dist/

View File

@ -1,2 +0,0 @@
# WGShare.Client.Pc

10
auto-imports.d.ts vendored Normal file
View File

@ -0,0 +1,10 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// Generated by unplugin-auto-import
export {}
declare global {
const ElLoading: typeof import('element-plus/es')['ElLoading']
const ElMessage: typeof import('element-plus/es')['ElMessage']
}

15
components.d.ts vendored Normal file
View File

@ -0,0 +1,15 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
export {}
declare module 'vue' {
export interface GlobalComponents {
ElButton: typeof import('element-plus/es')['ElButton']
ElEmpty: typeof import('element-plus/es')['ElEmpty']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
}
}

16
index.html Normal file
View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<!-- <link rel="icon" type="image/svg+xml" href="/vite.svg" /> -->
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title></title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

23366
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

51
package.json Normal file
View File

@ -0,0 +1,51 @@
{
"name": "vue3-project-template",
"private": true,
"version": "0.0.0",
"scripts": {
"dev": "vite",
"test": "vite --mode test",
"prod": "vite --mode production",
"build": "vite build",
"build:dev": "vite build --mode development",
"build:test": "vite build --mode test",
"build:prod": "vite build --mode production",
"preview": "vite preview",
"lint": "eslint . --ext src/**/*.{js,jsx,vue,ts,tsx} --fix"
},
"dependencies": {
"@element-plus/icons-vue": "^2.1.0",
"agora-rtc-sdk-ng": "^4.23.1",
"axios": "^1.4.0",
"element-plus": "^2.3.7",
"js-md5": "^0.7.3",
"pinia": "^2.1.4",
"vue": "^3.2.47",
"vue-router": "^4.2.2"
},
"devDependencies": {
"@iconify-json/ep": "^1.1.11",
"@types/node": "^20.3.1",
"@typescript-eslint/eslint-plugin": "^6.3.0",
"@typescript-eslint/parser": "^6.3.0",
"@vitejs/plugin-vue": "^4.1.0",
"@vue/cli-plugin-eslint": "^5.0.8",
"@vue/eslint-config-prettier": "^8.0.0",
"babel-eslint": "^10.1.0",
"eslint": "^8.46.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-plugin-import": "^2.28.0",
"eslint-plugin-prettier": "^5.0.0",
"eslint-plugin-vue": "^9.16.1",
"postcss-px-to-viewport": "^1.1.1",
"sass": "^1.63.6",
"terser": "^5.18.1",
"typescript": "^5.0.2",
"unplugin-auto-import": "^0.16.4",
"unplugin-icons": "^0.16.3",
"unplugin-vue-components": "^0.25.1",
"vite": "^4.3.9",
"vite-plugin-inspect": "^0.7.29",
"vue-tsc": "^1.4.2"
}
}

15
src/App.vue Normal file
View File

@ -0,0 +1,15 @@
<template>
<router-view />
</template>
<script setup lang="ts">
import { onMounted } from "vue";
onMounted(() => {
init();
})
const init = async () => {
};
</script>
<style lang="scss" scoped></style>

18
src/api/index.ts Normal file
View File

@ -0,0 +1,18 @@
import { request } from '@/utils'
export const GetRoomRtcToken = (roomNum: string) =>
request({
url: `/room/tk/rtc?roomNum=${roomNum}`,
method: 'get',
})
export const GetAgoraConf = () =>
request({
url: `/home/agora-conf`,
method: 'get',
})
export const GetPolling = (roomNum: string, count: string) =>
request({
url: `/room/polling?roomNum=${roomNum}&count=${count}`,
method: 'get'
})

BIN
src/assets/bg1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 860 KiB

BIN
src/assets/bg10.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

BIN
src/assets/bg11.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 KiB

BIN
src/assets/bg2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

BIN
src/assets/bg3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 836 KiB

BIN
src/assets/bg4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 456 KiB

BIN
src/assets/bg5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

BIN
src/assets/bg6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 KiB

BIN
src/assets/bg7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 KiB

BIN
src/assets/bg8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 336 KiB

BIN
src/assets/bg9.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 770 KiB

BIN
src/assets/icon1-active.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

BIN
src/assets/icon1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

BIN
src/assets/icon2-active.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

BIN
src/assets/icon2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

BIN
src/assets/icon3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
src/assets/icon4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 412 B

BIN
src/assets/icon5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 B

BIN
src/assets/icon6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

17
src/config/index.ts Normal file
View File

@ -0,0 +1,17 @@
// 常量配置
enum constant {
CONFIG_TITLE = 'vue3-vite-ts-pinia',
CONFIG_REQUEST_TIMEOUT_TIME = 10000, // 请求超时时间 10秒
CONFIG_TOKEN = 'token', // token
CONFIG_USERINFO = 'USERINFO', // 用户信息
CONFIG_STATUS_CODE_SUCCESS = 100, // 自定义代码 100成功、101失败
CONFIG_STATUS_CODE_ERROR = 101,
CONFIG_USERNAME_KEY = 'USERNAME', // 用户名
CONFIG_PASSWORD_KEY = 'PASSWORD', // 密码
CONFIG_IS_REMEMBER_KEY = 'REMEMBER', // 是否记住密码
CONFIG_CODE_SUCCESS = 200, // 成功码
}
// 常规配置
const config = {}
export { config, constant }

View File

@ -0,0 +1,4 @@
function user() {
return {}
}
export default user

15
src/main.ts Normal file
View File

@ -0,0 +1,15 @@
import { createApp } from 'vue'
import router from './router'
import './styles/index.scss'
import App from './App.vue'
import Store from './store'
// 清除项目中的console
if (import.meta.env.VITE_ENV !== 'development') {
console.log = function () { }
}
const app = createApp(App)
app.use(router).use(Store).mount('#app')

192
src/pages/Home/index.vue Normal file
View File

@ -0,0 +1,192 @@
<template>
<div class="home">
<div class="home-header">
<span>距离下次刷新监控人员还剩{{ timeNumber }}</span>
<el-button type="primary" @click="refreshUserList" style="margin-left: 10px;">刷新</el-button>
</div>
<div class="home-content" v-if="userList.length">
<div v-for="item in userList" class="home-content-item">
<div class="home-content-item-card" :id="'player-wrapper-' + item.uid">
<div class="home-content-item-card-name">{{ item.userName }}</div>
</div>
</div>
<di class="home-content-item">
<div class="home-content-item-card">
</div>
</di>
<di class="home-content-item">
<div class="home-content-item-card">
</div>
</di>
<di class="home-content-item">
<div class="home-content-item-card">
</div>
</di>
<di class="home-content-item">
<div class="home-content-item-card">
</div>
</di>
<div class="home-content-item">
<div class="home-content-item-card">
</div>
</div>
<div class="home-content-item">
<div class="home-content-item-card">
</div>
</div>
<div class="home-content-item">
<div class="home-content-item-card">
</div>
</div>
<div class="home-content-item">
<div class="home-content-item-card">
</div>
</div>
</div>
<div class="home-content" v-else style="justify-content: center;align-items: center;align-content: center;">
<el-empty description="暂无数据" />
</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref, onUnmounted } from "vue";
import { agora } from "@/utils/package/agora";
import { GetAgoraConf, GetPolling, GetRoomRtcToken } from "@/api";
import { storage } from '@/utils'
const userList = ref<any>([])
const channel = ref('')
const timer = ref<NodeJS.Timeout>()
const timeNumber = ref(30)
onMounted(() => {
init()
startCountdown()
});
onUnmounted(() => {
clearInterval(timer.value)
});
const startCountdown = () => {
clearInterval(timer.value);
timeNumber.value = 30;
timer.value = setInterval(() => {
timeNumber.value--;
if (timeNumber.value === 0) {
refreshUserList();
timeNumber.value = 30;
}
}, 1000);
};
const init = async () => {
const paramsString = location.search.startsWith('?') ? location.search.slice(1) : location.search;
const searchParams = new URLSearchParams(paramsString)
channel.value = searchParams.get('channel') as string;
storage.setItem('token', searchParams.get('token'))
agora.setOptions('uid', Number(searchParams.get('uid')))
agora.setOptions('channel', channel.value)
await getAgoraConf()
await getPolling()
await getRoomRtcToken()
agoraOn()
};
const getAgoraConf = async () => {
await GetAgoraConf().then(res => {
if (res.code === 200) {
agora.init();
agora.setOptions('appId', res.data)
}
})
};
const getPolling = async () => {
await GetPolling(channel.value, '9').then((res: any) => {
if (res.code === 200) {
userList.value = res.data.map((item: any) => {
return {
...item,
newScreenShareId: Number(1 + item.screenShareId)
}
})
}
})
};
const refreshUserList = async () => {
await agora.leave()
await getPolling()
await getRoomRtcToken()
};
const getRoomRtcToken = async () => {
GetRoomRtcToken(channel.value).then(res => {
if (res.code === 200) {
agora.setOptions('token', res.data)
agora.join()
}
})
};
const agoraOn = async () => {
agora.on({
userPublished: (user: any, mediaType: 'video' | 'audio') => {
if (mediaType === 'video') {
user.videoTrack.play(document.getElementById(`player-wrapper-${user.uid}`));
}
},
})
}
</script>
<style lang="scss" scoped>
.home {
background-color: rgb(7, 9, 11);
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
padding: 4px 120px;
box-sizing: border-box;
&-header {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
color: white;
padding: 0 20px;
}
&-content {
flex-grow: 1;
display: flex;
flex-wrap: wrap;
align-content: flex-start;
&-item {
height: calc(100% / 3);
width: calc(100% / 3);
box-sizing: border-box;
display: flex;
justify-content: center;
align-items: center;
&-card {
width: 516px;
height: 290px;
box-sizing: border-box;
position: relative;
background-color: #101317;
border: 1px solid #EBEBEB;
&-name {
background-color: rgb(253, 194, 41);
color: black;
font-size: 26px;
position: absolute;
left: 4px;
bottom: 4px;
padding: 0px 10px;
z-index: 1;
}
}
}
}
}
</style>

24
src/router/index.ts Normal file
View File

@ -0,0 +1,24 @@
import { RouteRecordRaw, createRouter, createWebHashHistory } from 'vue-router'
const routes: Array<RouteRecordRaw> = [
{
path: '/',
redirect: '/home' //重定向首页
},
{
path: '/home',
name: 'Home',
component: () => import("@/pages/Home/index.vue"), //首页
meta: {
title: '首页',
hasMenu: false,
},
}
]
const router = createRouter({
history: createWebHashHistory(),
routes,
})
export default router

6
src/shims-vue.d.ts vendored Normal file
View File

@ -0,0 +1,6 @@
/* eslint-disable */
declare module '*.vue' {
const component: DefineComponent<{}, {}, any>
export default component
}
declare module 'js-md5'

5
src/store/index.ts Normal file
View File

@ -0,0 +1,5 @@
import { createPinia } from 'pinia'
const Store = createPinia()
export default Store

7
src/store/user/index.ts Normal file
View File

@ -0,0 +1,7 @@
import { defineStore } from 'pinia'
export const userStore = defineStore('USER', {
state: () => ({
isLogin: false,
}),
})

0
src/styles/element.scss Normal file
View File

2
src/styles/index.scss Normal file
View File

@ -0,0 +1,2 @@
@import './public.scss';
@import './element.scss';

22
src/styles/public.scss Normal file
View File

@ -0,0 +1,22 @@
html,
body,
h1,
h2,
h3,
h4,
h5,
h6 {
padding: 0;
margin: 0;
}
html,
body,
#app {
width: 100%;
height: 100%;
}
em {
font-style: normal;
}

4
src/utils/index.ts Normal file
View File

@ -0,0 +1,4 @@
import storage from './package/storage'
import request from './request'
export { storage, request }

View File

@ -0,0 +1,44 @@
import AgoraRTC, { IAgoraRTCClient, IAgoraRTCRemoteUser } from "agora-rtc-sdk-ng"
let client: IAgoraRTCClient;
type optionsType = {
appId: string;
channel: string;
token: string;
uid: number;
};
const options: optionsType = {
appId: "",
channel: "",
token: "",
uid: 0,
};
export const agora = {
// 初始化
init: async () => {
client = AgoraRTC.createClient({ mode: "live", codec: "vp8" });
},
// 设置参数
setOptions: <K extends keyof optionsType>(key: K, value: optionsType[K]) => {
options[key] = value;
},
// 加入频道
join: async () => {
await client.join(
options.appId,
options.channel,
options.token,
options.uid
);
},
// 离开频道
leave: async () => {
await client.leave();
},
// 监听
on: ({ userPublished }: any) => {
client.on("user-published", async (user: IAgoraRTCRemoteUser, mediaType: 'video' | 'audio') => {
await client.subscribe(user, mediaType)
userPublished(user, mediaType)
})
}
}

View File

@ -0,0 +1,30 @@
class LocalStorage {
private constructor() {}
private static instance: LocalStorage | null = null
static getInstance() {
if (LocalStorage.instance === null) {
LocalStorage.instance = new LocalStorage()
}
return LocalStorage.instance
}
setItem(key: string, value: any) {
localStorage.setItem(key, value)
}
getItem(key: string) {
return localStorage.getItem(key)
}
removeItem(key: string) {
localStorage.removeItem(key)
}
removeAll() {
localStorage.clear()
}
}
export default LocalStorage.getInstance()

View File

@ -0,0 +1,26 @@
import { AxiosRequestConfig, AxiosResponse } from 'axios'
import Request from './request'
import { constant } from '@/config'
// 实例化
const req = new Request({
baseURL: import.meta.env.VITE_BASE_URL_API,
timeout: constant.CONFIG_REQUEST_TIMEOUT_TIME as number,
interceptors: {
// 请求拦截器
requestInterceptors: (config: AxiosRequestConfig) => config,
// 响应拦截器 <T = AxiosResponse>(result: T)
responseInterceptors: <T = AxiosResponse>(result: T) => result,
},
})
const request = (config: any) => {
const { method = 'GET' } = config
if (method === 'get' || method === 'GET') {
config.params = config.data
}
return req.request<any>(config)
}
export default request

View File

@ -0,0 +1,96 @@
import axios, { AxiosInstance, AxiosResponse } from 'axios'
import { RequestConfig, RequestInterceptors } from './types'
import { storage } from '@/utils'
import { constant } from '@/config'
import router from '@/router'
class Request {
// axios实例
instance: AxiosInstance
// 拦截器对象
interceptorsObj?: RequestInterceptors
constructor(config: RequestConfig) {
// 创建实例
this.instance = axios.create(config)
// 类请求拦截器
this.instance.interceptors.request.use(
(req: any) => {
const token = storage.getItem(constant.CONFIG_TOKEN)
console.log(token);
if (token) {
// 如果有token给请求头加上
req.headers.Authorization = `Bearer ${token}`
req.timeout = constant.CONFIG_REQUEST_TIMEOUT_TIME
}
return req
},
(err: any) => {
}
)
// 类响应拦截器
this.instance.interceptors.response.use(
(res: AxiosResponse) => {
const { data: resData } = res
if (resData.code !== 200) {
ElMessage.error(resData.message)
}
return resData
},
(err: any) => {
// 根据自己业务/接口返回做相应调整
const { status } = err.response
switch (status) {
case 401:
// @ts-ignore
ElMessage.warning('登录信息已失效,请关闭窗口,重新打开!')
storage.removeItem('token')
storage.removeItem('user')
// router.push({ path: '/login' })
break
default:
}
const { Code } = err.response.data
switch (Code) {
case 401:
// @ts-ignore
ElMessage.warning('登录信息已失效,请关闭窗口,重新打开!')
storage.removeItem('token')
storage.removeItem('user')
// router.push({ path: '/login' })
break
default:
}
}
)
}
request<T>(config: RequestConfig): Promise<T> {
return new Promise((resolve, reject) => {
if (config.interceptors?.requestInterceptors) {
config = config.interceptors.requestInterceptors(config)
}
this.instance
.request<any, T>(config)
.then((res: T) => {
// 如果我们为单个响应设置拦截器,这里使用单个响应的拦截器
if (config.interceptors?.responseInterceptors) {
res = config.interceptors.responseInterceptors<T>(res)
}
resolve(res)
})
.catch((err: any) => {
reject(err)
}).finally(() => {
})
})
}
}
export default Request

View File

@ -0,0 +1,20 @@
import { AxiosRequestConfig, AxiosResponse } from 'axios'
// 实例拦截器
export interface RequestInterceptors {
// 请求拦截
requestInterceptors?: (config: AxiosRequestConfig) => AxiosRequestConfig
requestInterceptorsCatch?: (err: any) => any
// 响应拦截
responseInterceptors?: <T = AxiosResponse>(config: T) => T
responseInterceptorsCatch?: (err: any) => any
}
// 自定义传入的参数
export interface RequestConfig extends AxiosRequestConfig {
interceptors?: RequestInterceptors
}
export interface useRequestConfig<T> extends RequestConfig {
data?: T
}

1
src/vite-env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="vite/client" />

43
tsconfig.json Normal file
View File

@ -0,0 +1,43 @@
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": [
"ESNext",
"DOM",
"DOM.Iterable"
],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"baseUrl": ".",
"paths": {
"@/*": [
"src/*"
]
}
},
"include": [
"src/**/*.ts",
"src/**/*.d.ts",
"src/**/*.tsx",
"src/**/*.vue",
"./auto-imports.d.ts"
],
"references": [
{
"path": "./tsconfig.node.json"
}
]
}

10
tsconfig.node.json Normal file
View File

@ -0,0 +1,10 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

69
vite.config.ts Normal file
View File

@ -0,0 +1,69 @@
import { resolve } from 'path'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import Icons from 'unplugin-icons/vite'
import IconsResolver from 'unplugin-icons/resolver'
import Inspect from 'vite-plugin-inspect'
import pxtovw from 'postcss-px-to-viewport'
const loder_pxtovw = pxtovw({
// 这里是设计稿宽度 自己修改
viewportWidth: 1920,
viewportUnit: 'vw',
selectorBlackList: ['.prism-player'],
})
// https://vitejs.dev/config/
export default defineConfig(() => ({
css: {
postcss: {
plugins: [loder_pxtovw]
},
},
server: {
port: 8080,
host: '0.0.0.0',
},
resolve: {
alias: [
{
find: '@',
replacement: resolve(__dirname, 'src'),
},
],
},
build: {
minify: 'terser',
terserOptions: {
compress: {
drop_console: true,
},
},
},
plugins: [
vue(),
AutoImport({
resolvers: [
ElementPlusResolver(),
IconsResolver({
prefix: 'Icon',
}),
],
}),
Components({
resolvers: [
ElementPlusResolver(),
IconsResolver({
enabledCollections: ['ep'],
}),
],
}),
Icons({
autoInstall: true,
}),
Inspect(),
],
}))