avm.js 是一个跨端开发框架,AVM(Application-View-Model)前端组件化开发模式基于规范 Web Components 组件化思维,提供蕴含虚构 DOM 和 Runtime 的编程框架 avm.js 以及多端对立编译工具,齐全兼容 Web Components 规范,同时兼容 Vue 和 React 语法糖编写代码,编译工具将 Vue 和 React 相干语法糖编译转换为 avm.js 代码。
有 Vue 和 React 开发教训的很容易上手。
1. 组件的定义和援用:
1.1 应用 stml 定义一个组件 / 页面
stml 组件兼容 Vue 单文件组件 (SFC) 标准,应用语义化的 Html 模板及对象化 js 格调定义组件或页面。stml 最终被编译为 JS 组件 / 页面,渲染到不同终端。
定义组件:
// api-test.stml:
<template>
<view class='header'>
<text>{this.data.title}</text>
</view>
</template>
<script>
export default {
name: 'api-test',
data(){
return {title: 'Hello APP'}
}
}
</script>
<style scoped>
.header{height: 45px;}
</style>
1.2 组件援用:
// app-index.stml:
<template>
<view class="app">
<img src="./assets/logo.png" />
<api-test></api-test>
</view>
</template>
<script>
import './components/api-test.stml'
export default {
name: 'app-index',
data: function () {
return {title: 'Hello APP'}
}
}
</script>
<style>
.app {
text-align: center;
margin-top: 60px;
}
</style>
2. 向子组件传值
向子组件传值采纳 props 的形式,这里以一个示例来进行阐明。
定义子组件,在 props 外面注册一个 title 属性:
// api-test.stml:
<template>
<text>{title}</text>
</template>
<script>
export default {
name:'api-test',
props:{title: String}
}
</script>
这里定义的 title 属性类型为 String,属性类型包含 String、Number、Boolean、Array、Object、Function 等。
2.1 在其它页面应用子组件时传递动态值:
// app-index.stml:
<template>
<view>
<api-test title="Hello App!"></api-test>
</view>
</template>
<script>
import './components/api-test.stml'
export default {name: 'app-index'}
</script>
2.2 通过数据绑定传递动静值:
// app-index.stml:
<template>
<view>
<api-test v-bind:title="msg"></api-test>
</view>
</template>
<script>
import './components/api-test.stml'
export default {
name: 'app-index',
data() {
return {msg: 'Hello App!'}
}
}
</script>
传递动态值时只能传递字符串类型数据,通过数据绑定的形式则能够传递任意类型的数据。
3. 监听子组件事件**
监听子组件事件和监听一般事件相似,如:
// api-index.stml:
<template>
<view>
<api-test onresult="onGetResult"></api-test>
</view>
</template>
<script>
import './components/api-test.stml'
export default {
name: 'app-index',
methods: {onGetResult(e){console.log(e.detail.msg);
}
}
}
</script>
以上示例中监听了子组件的 result 事件,子组件外面通过 fire 办法来触发监听的事件:
// app-test.stml:
<template>
<text onclick="onclick">Hello App!</text>
</template>
<script>
export default {
name:'api-test',
methods:{onclick(){let detail = {msg:'Hi'};
this.fire('result', detail);
}
}
}
</script>
fire 办法有两个参数,第一个参数为事件名称,第二个参数为要传递的自定义数据,在父组件监听办法外面通过 e.detail 获取传递的数据。
// api-index.stml:
methods: {onGetResult(e){console.log(e.detail.msg);
}
}
4. 声网组件实例
理解了以上组件的规定和用法,就能够封装本人的组件了。上面看一个基于 agoraRtc 声网模块,实现 1 对 1 语音通话的组件实例:
<template>
<view class="agorartc-call-voice_page">
<safe-area></safe-area>
<view class="agorartc-call-voice_list" v-for="(item,index) in userList">
<view class="agorartc-call-voice_userinfo">
<image class="agorartc-call-voice_userinfo-image" thumbnail='false'
style="width: 64px; height: 64px; margin-right:10px" src={{item.userimg}}></image>
<text class="agorartc-call-voice_userinfo-text">{{item.username}}</text>
</view>
<view class="agorartc-call-voice_callimg">
<image @click="fnstart_voice_call(item.userid)" thumbnail='false' style="width: 50px; height: 50px"
src="../../image/voice-call.png"></image>
</view>
</view>
<view class="agorartc-call-voice_connected" v-if="connected">
<image class="agorartc-call-voice_voice" thumbnail='false' style="width: 200px;"
src="../../image/video-voice.gif"></image>
<image class="agorartc-call-voice_hangup" @click="fnhangup()" thumbnail='false'
style="width: 64px; height: 64px;" src="../../image/video-hangup.png"></image>
</view>
</view>
</template>
<script>
export default {
name: 'agorartc-call-voice',
props: {
channel: String,
userList: Array,
rtcAppId: String
},
installed() {this.fnishasper_mic();
},
data() {
return {connected: false};
},
methods: {fnishasper_mic(_userid) {
var resultList = api.hasPermission({list: ["microphone"]
});
if (resultList[0].granted) { } else {
api.toast({msg: "须要启用麦克风权限"});
api.requestPermission({list: ["microphone"]
}, res => {if (res.list[0].granted) {}});
}
},
fnstart_voice_call(_userid) {this.fnrtc_init();
this.fnerr_listener();
this.fnjoin_channel(_userid);
},
fnrtc_init() {console.log('初始化');
var agoraRtc = api.require('agoraRtc');
agoraRtc.init({appId: this.props.rtcAppId});
},
fnjoin_channel(_userid) {console.log('121:---' + _userid);
this.data.connected = true;
var agoraRtc = api.require('agoraRtc');
agoraRtc.joinChannelSuccessListener(function (ret) {console.log(ret.uid + 'uid------');
});
agoraRtc.remoteUserJoinedListener((ret) => {console.log(ret.uid + 'remoteUserJoinedListener------');
if (ret.uid) {this.data.connected = true;}
});
// 多人语音通话,需设置角色为主播
agoraRtc.setClientRole({role: 1}, function (ret) {if (ret.code == 0) {
//success
console.log('设置主播模式胜利')
}
});
agoraRtc.enableAudio((ret) => {if (ret.code == 0) {
//success
console.log('开启音频胜利 ---' + this.props.channel);
agoraRtc.joinChannel({
channel: this.props.channel,
uid: _userid
}, function (ret) {if (ret.code == 0) {console.log('退出频道胜利');
}
});
}
});
agoraRtc.remoteUserOfflineListener((ret) => {
api.toast({msg: '对方已挂断'})
this.fnhangup();});
},
fnerr_listener() {var agoraRtc = api.require('agoraRtc');
agoraRtc.errorListener(function (ret) {if (ret.errorCode == 0) {var agoraRtc = api.require('agoraRtc');
agoraRtc.leaveChannel(function (ret) {if (ret.code == 0) {//success}
});
api.toast({msg: '通话出错!'});
}
});
},
fnhangup() {var agoraRtc = api.require('agoraRtc');
agoraRtc.leaveChannel(function (ret) {if (ret.code == 0) {//success}
});
this.data.connected = false;
}
}
};
</script>
<style>
.agorartc-call-voice_page {
height: 100%;
width: 100%;
background-color: #fff;
}
.agorartc-call-voice_list {
height: 64px;
width: 100%;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: flex-start;
margin-bottom: 10px;
}
.agorartc-call-voice_userinfo {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: flex-start;
align-items: center;
padding-left: 20px;
}
.agorartc-call-voice_callimg {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: flex-end;
align-items: center;
flex-grow: 2;
padding-right: 20px;
}
.agorartc-call-voice_connected {
position: absolute;
top: 0;
left: 0;
background-color: #fff;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: center;
}
.agorartc-call-voice_hangup {margin-top: 30px;}
</style>
avm.js 默认应用 flex 弹性盒子布局,实现 UI 时,应充分利用 flex 弹性布局原理进行布局。而实现声网语音通话的外围逻辑很简略:两个用户退出同一个频道即可。