Commit 75dcdad6 by wxl

init

parents
# http://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
{
"extends": ["taro/vue3"]
}
dist/
deploy_versions/
.temp/
.rn_temp/
node_modules/
.DS_Store
registry=https://registry.npm.taobao.org
disturl=https://npm.taobao.org/dist
sass_binary_site=https://npm.taobao.org/mirrors/node-sass/
phantomjs_cdnurl=https://npm.taobao.org/mirrors/phantomjs/
electron_mirror=https://npm.taobao.org/mirrors/electron/
chromedriver_cdnurl=https://npm.taobao.org/mirrors/chromedriver
operadriver_cdnurl=https://npm.taobao.org/mirrors/operadriver
selenium_cdnurl=https://npm.taobao.org/mirrors/selenium
node_inspector_cdnurl=https://npm.taobao.org/mirrors/node-inspector
fsevents_binary_host_mirror=http://npm.taobao.org/mirrors/fsevents/
{
"volar.tsPlugin": false
}
\ No newline at end of file
// babel-preset-taro 更多选项和默认值:
// https://github.com/NervJS/taro/blob/next/packages/babel-preset-taro/README.md
module.exports = {
presets: [
['taro', {
framework: 'vue3',
ts: true
}]
]
}
module.exports = {
env: {
NODE_ENV: '"development"'
},
defineConstants: {
},
mini: {},
h5: {}
}
const config = {
projectName: 'anyremote-miniapp',
date: '2021-2-2',
designWidth: 750,
deviceRatio: {
640: 2.34 / 2,
750: 1,
828: 1.81 / 2
},
sourceRoot: 'src',
outputRoot: 'dist',
plugins: [],
defineConstants: {
},
copy: {
patterns: [
],
options: {
}
},
framework: 'vue3',
mini: {
postcss: {
pxtransform: {
enable: true,
config: {
}
},
url: {
enable: true,
config: {
limit: 1024 // 设定转换尺寸上限
}
},
cssModules: {
enable: false, // 默认为 false,如需使用 css modules 功能,则设为 true
config: {
namingPattern: 'module', // 转换模式,取值为 global/module
generateScopedName: '[name]__[local]___[hash:base64:5]'
}
}
}
},
h5: {
publicPath: '/',
staticDirectory: 'static',
postcss: {
autoprefixer: {
enable: true,
config: {
}
},
cssModules: {
enable: false, // 默认为 false,如需使用 css modules 功能,则设为 true
config: {
namingPattern: 'module', // 转换模式,取值为 global/module
generateScopedName: '[name]__[local]___[hash:base64:5]'
}
}
}
}
}
module.exports = function (merge) {
if (process.env.NODE_ENV === 'development') {
return merge({}, config, require('./dev'))
}
return merge({}, config, require('./prod'))
}
module.exports = {
env: {
NODE_ENV: '"production"'
},
defineConstants: {
},
mini: {},
h5: {
/**
* 如果h5端编译后体积过大,可以使用webpack-bundle-analyzer插件对打包体积进行分析。
* 参考代码如下:
* webpackChain (chain) {
* chain.plugin('analyzer')
* .use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin, [])
* }
*/
}
}
declare module '*.png';
declare module '*.gif';
declare module '*.jpg';
declare module '*.jpeg';
declare module '*.svg';
declare module '*.css';
declare module '*.less';
declare module '*.scss';
declare module '*.sass';
declare module '*.styl';
declare namespace NodeJS {
interface ProcessEnv {
TARO_ENV: 'weapp' | 'swan' | 'alipay' | 'h5' | 'rn' | 'tt' | 'quickapp' | 'qq' | 'jd'
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"name": "anyremote-miniapp",
"version": "1.0.0",
"private": true,
"description": "远程协作系统小程序版",
"templateInfo": {
"name": "default",
"typescript": true,
"css": "less"
},
"scripts": {
"build:weapp": "taro build --type weapp",
"build:swan": "taro build --type swan",
"build:alipay": "taro build --type alipay",
"build:tt": "taro build --type tt",
"build:h5": "taro build --type h5",
"build:rn": "taro build --type rn",
"build:qq": "taro build --type qq",
"build:jd": "taro build --type jd",
"build:quickapp": "taro build --type quickapp",
"dev:weapp": "npm run build:weapp -- --watch",
"dev:swan": "npm run build:swan -- --watch",
"dev:alipay": "npm run build:alipay -- --watch",
"dev:tt": "npm run build:tt -- --watch",
"dev:h5": "npm run build:h5 -- --watch",
"dev:rn": "npm run build:rn -- --watch",
"dev:qq": "npm run build:qq -- --watch",
"dev:jd": "npm run build:jd -- --watch",
"dev:quickapp": "npm run build:quickapp -- --watch"
},
"browserslist": [
"last 3 versions",
"Android >= 4.1",
"ios >= 8"
],
"author": "",
"dependencies": {
"@babel/runtime": "^7.7.7",
"@tarojs/components": "3.0.26",
"@tarojs/extend": "^3.0.26",
"@tarojs/runtime": "3.0.26",
"@tarojs/taro": "3.0.26",
"rxjs": "^6.6.3",
"vue": "^3.0.0",
"vue-vulcan": "^0.1.0"
},
"devDependencies": {
"@types/webpack-env": "^1.13.6",
"@tarojs/mini-runner": "3.0.26",
"@babel/core": "^7.8.0",
"@tarojs/webpack-runner": "3.0.26",
"babel-preset-taro": "3.0.26",
"@vue/compiler-sfc": "^3.0.0",
"vue-loader": "^16.0.0-beta.8",
"eslint-plugin-vue": "^7.0.0",
"eslint-config-taro": "3.0.26",
"eslint": "^6.8.0",
"stylelint": "9.3.0",
"@typescript-eslint/parser": "^2.x",
"@typescript-eslint/eslint-plugin": "^2.x",
"typescript": "^3.7.0"
}
}
{
"miniprogramRoot": "dist/",
"projectname": "anyremote-miniapp",
"description": "anyremote远程协作系统小程序版",
"appid": "wxe817e507adcda5f7",
"setting": {
"urlCheck": true,
"es6": false,
"enhance": false,
"postcss": false,
"preloadBackgroundData": false,
"minified": false,
"newFeature": false,
"coverView": true,
"nodeModules": false,
"autoAudits": false,
"showShadowRootInWxmlPanel": true,
"scopeDataCheck": false,
"uglifyFileName": false,
"checkInvalidKey": true,
"checkSiteMap": true,
"uploadWithSourceMap": true,
"compileHotReLoad": false,
"useMultiFrameRuntime": true,
"useApiHook": true,
"useApiHostProcess": false,
"babelSetting": {
"ignore": [],
"disablePlugins": [],
"outputPath": ""
},
"enableEngineNative": false,
"bundle": false,
"useIsolateContext": true,
"useCompilerModule": true,
"userConfirmedUseCompilerModuleSwitch": false,
"userConfirmedBundleSwitch": false,
"packNpmManually": false,
"packNpmRelationList": [],
"minifyWXSS": true
},
"compileType": "miniprogram",
"condition": {}
}
\ No newline at end of file
### 介绍
#### anyremote 小程序版本
基于taro+vue3 实现
\ No newline at end of file
export default {
pages: [
'pages/index/index',
'pages/login/index',
'pages/meeting/index'
],
window: {
backgroundTextStyle: 'light',
navigationBarBackgroundColor: '#fff',
navigationBarTitleText: 'WeChat',
navigationBarTextStyle: 'black'
}
}
import { authorize } from '@tarojs/taro';
import { createApp } from 'vue';
import './app.less';
const App = createApp({
onShow () {},
// 入口组件不需要实现 render 方法,即使实现了也会被 taro 所覆盖
setup() {
authorize({
scope: 'scope.camera'
})
}
})
export default App
export const environment = {
baseUrl: 'https://devserver.anyremote.cn/index.php/api/common'
}
\ No newline at end of file
export const HTTP_CONFIG = Symbol('http config');
\ No newline at end of file
// export function useCanvasContext(selector: string) {
// let
// }
\ No newline at end of file
import { reactive, Ref, toRaw, UnwrapRef } from "vue";
import { useRequest, HttpStatus } from "./useRequest";
interface FormOption {
loadingTip?: string;
successTip?: string;
callback?: (res?: any) => void;
}
interface FormAction<T> {
submit: (data?: any) => void,
patchValues: (data: T, keys?: Array<keyof T>) => void
}
interface FormStates {
valid: boolean;
httpStatus: Ref<HttpStatus>;
}
export function useFormModel<T extends object, R = object>(
url: string, formData: T , option?: FormOption
): [T, FormAction<T>, FormStates, Ref<UnwrapRef<R>> ] {
const formDataRef = reactive<T>(formData) as T;
const [res, doRequest, httpStatus] = useRequest<R>(url, {}, {
auto: false,
successTip: option?.successTip || '提交成功',
loadingTip: option?.loadingTip || '正在提交'
});
/**
* @param data 要并入的表单对象
* @param keys 指定data哪些属性会被并入表单对象,若不传则会并入data对象所有属性
*/
const patchValues = (data: T , keys?: Array<keyof T>) => {
const patchkeys = keys ? keys : Object.keys(data)
for (const key of patchkeys) {
if (data[key as string]) {
formDataRef[key as keyof T] = data[key as string]
}
}
}
// 提交表单请求
const submit = (params: any = {}) => {
doRequest({...toRaw(formDataRef), ...params})
}
return [
formDataRef,
{
submit,
patchValues
},
{
valid: true,
httpStatus
},
res
]
}
import { FunctionalStore } from "../../types/common";
import { inject } from "vue";
type InjectType = 'root' | 'optional';
export function useInjector<T extends object>(func: FunctionalStore<T>, type?: InjectType) {
const token = func.token;
const root = func.root;
switch(type) {
default:
if(inject(token)) {
return inject<T>(token)
};
if(root) return func.root;
throw new Error(`状态钩子函数${func.name}未在上层组件通过调用useProvider提供`);
case 'optional':
if(!token) return null;
case 'root':
if(!func.root) func.root = func();
return func.root;
}
}
\ No newline at end of file
export function useLoginByAccounts() {
}
\ No newline at end of file
import { FunctionalStore } from "../../types/common";
import { provide } from "vue";
export function useProvider<T extends object>(func: FunctionalStore<T>): T {
!func.token && (func.token = Symbol('functional store'));
const depends = func();
provide(func.token, depends);
return depends;
}
export function useProviders(...funcs: FunctionalStore<any>[]) {
funcs.forEach( func => {
!func.token && (func.token = Symbol('functional store'));
provide(func.token, func());
});
}
\ No newline at end of file
import Taro from '@tarojs/taro';
import { useState } from "./useState";
import { ResBody } from '../../types/https';
import { from } from 'rxjs';
import { finalize, pluck, tap } from 'rxjs/operators';
import { onMounted, Ref, UnwrapRef, watch } from 'vue';
import { useInjector } from './useInjector';
import { useAuthStore } from '../store/auth/useAuthStore';
import { environment } from '../../contains/environment';
interface RequestOption {
auto?: boolean;
method?: 'GET' | 'POST' | 'OPTIONS';
pluck?: string[];
successTip?: string;
loadingTip?: string;
type?: 'ref' | 'reactive';
callback?: () => void;
}
export type HttpStatus = 'pending' | 'success' | 'failed' | 'error';
const defaultOption: RequestOption = {
auto: true,
method: 'POST',
pluck: ['data'],
type: 'ref',
}
// [Ref<UnwrapRef<T>>, (data?: any) => Observable<UnwrapRef<T>>, Ref<HttpStatus> ]
export function useRequest<T>(
url: string,
params: any = {},
options: RequestOption = defaultOption
): [Ref<UnwrapRef<T>>, (data?: any) => void, Ref<HttpStatus> ] {
const [ status, setStatus ] = useState<HttpStatus>(null);
const [ resData, setResData ] = useState<T>(null);
const opt = { ...defaultOption, ...options };
const authInfo = useInjector(useAuthStore, 'root');
onMounted(() => {
if (!opt.auto) { return };
doRequest();
})
watch(status, val => {
// Taro.hideToast()
switch (val ) {
case 'pending':
if (!opt.loadingTip) {return};
Taro.showToast({
title: opt.loadingTip,
icon: 'loading'
})
break;
case 'success':
if (!opt.successTip) return;
Taro.showToast({title: opt.successTip})
break;
case 'error':
case 'failed':
Taro.showToast({title: '请求失败', icon:'none'})
break;
}
})
const doRequest = (data: any = {}) => {
setStatus('pending') ;
from(
Taro.request({
url: environment.baseUrl + url,
method: opt.method,
data: {...params, ...data, token: authInfo.token.value, uid: authInfo.user.value.id}
})
).pipe(
tap(console.log),
tap(
res => setStatus(res.data.success ?'success': 'failed'),
_ => {setStatus('error'); console.log(_)}
),
pluck<ResBody<T>, UnwrapRef<T>>('data', ...opt.pluck as any),
tap(setResData),
finalize(() => Taro.hideToast())
).subscribe()
}
return [ resData, doRequest, status ]
}
import { Ref, ref, UnwrapRef } from "vue";
interface Payload {
}
export function useState<T>(state: T): [Ref<UnwrapRef<T>>, (param: UnwrapRef<T>) => void ] {
const stateRef = ref(state);
function setState(payload: UnwrapRef<T>): void;
function setState(payload: (current: T) => T): void;
function setState(payload): void {
if(typeof payload === 'function') {
stateRef.value = payload(stateRef.value)
} else {
stateRef.value = payload
}
}
return [stateRef, setState]
}
export function useAuthIntercept() {
}
\ No newline at end of file
import { useState } from '../../../hooks/common/useState';
import { UserData } from 'src/types/auth';
import { onBeforeMount } from 'vue';
import Taro from '@tarojs/taro'
export function useAuthStore() {
const [token, setToken] = useState('');
const [user, setUser] = useState<UserData>(null);
onBeforeMount( () => {
const token = Taro.getStorageSync<string>('token');
const user = Taro.getStorageSync<UserData>('user');
setToken(token);
setUser(user);
})
const saveToken = (token: string) => {
setToken(token);
Taro.setStorageSync('token', token);
}
const saveUserInfo = (user: UserData) => {
setUser(user);
Taro.setStorageSync('user', user)
}
return {
token,
user,
saveToken,
saveUserInfo
}
}
import { watch } from "vue";
import { useInjector } from "../../../hooks/common/useInjector";
import { useRequest } from "../../../hooks/common/useRequest";
import { useState } from "../../../hooks/common/useState";
import { useAuthStore } from "../auth/useAuthStore";
import { useAgoraClient } from "./useAgoraClient";
export function useVideoCallStore() {
const [isMini, setMiniState] = useState(false);
const [channelInfo, getChannelInfo, status] = useRequest<any>('/getAgoraToken', {}, {auto: false});
const { user } = useInjector(useAuthStore);
const agora = useAgoraClient();
const joinChannel = (channel_id?: string) => {
getChannelInfo({channel_id});
watch( status, val => {
if(val === 'success') {
const token = channelInfo.value.agora_token;
const cid = channelInfo.value.channel_id;
agora.joinChannel(token, cid, user.value.id);
}
})
}
return {
isMini,
streams: agora.streamList,
action: {
setMiniState,
joinChannel
}
}
}
import { BehaviorSubject, Observable, Subject } from 'rxjs';
import { filter, tap } from 'rxjs/operators';
import { useState } from '../../../hooks/common/useState';
import { onMounted } from 'vue';
import * as AgoraMiniappSDK from '../../../libs/Agora_SDK_for_WeChat';
interface VideoItemData {
url: string;
uid?: string;
rotation?: string;
}
AgoraMiniappSDK.LOG.onlog = (text) => {
console.log(text);
};
const AGORA_APPID = '0a67966ff4ae4eb3b32446df0151e16a';
const streamLog$ = new Subject<string>();
const client = new AgoraMiniappSDK.Client();
const agoraInit$ =
new Observable<boolean>( observer => {
client.init(AGORA_APPID, () => {
streamLog$.next('初始化成功')
observer.next(true);
observer.complete();
}, (e: any) => observer.error(e));
})
export function useAgoraClient() {
const channelState$ = new BehaviorSubject<string>('leave');
const [streamList, setStreamList] = useState<VideoItemData[]>([])
onMounted( () => {
streamLog$.subscribe(console.log);
subscribeRemoteStream();
})
const joinChannel = (ctoken: string, cid: string, uid: string) => {
agoraInit$
.pipe(tap(_=>console.log('准备加入频道')))
.subscribe(
_ => client.join(ctoken, cid, uid, () => {
channelState$.next('joined');
streamLog$.next('频道加入成功'+ctoken);
pushLocalStream();
}, console.log),
console.log
)
}
const leaveChannel = () => {
client.leave( () => channelState$.next('leave'))
}
const subscribeRemoteStream = () => {
client.on("update-url", e => {
console.log('genx', e)
})
client.on("stream-added", (e: any) => {
console.log('新人加入', e)
const uid = e.uid
client.subscribe(
uid,
(url: string) => {
const newStream = {url, uid}
setStreamList(streamList.value.concat([newStream]))
}
);
});
client.on("stream-removed", e => {
console.log('有人退出', e)
const uid = e.uid;
const index = streamList.value.findIndex( item => item.uid === uid );
const list = streamList.value.splice(index, 1);
setStreamList(list);
})
}
const pushLocalStream = () => {
channelState$
.pipe(
filter( state => state === 'joined' )
)
.subscribe( _ => {
console.log('推送本地流')
client.publish( (url: string) => {
console.log('推送成功', url)
const list = streamList.value.concat([{ url }])
setStreamList(list)
} )
})
}
return {
streamList,
joinChannel,
leaveChannel,
pushLocalStream
}
}
\ No newline at end of file
import { useRequest } from "../../common/useRequest";
export function useChannelInfo() {
const [channelInfo, request] = useRequest<any>('/getAgoraToken');
return {
channelInfo,
request
}
}
\ No newline at end of file
import Taro from '@tarojs/taro';
import { UserData } from "src/types/auth";
import { LoginData } from "src/types/user";
import { watch } from "vue";
import { useFormModel } from "../common/useFormModel";
import { useInjector } from "../common/useInjector";
import { useAuthStore } from "../store/auth/useAuthStore";
const loginTips = {
successTip: '登录成功',
loadingTip: '登录中...'
}
export function useLoginForm() {
const [forms, action, states, res ] = useFormModel<LoginData, UserData>('/loginIn', {}, loginTips);
const authStore = useInjector(useAuthStore, 'root');
watch(res, val => {
authStore.saveToken(val.token);
authStore.saveUserInfo(val);
})
watch(states.httpStatus, val => {
if(val === 'success') {
Taro.navigateTo({url: '/pages/meeting/index'})
}
})
return {
forms,
action
}
}
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<meta content="width=device-width,initial-scale=1,user-scalable=no" name="viewport">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-touch-fullscreen" content="yes">
<meta name="format-detection" content="telephone=no,address=no">
<meta name="apple-mobile-web-app-status-bar-style" content="white">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" >
<title></title>
<script>
!function(x){function w(){var v,u,t,tes,s=x.document,r=s.documentElement,a=r.getBoundingClientRect().width;if(!v&&!u){var n=!!x.navigator.appVersion.match(/AppleWebKit.*Mobile.*/);v=x.devicePixelRatio;tes=x.devicePixelRatio;v=n?v:1,u=1/v}if(a>=640){r.style.fontSize="40px"}else{if(a<=320){r.style.fontSize="20px"}else{r.style.fontSize=a/320*20+"px"}}}x.addEventListener("resize",function(){w()});w()}(window);
</script>
</head>
<body>
<div id="app"></div>
</body>
</html>
This source diff could not be displayed because it is too large. You can view the blob instead.
export default {
navigationBarTitleText: '首页'
}
<template>
<view class="index">
<button @tap="gotoLogin()">登录</button>
<button @tap="gotoMeeting()">进入会议</button>
</view>
</template>
<script setup>
import { navigateTo } from '@tarojs/taro';
import './index.less';
const gotoMeeting = () => {
navigateTo({url: '/pages/meeting/index'})
}
const gotoLogin = () => {
navigateTo({url: '/pages/login/index'})
}
</script>
<script setup>
</script>
<template>
<view></view>
</template>
\ No newline at end of file
<script setup>
import { onMounted, watch } from 'vue';
import { useInjector } from '../../hooks/common/useInjector';
import { useVideoCallStore } from '../../hooks/store/video';
const videoMeeting = useInjector(useVideoCallStore, 'root')
onMounted( () => {
videoMeeting.action.joinChannel('3');
})
watch(videoMeeting.streams, console.log);
const onError = (e) => {console.log('err',e)}
const net = e => console.log(e)
</script>
<template>
<view class ="meeting-container">
<view
class="players"
v-for="(item, key) in videoMeeting.streams.value"
:key="key"
>
<live-player v-if="item.uid" :src="item.url" mode="RTC" style="width: 100%;height:200px;" />
<live-pusher v-else :autopush="true" @error="onError" @statechange="net" :url="item.url" :local-mirror="'enable'" mode="RTC" style="width: 100%;height:100px;" />
</view>
</view>
</template>
\ No newline at end of file
export interface UserData {
id: string;
company_id: string;
nickname: string;
avatar: string;
permission: string;
token: string;
}
\ No newline at end of file
export interface FunctionalStore<T extends object> {
(...args: any[]): T;
token?: symbol;
root?: T;
}
\ No newline at end of file
export interface ResBody<T> {
data: T;
success: boolean;
msg: string;
other: any[];
}
type RuleFunc = (v: any) => any;
export interface FormModel {
value: any;
rules: RuleFunc[];
}
export interface VuetifyTable<T> {
tableLoading: boolean;
dataList: T[];
selectedList?: T[];
totalNums: number;
filters?: any;
}
export interface ImgFile {
url: string;
id: string;
}
export interface FunctionalStore<T extends object> {
(...args: any[]): T;
token?: symbol;
}
\ No newline at end of file
export interface ChannelEvent {
type: 'join' | 'leave' | 'new_person',
}
\ No newline at end of file
export interface LoginData {
login_id?: string;
login_password?: string;
}
{
"compilerOptions": {
"target": "es2017",
"module": "commonjs",
"removeComments": false,
"preserveConstEnums": true,
"moduleResolution": "node",
"experimentalDecorators": true,
"noImplicitAny": false,
"allowSyntheticDefaultImports": true,
"outDir": "lib",
"noUnusedLocals": true,
"noUnusedParameters": true,
"strictNullChecks": false,
"sourceMap": true,
"baseUrl": ".",
"rootDir": ".",
"jsx": "react",
"allowJs": true,
"resolveJsonModule": true,
"typeRoots": [
"node_modules/@types",
"global.d.ts"
]
},
"exclude": [
"node_modules",
"dist"
],
"compileOnSave": false
}
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