Commit 0f2e86c6 by zhangjiaming

wecloud 重构框架搭建

parents
# 正式
## sdk外的接口域名
VUE_APP_BASEURL=https://imapi.wecloud.cn
## wecloud-im-sdk 配置
VUE_APP_IM_APPKEY=QizKVHcILRWp6Td2
VUE_APP_IM_APPSECRET=287d04828099fb7de871e9dda845fa8b6b2302faf2ab3737
VUE_APP_IM_APIURL=https://imapi.wecloud.cn
VUE_APP_IM_WSURL=wss://imapi.wecloud.cn/ws
VUE_APP_IM_PLATFORM=3
# 客户端平台: 1 ios, 2 安卓, 3 web, 4 pc-win, 5 pc-macOs
## minio配置
VUE_APP_MINIO_ENDPOINT=oss.wecloud.cn
VUE_APP_MINIO_USESSL=true
VUE_APP_MINIO_ACCESS_KEY=ee4tXw1pKzdIf0rW
VUE_APP_MINIO_SECRET_KEY=H8DkFwN3oOsxmVNVZ3ZuQ4iSX40ajehHCHnSPmAaepknGI5qNLulm7QSDPKX4gt4
VUE_APP_MINIO_BUCKET_NAME=paas-im-demo
VUE_APP_MINIO_BUCKET_NAME_FOREVER=paas-im-demo-forever
## 视频会议配置
VUE_APP_MEETING_BASEURL=https://meet-api-test.wecloud.cn
VUE_APP_MEETING_WSSURL=wss://ws-meet-test.wecloud.cn
# 本地开发
####### 温
## sdk外的接口域名
VUE_APP_BASEURL=http://192.168.8.27:8082
## wecloud-im-sdk 配置
VUE_APP_IM_APPKEY=QizKVHcILRWp6Td2
VUE_APP_IM_APPSECRET=287d04828099fb7de871e9dda845fa8b6b2302faf2ab3737
VUE_APP_IM_APIURL=http://192.168.8.27:8082
VUE_APP_IM_WSURL=ws://192.168.8.27:8899/ws
VUE_APP_IM_PLATFORM=3
## minio配置
VUE_APP_MINIO_ENDPOINT=test-file.wecloud.cn
VUE_APP_MINIO_USESSL=true
VUE_APP_MINIO_ACCESS_KEY=test-pwdisZz123456
VUE_APP_MINIO_SECRET_KEY=Zz123456
VUE_APP_MINIO_BUCKET_NAME=xiaolandousaas
VUE_APP_MINIO_BUCKET_NAME_FOREVER=xiaolandousaas-forever
## 视频会议配置
VUE_APP_MEETING_BASEURL=http://121.37.22.224:8089
VUE_APP_MEETING_WSSURL=wss://ws-meet-test.wecloud.cn
# 测试
## sdk外的接口域名
VUE_APP_BASEURL=https://imapitest.wecloud.cn
## wecloud-im-sdk 配置
VUE_APP_IM_APPKEY=QizKVHcILRWp6Td2
VUE_APP_IM_APPSECRET=287d04828099fb7de871e9dda845fa8b6b2302faf2ab3737
VUE_APP_IM_APIURL=https://imapitest.wecloud.cn
VUE_APP_IM_WSURL=wss://imapitest.wecloud.cn/ws
VUE_APP_IM_PLATFORM=3
## minio配置s
VUE_APP_MINIO_ENDPOINT=test-file.wecloud.cn
VUE_APP_MINIO_USESSL=true
VUE_APP_MINIO_ACCESS_KEY=test-pwdisZz123456
VUE_APP_MINIO_SECRET_KEY=Zz123456
VUE_APP_MINIO_BUCKET_NAME=xiaolandousaas
VUE_APP_MINIO_BUCKET_NAME_FOREVER=xiaolandousaas-forever
## 视频会议配置
# VUE_APP_MEETING_BASEURL=http://121.37.22.224:8089
VUE_APP_MEETING_BASEURL=https://meet-api-test.wecloud.cn
VUE_APP_MEETING_WSSURL=wss://ws-meet-test.wecloud.cn
node_modules
.DS_Store
dist
dist-ssr
*.local
{
"recommendations": ["johnsoncodehk.volar"]
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<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>
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"name": "vue3-ts-template",
"version": "0.0.0",
"scripts": {
"dev": "vue-cli-service serve --mode dev",
"qa": "vue-cli-service serve --mode qa",
"serve": "vue-cli-service serve",
"build:qa": "vue-cli-service build --mode qa",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"@vue/cli": "^5.0.8",
"@vue/cli-service": "^5.0.8",
"axios": "^0.24.0",
"element-plus": "^2.2.14",
"element-ui": "^2.15.9",
"nodejs-websocket": "^1.7.2",
"sass-loader": "^13.0.2",
"ts-loader": "^9.3.1",
"tslint-loader": "^3.5.4",
"vue": "^3.2.25",
"vue-router": "^4.0.12",
"vuex": "^4.0.2"
},
"devDependencies": {
"@vitejs/plugin-vue": "^2.0.0",
"sass": "^1.47.0",
"sass-resources-loader": "^2.2.5",
"typescript": "^4.4.4",
"vite": "^2.7.2",
"vue-tsc": "^0.29.8"
}
}
<template>
<div>
<router-view></router-view>
</div>
</template>
<style lang="scss">
//引入全局样式
@import './assets/style/reset.scss';
@import './assets/style/main.scss';
</style>
// 全局css 变量
body {
height: 100%;
width: 100%;
}
\ No newline at end of file
html {
// overflow-x:auto;
// overflow-y:scroll;
}
body, dl, dt, dd, ul, ol, li, pre, form, fieldset, input, p, blockquote, th, td {
font-weight:400;
margin:0;
padding:0;
}
h1, h2, h3, h4, h4, h5 {
margin:0;
padding:0;
}
body {
background-color:#FFFFFF;
color:#666666;
font-family:Helvetica,Arial,sans-serif;
font-size:12px;
padding:0;
text-align:left;
}
select {
font-size:12px;
}
table {
border-collapse:collapse;
}
fieldset, img {
border:0 none;
}
fieldset {
margin:0;
padding:0;
}
fieldset p {
margin:0;
padding:0 0 0 8px;
}
legend {
display:none;
}
address, caption, em, strong, th, i {
font-style:normal;
font-weight:400;
}
table caption {
margin-left:-1px;
}
hr {
border-bottom:1px solid #FFFFFF;
border-top:1px solid #E4E4E4;
border-width:1px 0;
clear:both;
height:2px;
margin:5px 0;
overflow:hidden;
}
ol, ul {
list-style-image:none;
list-style-position:outside;
list-style-type:none;
}
caption, th {
text-align:left;
}
q:before, q:after, blockquote:before, blockquote:after {
content:””;
}
\ No newline at end of file
$test-color: #3478F5;
$theme_color: #3478F5;
$theme_font: PingFangSC-Semibold, PingFang SC;
\ No newline at end of file
<script setup lang="ts">
import { ref } from 'vue'
defineProps<{ msg: string }>()
const count = ref(0)
</script>
<template>
<h1>{{ msg }}</h1>
<p>
Recommended IDE setup:
<a href="https://code.visualstudio.com/" target="_blank">VSCode</a>
+
<a href="https://github.com/johnsoncodehk/volar" target="_blank">Volar</a>
</p>
<p>See <code>README.md</code> for more information.</p>
<p>
<a href="https://vitejs.dev/guide/features.html" target="_blank">
Vite Docs
</a>
|
<a href="https://v3.vuejs.org/" target="_blank">Vue 3 Docs</a>
</p>
<button type="button" @click="count++">count is: {{ count }}</button>
<p>
Edit
<code>components/HelloWorld.vue</code> to test hot module replacement.
</p>
</template>
<style scoped>
a {
color: #42b983;
}
label {
margin: 0 0.5em;
font-weight: bold;
}
code {
background-color: #eee;
padding: 2px 4px;
border-radius: 4px;
color: #304455;
}
</style>
/// <reference types="vite/client" />
declare module '*.vue' {
import { DefineComponent } from 'vue'
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
const component: DefineComponent<{}, {}, any>
export default component
}
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';
import store from './store/index'
import {useState, useMutations, useGetters, useActions} from './utils/mapStore'
let app = createApp(App)
app.config.globalProperties.$mapStore = {
useState,
useMutations,
useGetters,
useActions
};
app.use(ElementPlus)
app.use(router)
app.use(store)
app.mount('#app')
declare module 'element-plus'
declare module 'vue'
\ No newline at end of file
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
const routes: RouteRecordRaw[] = [
{
path: '/demo',
name: 'demo',
component: () => import('@/views/demo.vue'), // 注意这里要带上 文件后缀.vue
},
{
path: '/',
name: 'Login',
component: () => import('@/views/login.vue'), // 注意这里要带上 文件后缀.vue
},
]
const router = createRouter({
history: createWebHistory(),
routes,
})
export default router
\ No newline at end of file
import http from '@/service/http'
import * as T from './types'
\ No newline at end of file
export interface ILoginParams {
username: string,
password: string | number
}
export interface ILoginApi {
login: (params: ILoginParams) => Promise<any>
}
\ No newline at end of file
// http.ts
import axios, {AxiosRequestConfig} from 'axios'
// 创建axiso实例
// 创建请求时可以用的配置选项
// 设置请求头和请求路径
axios.defaults.baseURL = '/api'
axios.defaults.timeout = 10000
axios.defaults.headers.post['Content-Type'] = 'application/json;charset=UTF-8'
axios.interceptors.request.use(
(config): AxiosRequestConfig<any> => {
const token = window.sessionStorage.getItem('token')
if(token) {
(config.headers as any).token = token
}
return config
},
error => {
return error
}
)
axios.interceptors.response.use(res => {
if(res.data.code === '222') {
sessionStorage.setItem('token', '')
// token过期操作
}
return res
})
interface ResType<T> {
code: number
data?: T
msg: string
err: string
}
interface Http{
get<T>(url: string, params?: unknown): Promise<ResType<T>>
post<T>(url: string, params?: unknown): Promise<ResType<T>>
upload<T>(url: string, params?: unknown): Promise<ResType<T>>
download(url: string):void
}
const http: Http = {
get(url, params) {
return new Promise((resolve, reject) => {
axios
.get(url, {params})
.then((res) => {
resolve(res.data)
})
.catch((err) => {
reject(err.data)
})
})
},
post(url, params) {
return new Promise((resolve, reject) => {
axios
.post(url, JSON.stringify(params))
.then((res) => {
resolve(res.data)
})
.catch((err) => {
reject(err.data)
})
})
},
upload(url, file) {
return new Promise((resolve, reject) => {
axios
.post(url, file, {
headers: { 'Content-Type': 'multipart/form-data' },
})
.then((res) => {
resolve(res.data)
})
.catch((err) => {
reject(err.data)
})
})
},
download(url) {
const iframe = document.createElement('iframe')
iframe.style.display = 'none'
iframe.src = url
iframe.onload = function () {
document.body.removeChild(iframe)
}
document.body.appendChild(iframe)
},
}
export default http
\ No newline at end of file
import userModule from './modules/user'
import { createStore } from "vuex";
export default createStore({
state: {
father: 'father',
mom: 'mon'
},
mutations: {
father(){
console.log('getMutations')
}
},
actions: {
father(){
console.log('get actions')
}
},
getters:{
father(){
return 'get getters'
}
},
modules: {
user: userModule
},
});
\ No newline at end of file
export default {
namespaced:true,
state: {
name: 'gavin'
},
mutations: {
son(){
console.log('mutations son')
}
},
actions: {
son(){
console.log('action son')
}
},
getters: {
son(){
return 'getters son'
}
}
};
\ No newline at end of file
import { computed } from 'vue'
import { mapState, mapGetters, mapMutations, mapActions, useStore, Computed, Mapper, MapperForState, MapperForStateWithNamespace, MapperWithNamespace } from 'vuex'
/**
*
* @param mapperFn 传入的map辅助函数,mapState, mapGetters, mapActions, mapMutations
* @param mapper 方法或者属性的名字,actions或者mutations或者getter的函数名,state的属性名字
* @param module 开启命名空间后的模块名
* @resultFn {{}} 返回数组,数组内容为fn函数,fn函数为每个属性所对应的map辅助函数
*/
enum mapperFnEnum{
mapState,
mapGetters,
mapActions,
mapMutations
}
const hooks = (mapperFn: any, mapper: string, module: string) => {
const store = useStore(); // 引入vuex中的useStore函数
const resultFn = {};
let mapData: Record<string, any> = {};
if (module) { // 判断是否存在命名空间,如果存在则绑定
mapData = mapperFn(module, mapper);
} else {
mapData = mapperFn(mapper);
}
Object.keys(mapData).forEach( item => {
const fn = mapData[item].bind({'$store': store}); // 使用bind方法将得到map函数结果绑定到vuex上
(resultFn as any)[item] = fn;
});
return resultFn
};
/**
* 满足mapState和mapGetters调用
* @param mapperFn 传入的map辅助函数,主要是mapState和mapGetters
* @param mapper 数组类型,主要是变量或者返回值的key
* @param module 打开命名空间,模块名称,非必传
* 调用hooks函数后得到其返回值,然后将返回值放在computed中做一个监听,
* computed参会可以是函数的返回值,即这样就完成了对数据的返回监听
*/
const useDataHooks = (mapperFn: Mapper<Computed> & MapperWithNamespace<Computed> & MapperForState & MapperForStateWithNamespace, mapper: string, module: string) => {
const store = useStore()
const storeState: Record<string, any> = {}
let hooksData = hooks( mapperFn, mapper, module);
Object.keys(hooksData).forEach((fnKey) => {
const fn = (hooksData as any)[fnKey].bind({ $store: store })
storeState[fnKey] = computed(fn)
})
return storeState
}
/**
* 封装useState函数
* @param module 命名空间,名称
* @param mapper 数组, state中定义的变量名称
*/
export const useState = (mapper: any, module: any) => {
return useDataHooks(mapState, mapper, module)
};
/**
* 封装useGetters函数
* @param module 命名空间,
* @param mapper 数组,即getters中的返回值名称
*/
export const useGetters = (mapper: any, module: any) => {
return useDataHooks(mapGetters, mapper, module)
};
/**
* 封装mapMutations函数
* @param mapper 数组,mutations中函数的名称
* @param module 命名空间,模块名称
*/
export const useMutations = (mapper: string, module: string) => {
return hooks( mapMutations, mapper, module);
};
/**
* 封装mapActions函数
* @param mapper 数组,actions中函数的名称
* @param module 命名空间,模块名称
*/
export const useActions = (mapper: string, module: string) => {
return hooks( mapActions ,mapper, module);
};
\ No newline at end of file
const ws = require('nodejs-websocket')
const port = 3000
const server = ws.createServer(connect => {
console.log('有用户连接上来了')
connect.on('text', data => {
console.log(`用户传递的数据${data}`)
connect.send(data)
})
connect.on('close', () => {
console.log('连接断开了')
})
connect.on('error', () => {
console.log('有异常')
})
})
server.listen(port, () => {
console.log('websocket服务器启动')
})
/**
* @author Gavin
* @description socket封装
* */
interface ConstructorInterface {
url: string;
cb: (msg: MessageEvent<any>) => void;
}
enum WsOrderEnum {
open = 0,
close = 1,
}
export class WsClass {
wsImpl: WebSocket;
url: string;
cb: (msg: MessageEvent<any>) => void;
isConnect: Boolean;
wsOrder: WsOrderEnum;
reconnectTime: number;
reconnectTimeId: number;
heartTimeId: number;
constructor({ url, cb }: ConstructorInterface) {
this.url = url;
this.wsOrder = 0;
this.wsImpl = null as unknown as WebSocket;
this.isConnect = false;
this.reconnectTime = 0;
this.reconnectTimeId = 0;
this.heartTimeId = 0;
this.cb = cb as unknown as (msg: MessageEvent<any>) => void;
this.init({ url, cb });
}
init({ url, cb }: ConstructorInterface) {
this.wsOrder = WsOrderEnum.open;
this.wsImpl = new WebSocket(url);
this.wsOpenListener();
this.wsMessageListener(cb);
this.wsErrorListener();
this.wsCloseListener();
}
wsOpenListener() {
this.wsImpl.addEventListener("open", () => {
console.log("链接成功");
this.isConnect = true;
this.wsHeart();
});
}
wsSend(sendObj: any) {
console.log("this", this);
console.log("this.wsImpl", this.wsImpl);
console.log(`发送信息${sendObj}`);
this.wsImpl.send(JSON.stringify(sendObj));
}
wsMessageListener(cb: (msg: MessageEvent<any>) => void) {
this.wsImpl.addEventListener("message", (msg) => {
cb(msg);
});
}
wsErrorListener() {
this.wsImpl.addEventListener("error", () => {
this.isConnect = false;
// 如果是关闭的时候出现异常
if(this.wsOrder === WsOrderEnum.open){
console.log("ws异常重连");
this.wsReconnect();
} else {
this.wsImpl.close()
}
});
}
wsCloseListener() {
this.wsImpl.addEventListener("close", (event) => {
this.isConnect = false;
// 非正常关闭 服务器奔溃 或 网络慢 链接超时 会自动出发close
if (this.wsOrder === WsOrderEnum.open) {
this.wsReconnect();
} else {
console.log("ws链接关闭");
this.wsClear()
}
});
}
wsClear(){
// 垃圾回收 周期前 挂载的事件 包括 定时器 要清除一下绑定关系
clearInterval(this.heartTimeId);
this.wsImpl.close()
this.wsImpl.onmessage = null;
this.wsImpl.onopen = null;
this.wsImpl.onerror = null;
this.wsImpl.onclose = null;
}
wsReconnect() {
// 防抖
this.wsClear()
clearInterval(this.reconnectTimeId);
this.reconnectTimeId = (setTimeout(() => {
this.reconnectTime++;
this.init({
url: this.url,
cb: this.cb,
});
}, 1000) as any);
}
// ping pong 心跳 检索链接 及时作出重连动作
wsHeart() {
clearInterval(this.heartTimeId);
this.heartTimeId = (setInterval(() => {
this.wsSend("ping");
}, 10000) as any);
}
}
<template>
<div class="app">
这是home.............
<el-button type="primary">123</el-button>
</div>
</template>
<script lang="ts">
import {defineComponent, getCurrentInstance} from 'vue';
import { useStore, mapState } from 'vuex'
// This starter template is using Vue 3 <script setup> SFCs
// Check out https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup
export default {
props: {
fun: {
type: String,
require: true
}
},
setup(){
// const store = useStore()
// console.log('store', store.state.user.name)
let { proxy } = getCurrentInstance()
console.log('useState', proxy.$mapStore.useState(['name'], 'user').name.value)
console.log('useMutations', proxy.$mapStore.useMutations(['son'], 'user').son())
console.log('useGetters', proxy.$mapStore.useGetters(['son'], 'user').son.value)
console.log('useActions', proxy.$mapStore.useActions(['son'], 'user').son())
}
}
</script>
<style lang="scss" scoped>
div {
color: $test-color;
}
</style>
\ No newline at end of file
<template>
<div class="login_bg">
<div class="login_wrap">
<div class="title">
<div class="main_title">欢迎使用,</div>
<div class="sub_title">PASS IM</div>
</div>
<el-form class="login_form">
<el-form-item>
<el-input size="medium" placeholder="请输入账号"></el-input>
</el-form-item>
<el-form-item>
<div class="msg_code_input">
<el-input size="medium" placeholder="请输入验证码"></el-input>
<div class="msg_code"></div>
</div>
<div class="pwd_input">
<el-input size="medium" placeholder="请输入密码" type="password"></el-input>
</div>
</el-form-item>
</el-form>
</div>
<div class="transform_login_method">
密码登录
</div>
</div>
</template>
<script lang="ts">
import {defineComponent} from 'vue';
export default defineComponent({
setup(){
}
})
</script>
<style lang="scss" scoped>
.login_bg {
width: 100vw;
height: 100vh;
position: relative;
background: rgba($color: #000000, $alpha: 0.3);
.login_wrap {
padding: 40px 30px;
box-sizing: border-box;
width: 360px;
height: 440px;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
background: #fff;
border-radius: 5px;
&:deep(.el-input__wrapper) {
background: #F3F4F6;
}
.title {
font-size: 30px;
font-family: $theme_font;
font-weight: 600;
color: #000000;
line-height: 42px;
margin-bottom: 50px;
.sub_title {
font-size: 24px;
font-family: $theme_font;
font-weight: 400;
color: #000000;
line-height: 33px;
}
}
}
}
</style>
\ No newline at end of file
{
"compilerOptions": {
"target": "esnext",
"useDefineForClassFields": true,
"module": "esnext",
"moduleResolution": "node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"lib": ["esnext", "dom"],
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue", "src/utils/node-ws/index.js"]
}
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
pages: {
index: {
entry: './src/main.ts'
}
},
chainWebpack: config => {
// 全局引入scss文件, 无需在每个组件中再次手动导入
const oneOfsMap = config.module.rule('scss').oneOfs.store
oneOfsMap.forEach(item => {
item.use('sass-resources-loader')
.loader('sass-resources-loader')
.options({
resources: ['./src/assets/style/variable.scss']
})
.end()
})
},
configureWebpack: {
resolve: { extensions: [".ts", ".tsx", ".js", ".json"] },
module: {
rules: [
// {
// test: /\.ts$/,
// exclude: /node_modules/,
// enforce: 'pre',
// loader: 'tslint-loader'
// },
{
test: /\.tsx?$/,
loader: 'ts-loader',
exclude: /node_modules/,
options: {
appendTsSuffixTo: [/\.vue$/],
}
}
]
}
}
})
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment