
本实训项目结合云开发的云数据库和 “微信同声传译”插件,制作一个可真实运营的小学生语文听写工具,页面效果如图1所示。

▍图1 “听写小助手”页面
基于云开发的微信小程序具有众多优势,云开发模式真正解放了开发者,使得开发效率大大提升,其模式下的小程序开发和交付流程也更加便捷;云开发建立了小程序端通向腾讯云和小程序端通向微信的捷径,也为连接其他更多的腾讯云资源提供了捷径,还可以打通云到云、端到端的界限,其计算资源计费更合理,成本也更低。
在小程序互联网飞速发展的时代,教育场景被重塑,教育类小程序迎来猛增。2020年的新冠疫情为在线教育带来了新活力,推动了用户对在线教育的需求。因此,本团队基于在线教育需求研发了“听写好助手”这款小程序。
“听写好助手”是一个以语文为核心,以微信小程序为窗口,以学生及其家长为服务对象的全语音化教学平台。“听写好助手”集语音听写、错题分析、每日十词、复习提醒、个性定制、阶段复习六项功能于一身,采用语音播报模式,减少学生用眼,大大提高了学生的学习效率,同时也减轻了家长在为孩子辅导听写作业上的压力。
本案例以云开发的云数据库为基础,制作一个面向小学语文听写的微信小程序。
为了实现“听写小助手”的语音播放功能,需要添加插件“微信同声传译”,具体步骤为:登录微信平台,选择“设置”→“第三方设置”→“插件管理”→“搜索插件”并完成添加。添加插件后打开“控制台”→“数据库”,将数据库文件导入数据库,从而完成了小学六年课后的所有单词的储存。最后为了前后端的用户互动需要用云函数来进行操作,为此要完成同步云函数列表以及上传并部署getContent和getUserCollectList云函数操作,重新编译后选择一年级上册的书,即可实现听写功能。同样的导入剩余的数据库集合即可实现所有书册的听写功能。
听写数据单个集合每条记录包含的字段,如图2所示。

▍图2 rn_11集合导入完成
本案例开发主要包括添加插件、数据库页面、云函数上传部署三个步骤。
听写好助手的代码中使用了微信同声传译的插件,这是由于听写好助手需要将存在数据库中的文字转换成语音,要让代码正常跑起来,需要登录微信公众平台,在“设置”→“第三方设置”→“插件管理”中,添加插件“微信同声传译”,添加插件后,如图3所示。

▍图3添加插件“微信同声传译”
添加完插件后再进行重新编译,会发现还有报错,原因是云开发数据库里没有需要的课本对应的数据记录,因此需要进行数据库的导入。数据库文件具体如图四所示。其中,rn_11对应的是一年级上册的听写数据,rn_12对应的是一年级下册的听写数据,以此类推。

▍图4 数据库文件
右击cloudfunctions,选择“同步云函数列表”,完成同步云函数列表以及上传并部署getContent和getUserCollectList云函数操作,重新编译后选择一年级上册的书,即可实现听写功能。同样的导入剩余的数据库集合即可实现所有书册的听写功能,如图5所示。

▍图5 同步云函数列表
pages/chooseBook/chooseBook.wxml的代码如下:
<view id="chooseBook">
<button
class='toCollect'
bindtap='toCollect'
>错题</button>
<button class='button' open-type="feedback">
<icon type="info_circle" color='rgba(255, 0, 0, 0.6)' size="16" style='margin-right:2px;'></icon>
<text class='button_title'>反馈建议</text>
</button>
<view class='tab'>
<scroll-view scroll-x="true" class='tab-nav' scroll-left='{{scrollLeft}}' scroll-with-animation="true">
<view wx:for="{{navlist}}" wx:key="unique" class='{{current==index?"on":""}}' data-current="{{index}}" bindtap='tab'>{{item}}</view>
</scroll-view>
<swiper class='tab-box'zz current="{{current}}" bindchange="eventchange">
<swiper-item wx:for="{{conlist}}" wx:key="unique">
<view class='tip'>左右滑动切换哦</view>
<view class="module-container">
<view class="box-wrapper" wx:for="{{item.moudles}}" wx:key="index">
<navigator url="{{item.url}}" hover-class="none">
<view class="servicebox">
<image src="{{item.src}}" class="box-img"/>
<text style='font-size: 35rpx;'>{{item.text}}</text>
</view>
</navigator>
</view>
</view>
</swiper-item>
</swiper>
</view>
</view>
pages/chooseBook/chooseBook.js的代码如下:
const app = getApp()
Page({
data: {
current: 0,//当前所在滑块的 index
navlist: ["一二年级", "三四年级", "五六年级"],
//课本列表
conlist: []
},
//tab切换
tab: function (event) {
this.setData({ current: event.target.dataset.current })
//锚点处理
},
//滑动事件
eventchange: function (event) {
this.setData({ current: event.detail.current })
//锚点处理
},
//生命周期函数--监听页面加载
onLoad: function (options) {
this.setData({
conlist: [
{
moudles: [
{
url: './chooseLesson/chooseLesson?book=rn_11',
src: '/img/book/ch_rn_11.jpg',
text: '部编版一年级上册'
},
{
url: './chooseLesson/chooseLesson?book=rn_12',
src: '/img/book/ch_rn_12.jpg',
text: '部编版一年级下册'
},
{
url: './chooseLesson/chooseLesson?book=rn_21',
src: '/img/book/ch_rn_21.jpg',
text: '部编版二年级上册'
},
{
url: './chooseLesson/chooseLesson?book=rn_22',
src: '/img/book/ch_rn_22.jpg',
text: '部编版二年级下册'
}
]
},
{
moudles: [
{
url: './chooseLesson/chooseLesson?book=rn_31',
src: '/img/book/ch_rn_31.jpg',
text: '部编版三年级上册'
},
{
url: './chooseLesson/chooseLesson?book=rn_32',
src: '/img/book/ch_rn_32.jpg',
text: '部编版三年级下册'
},
{
url: './chooseLesson/chooseLesson?book=rn_41',
src: '/img/book/ch_rn_41.jpg',
text: '人教版四年级上册'
},
{
url: './chooseLesson/chooseLesson?book=rn_42',
src: '/img/book/ch_rn_42.jpg',
text: '人教版四年级下册'
}
]
},
{
moudles: [
{
url: './chooseLesson/chooseLesson?book=rn_51',
src: '/img/book/ch_rn_51.jpg',
text: '人教版五年级上册'
},
{
url: './chooseLesson/chooseLesson?book=rn_52',
src: '/img/book/ch_rn_52.jpg',
text: '人教版五年级下册'
},
{
url: './chooseLesson/chooseLesson?book=rn_61',
src: '/img/book/ch_rn_61.jpg',
text: '人教版六年级上册'
},
{
url: './chooseLesson/chooseLesson?book=rn_62',
src: '/img/book/ch_rn_62.jpg',
text: '人教版六年级下册'
}
]
},
],
})
},
toCollect: function () {
wx.navigateTo({
url: "../user/collectList/collectList",
})
},
onReady: function () {},
onShow: function () {},
onHide: function () {},
onUnload: function () {},
onPullDownRefresh: function () {},
onReachBottom: function () {},
onShareAppMessage: function () {}
})
pages/chooseBook/chooseBook.wxss的代码如下:
.button {
position: fixed;
left: 20rpx;
bottom: 30rpx;
background: #FAF0E6;
border: none;
text-align: left;
margin: 0px;
line-height: 1.6;
border-radius: 0;
}
.button::after {
border: none;
border-radius: 0;
}
.button_title {
font-size: 12px;
color: rgb(114, 112, 112);
}
.toCollect {
position: fixed;
bottom: 100rpx;
right: 40rpx;
font-size: 40rpx;
height: 70rpx;
line-height: 70rpx;
background-color: rgba(255, 213, 124, 0.925);
z-index: 999;
box-shadow: 2px 2px 2px #bbb;
}
/* tab切换效果 */
swiper {
height: 1000rpx;
}
.tab{ padding: 20rpx 0;}
.tab-nav{
height: 80rpx;
line-height: 80rpx;
}
.tab-nav view{
float: left;
height: 80rpx;
line-height: 80rpx;
background: #FAF0E6;
width: 33.33%;
font-size: 30rpx;
text-align: center;
color: #000;
}
.tab-nav view.on{
background: #FAF0E6;
color: rgb(255, 201, 18);
position: relative;
}
.tab-nav view.on:after{
content: "";
display: block;
height: 6rpx;
width: 26px;
background: rgb(243, 189, 10);
position: absolute;
bottom: 2px;
left: calc(50% - 12px);
border-radius: 16rpx;
}
.tip {
color: #aaa;
text-align: center;
font-size: 35rpx;
margin-top: 20rpx;
}
/* 书本选项 */
#chooseBook .module-container {
width: 100%;
display: flex;
flex-wrap:wrap;
box-sizing: border-box;
flex-direction:row;
justify-content: center;
margin-top: 55rpx;
}
#chooseBook .module-container .box-wrapper{
height: 300rpx;
width: 200rpx;
margin: 0 70rpx;
margin-bottom: 95rpx;
}
/* 服务选项 */
#chooseBook .module-container .box-wrapper .servicebox{
display:flex;
flex-direction:column;
justify-content:center;
align-items:center;
text-align: center;
}
#chooseBook .module-container .box-wrapper .servicebox .box-img{
height:250rpx;
width: 100%;
margin-bottom: 10rpx;
box-shadow: 2px 2px 3px #aaa;
}
代码讲解
chooseBook.js的onLoad()函数为conlist列表中每个元素设置对应的url、src和text内容,以此将这些数据绑定在chooseBook.wxml中,运行程序便可渲染显示出来。
pages/chooseBook/chooseLesson/chooseLesson.wxml的代码如下:
<view id="listen">
<view class='tab'>
<scroll-view scroll-x="true" class='tab-nav' scroll-left='{{scrollLeft}}' scroll-with-animation="true">
<view class='tab-nav-c' style='width:{{conlist.length*90}}px'>
<view wx:for="{{conlist}}" wx:key="unit" class='{{current==index?"on":""}}' data-current="{{index}}" bindtap='tab'>第{{index==0?'一':index==1?'二':index==2?'三':index==3?'四':index==4?'五':index==5?'六':index==6?'七':index==7?'八':index==8?'九':index==9?'十':''}}单元</view>
</view>
</scroll-view>
</view>
<view class='swiper-box'>
<swiper class='swiper' style='height:{{conlist[current].length*150+135}}rpx;' current="{{current}}" bindchange="eventchange">
<swiper-item wx:for="{{conlist}}" wx:key="unit">
<view class='tip'>左右滑动切换哦</view>
<view class="module-container">
<view class="box-wrapper" wx:for="{{item}}" wx:key="index">
<view class="text-box">
<text>{{item.title}}</text>
</view>
<view class="img-box" data-content='{{item}}' bindtap='toDetail'>
<image src='/img/listen2.png' mode="widthFix"></image>
</view>
</view>
</view>
</swiper-item>
</swiper>
</view>
</view>
pages/chooseBook/chooseLesson/chooseLesson.js的代码如下:
const db = wx.cloud.database();
const _ = db.command;
let plugin = requirePlugin("WechatSI");
let manager = plugin.getRecordRecognitionManager();
const innerAudioContext = wx.createInnerAudioContext();
let that;
let book;
Page({
data: {
current: 0,//当前所在滑块的 index
scrollLeft: -90,//滚动条的位置,一个选项卡宽度是90(自定义来自css),按比例90*n设置位置
conlist: [],
},
//tab切换
tab: function (event) {
// console.log(event.target.dataset.current);
this.setData({ current: event.target.dataset.current })
//锚点处理
this.setData({
scrollLeft: event.target.dataset.current * 90 - 90,
})
},
//滑动事件
eventchange: function (event) {
console.log(event.detail.current)
this.setData({ current: event.detail.current })
//锚点处理
this.setData({
scrollLeft: event.detail.current * 90 - 90,
})
},
toDetail: function (e) {
let content = '';
let speak = '';
for (let word of e.currentTarget.dataset.content.content) {
content = content + word + '/';
}
if (e.currentTarget.dataset.content.speak) {
for (let word of e.currentTarget.dataset.content.speak) {
speak = speak + word + '/';
}
}
wx.navigateTo({
url: './detail/detail?content=' + content + '&speak=' + speak + '&book=' + book,
})
},
onLoad: function (options) {
wx.showLoading({
title: '加载中',
});
book = options.book;
that = this;
// setNavigationBarTitle
let bookName = '语文';
let bookLevel = {
"11": "一年级上册",
"12": "一年级下册",
"21": "二年级上册",
"22": "二年级下册",
"31": "三年级上册",
"32": "三年级下册",
"41": "四年级上册",
"42": "四年级下册",
"51": "五年级上册",
"52": "五年级下册",
"61": "六年级上册",
"62": "六年级下册",
}
if (book.search("su") != -1) { bookName += '苏教版' } else if (book.search("zh") != -1) { bookName += '浙教版' } else if (book.search("rn") != -1 && (book.search("4") != -1 || book.search("5") != -1 || book.search("6") != -1)) { bookName += '人教版' } else { bookName += '部编版' }
for (let key in bookLevel) {
if (book.search(key) != -1) {
bookName += bookLevel[key]
}
}
wx.setNavigationBarTitle({
title: bookName
})
let dbBook = book;
let conlist = [];
// 使用云函数,能读100条
wx.cloud.callFunction({
name: 'getContent',
data: {
dbBook: dbBook
}
}).then(res => {
that.setData({
conlist: res.result
});
wx.hideLoading();
})
},
onReady: function () {
},
onShow: function () {
},
onHide: function () {
},
onUnload: function () {
innerAudioContext.offPlay();
},
onPullDownRefresh: function () {
},
onReachBottom: function () {},
onShareAppMessage: function () {
}
})
pages/chooseBook/chooseLesson/chooseLesson.wxss的代码如下:
page {
background-color: #fff;
}
/* tab切换效果 */
.swiper-box {
/* overflow-y: scroll; */
height: 90%;
position: absolute;
width: 100%;
}
.swiper {
min-height: 100%;
width: 100%;
height: 100%;
}
.tip {
color: #888;
/* border-bottom: 1px solid #f2f2f2; */
text-align: center;
font-size: 35rpx;
line-height: 35rpx;
padding: 30rpx;
}
scroll-view{
width: 100%;
height: 100%;/*动态高度*/
overflow-y: scroll;
}
/* 顶部tab */
.tab{
height: 80rpx;
box-shadow: 0px 2px 3px #888888;
}
.tab-nav{
height: 80rpx;
line-height: 80rpx;
width: 100%;
background-color: #FAF0E6;
}
.tab-nav .tab-nav-c view{
height: 80rpx;
line-height: 80rpx;
float: left;
width: 90px;
font-size: 30rpx;
text-align: center;
color: #000;
}
.tab-nav view.on{
background: #FAF0E6;
color: rgb(255, 201, 18);
position: relative;
}
.tab-nav view.on:after{
content: "";
display: block;
height: 6rpx;
width: 26px;
background: rgb(243, 189, 10);
position: absolute;
bottom: 2px;
left: 32px;
border-radius: 16rpx;
}
/* 词语 */
#listen .module-container {
width: 100%;
display: flex;
flex-wrap:nowrap;
flex-direction:column;
justify-content: center;
align-items: center;
}
#listen .module-container .box-wrapper{
background-color: #f2f2f2;
border-bottom: 1px solid #c2c2c2;
display: flex;
flex-direction: row;
align-items: center;
flex-wrap:nowrap;
width: 100%;
height: 150rpx;
justify-content: center;
}
#listen .module-container .box-wrapper .text-box{
display: flex;
width: 70%;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
}
#listen .module-container .box-wrapper .text-box text{
font-size: 40rpx;
text-align: center;
line-height: 60rpx;
}
#listen .module-container .box-wrapper .img-box {
width: 20%;
}
#listen .module-container .box-wrapper .img-box image {
width: 100%;
}
/* 服务选项 */
#listen .module-container .box-wrapper .servicebox{
display:flex;
flex-direction:column;
justify-content:center;
align-items:center;
text-align: center;
}
#listen .module-container .box-wrapper .servicebox .box-img{
height:250rpx;
width: 100%;
margin-bottom: 5rpx;
}
代码讲解
chooseLesson .js的onLoad()函数自动执行对云数据库的查询操作,获取到云数据库中课本的数据,并赋值给“book”,然后通过数据绑定的方式在chooseLesson.wxml中进行渲染显示。
pages/chooseBook/chooseLesson/detail/detail.wxml的代码如下:
<view id='detail'>
<van-transition name="fade" duration='1000' show="{{show}}" style="{{i==sum?'display:none':''}}">
<view style="width:80%;margin:0 auto;position:relitive;top:-80rpx;">
<van-steps
steps="{{ steps }}"
active="{{ active }}"
/>
</view>
<view class="page__bd">
<view class="icon-box" bindtap='preWord'>
<image
class='icon'
style=' width: 150rpx;height: 150rpx;'
src="/img/pre.png"
>上一个</image>
<view class="icon-box__ctn">
<view class="icon-box__title">上一个</view>
</view>
</view>
<view class="icon-box" bindtap='nextWord'>
<image
class='icon'
src="/img/{{(i==-1?'start':i==sum-1?'end':'next')}}.png"
>下一个</image>
<view class="icon-box__ctn">
<view class="icon-box__title">下一个</view>
</view>
</view>
<view class="icon-box" style='margin-bottom: 0;' bindtap='again'>
<image
class='icon'
style=' width: 150rpx;height: 150rpx;'
src="/img/again.png"
>再读一遍</image>
<view class="icon-box__ctn">
<view class="icon-box__title">再读一遍</view>
</view>
</view>
</view>
</van-transition>
<view style="{{i<sum?'display:none':''}}">
<view class="weui-cells__title" style="font-size:16px;color:#000;margin-bottom:40rpx;">请校对:</view>
<view class="weui-cells weui-cells_after-title">
<checkbox-group bindchange="checkboxChange">
<label class="weui-cell weui-check__label" wx:for="{{content}}" wx:key="index">
<checkbox class="weui-check" value="{{item.value}}" checked="{{item.checked}}"/>
<view class="weui-cell__hd weui-check__hd_in-checkbox">
<icon class="weui-icon-checkbox_circle" type="circle" size="23" wx:if="{{!item.checked}}"></icon>
<icon class="weui-icon-checkbox_success" type="cancel" size="23" wx:if="{{item.checked}}"></icon>
</view>
<view class="weui-cell__bd">{{item.name}}</view>
</label>
</checkbox-group>
</view>
<view class="weui-btn-area">
<button class="weui-btn" style='background-color:#fff' plain="" type="default" bindtap="submit" disabled='{{submit}}'>提交错题</button>
<button class="weui-btn weui_btn_primary" style='color:#fff;background-color:#33CC99' plain="" type="default" bindtap="submitAndAgain" disabled='{{submit}}'>再听一遍</button>
</view>
</view>
</view>
pages/chooseBook/chooseLesson/detail/detail.js的代码如下:
const db = wx.cloud.database();
const _ = db.command;
let plugin = requirePlugin("WechatSI");
let manager = plugin.getRecordRecognitionManager();
const innerAudioContext = wx.createInnerAudioContext();
let that;
let i;
let active;
let oriSpeak;
let oriContent;
let book;
Page({
data: {
i: -1,
sum: 99,
userCollect: [],
content: [],
speak: [],
steps: [],
active: -1,
show: true,
submit: false
},
// 文字转语音(语音合成)
wordToSpeak: function (word) {
let that = this;
plugin.textToSpeech({
lang: "zh_CN",
tts: true,
content: word,
success: function (res) {
console.log(" tts", res)
innerAudioContext.autoplay = true
innerAudioContext.src = res.filename
wx.showLoading({
// 提交时取消注释
mask: true,
title: '正在播放',
})
},
fail: function (res) {
console.log("fail tts", res)
}
})
},
// 下一个
nextWord: function (e) {
active = this.data.active;
i = this.data.i;
this.setData({
active: ++active,
i: i+1
});
that.wordToSpeak(this.data.speak[i+1]);
},
// 上一个
preWord: function (e) {
i = this.data.i;
i = this.data.i;
if (i > 0) {
this.setData({
active: --active,
i: i - 1
});
that.wordToSpeak(this.data.speak[i-1]);
} else {
wx.showToast({
icon: 'none',
title: '没有上一个了!',
})
}
},
// 重复
again: function (e) {
i = this.data.i;
if (i > -1) {
that.wordToSpeak(this.data.speak[i]);
} else {
wx.showToast({
icon: 'none',
title: '请先开始噢!',
})
}
},
onLoad: function (options) {
oriSpeak = options.speak;
oriContent = options.content;
book = options.book;
let content = [];
let speak = [];
let contentTemp = [];
console.log(options);
that = this;
speak = options.speak.split('/');
speak.pop();
content = options.content.split('/');
content.pop();
this.setData({
sum: content.length,
speak: (speak.length == 0 ? content : speak),
steps: content
})
for (let name of content) {
let o = {};
o['name'] = name;
o['value'] = name;
contentTemp.push(o);
}
that.setData({
content: contentTemp
})
innerAudioContext.onPlay(() => {
console.log('开始播放')
})
innerAudioContext.onError((res) => {
if (res) {
console.log(res)
wx.hideLoading(),
wx.showToast({
title: '文本格式错误',
image: '/images/fail.png',
})
}
})
innerAudioContext.onEnded(function () {
manager.start({
lang: "zh_CN"
})
wx.hideLoading()
})
},
checkboxChange: function (e) {
console.log('checkbox发生change事件,携带value值为:', e.detail.value);
var checkboxItems = this.data.content, values = e.detail.value;
for (var i = 0, lenI = checkboxItems.length; i < lenI; ++i) {
checkboxItems[i].checked = false;
for (var j = 0, lenJ = values.length; j < lenJ; ++j) {
if (checkboxItems[i].value == values[j]) {
checkboxItems[i].checked = true;
break;
}
}
}
this.setData({
content: checkboxItems,
userCollect: e.detail.value
});
},
submit: function () {
this.setData({
submit: true
})
wx.showLoading({
title: '提交中...',
mask:true
})
let userCollectID;
if (that.data.userCollect) {
db.collection('userCollectList').add({
data: {
collect: that.data.userCollect,
book: book,
createTime: db.serverDate()
},
success(res) {
wx.hideLoading();
wx.showToast({
title: '提交成功!',
duration: 3000,
mask: true
})
setTimeout(() => {
wx.navigateBack({
})
}, 1000)
}
})
} else {
wx.hideLoading();
wx.showToast({
title: '提交成功!',
duration: 3000,
mask: true
})
setTimeout(() => {
wx.navigateBack({
})
},1000)
}
},
submitAndAgain: function () {
this.setData({
submit: true
})
wx.showLoading({
title: '提交中...',
mask: true
})
let userCollectID;
if (that.data.userCollect) {
db.collection('userCollectList').add({
data: {
collect: that.data.userCollect,
book: book,
createTime: db.serverDate()
},
success(res) {
wx.hideLoading();
wx.showToast({
title: '提交成功!',
duration: 3000,
mask: true
})
setTimeout(() => {
wx.redirectTo({
url: './detail?content=' + oriContent + '&speak=' + oriSpeak
})
}, 300)
}
})
} else {
wx.hideLoading();
wx.showToast({
title: '提交成功!',
duration: 3000,
mask: true
})
setTimeout(() => {
wx.redirectTo({
url:'./detail?content=' + oriContent + '&speak=' + oriSpeak
})
}, 800)
}
},
onReady: function () {},
onShow: function () {},
onHide: function () {},
onUnload: function () {
innerAudioContext.offPlay();
innerAudioContext.offEnded();
innerAudioContext.offError();
innerAudioContext.stop();
wx.stopBackgroundAudio();
manager.start({
lang: "zh_CN"
})
wx.hideLoading()
},
onPullDownRefresh: function () {},
onReachBottom: function () {},
onShareAppMessage: function () {}
})
pages/chooseBook/chooseLesson/detail/detail.wxss的代码如下:
#detail {
position: relative;
}
.weui-cell {
width: 40%;
}
checkbox-group {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
.weui-cell__bd {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
#detail .content-box {
width: 80%;
margin: 0 auto;
margin-top: 220rpx;
display: flex;
align-items: center;
flex-direction: row;
flex-wrap: wrap;
}
#detail .content-box .content {
font-size: 60rpx;
margin: 0 20rpx;
display: line-block;
}
.page__bd {
margin-top: 90rpx;
padding: 0 30px;
text-align: left;
}
.icon-box{
margin-bottom: 80rpx;
display: flex;
align-items: center;
border: 2px solid #FF9933;
border-radius: 80rpx;
box-shadow: 4px 4px 4px #ddd;
background-color: rgba(255, 224, 51, 0.329);
padding: 30rpx 20rpx;
justify-content: center;
}
.icon-box__ctn{
flex-shrink: 100;
}
.icon-box__title{
font-size: 20px;
}
.icon {
width: 250rpx;
height: 250rpx;
margin-right: 30rpx
}
代码讲解
detail.js获取到chooseLesson.js传入的书本数据,利用微信同声传译插件提供的功能,调用wordToSpeak()函数实现文字转语音,并在该页面实现了上下切换和重复播放功能。
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
有时我需要处理键/值数据。我不喜欢使用数组,因为它们在大小上没有限制(很容易不小心添加超过2个项目,而且您最终需要稍后验证大小)。此外,0和1的索引变成了魔数(MagicNumber),并且在传达含义方面做得很差(“当我说0时,我的意思是head...”)。散列也不合适,因为可能会不小心添加额外的条目。我写了下面的类来解决这个问题:classPairattr_accessor:head,:taildefinitialize(h,t)@head,@tail=h,tendend它工作得很好并且解决了问题,但我很想知道:Ruby标准库是否已经带有这样一个类? 最佳
我正在尝试使用Curbgem执行以下POST以解析云curl-XPOST\-H"X-Parse-Application-Id:PARSE_APP_ID"\-H"X-Parse-REST-API-Key:PARSE_API_KEY"\-H"Content-Type:image/jpeg"\--data-binary'@myPicture.jpg'\https://api.parse.com/1/files/pic.jpg用这个:curl=Curl::Easy.new("https://api.parse.com/1/files/lion.jpg")curl.multipart_form_
无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD
导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵
本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01 客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02 数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit
文章目录一、概述简介原理模块二、配置Mysql使用版本环境要求1.操作系统2.mysql要求三、配置canal-server离线下载在线下载上传解压修改配置单机配置集群配置分库分表配置1.修改全局配置2.实例配置垂直分库水平分库3.修改group-instance.xml4.启动监听四、配置canal-adapter1修改启动配置2配置映射文件3启动ES数据同步查询所有订阅同步数据同步开关启动4.验证五、配置canal-admin一、概述简介canal是Alibaba旗下的一款开源项目,Java开发。基于数据库增量日志解析,提供增量数据订阅&消费。Git地址:https://github.co
C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.
1.postman介绍Postman一款非常流行的API调试工具。其实,开发人员用的更多。因为测试人员做接口测试会有更多选择,例如Jmeter、soapUI等。不过,对于开发过程中去调试接口,Postman确实足够的简单方便,而且功能强大。2.下载安装官网地址:https://www.postman.com/下载完成后双击安装吧,安装过程极其简单,无需任何操作3.使用教程这里以百度为例,工具使用简单,填写URL地址即可发送请求,在下方查看响应结果和响应状态码常用方法都有支持请求方法:getpostputdeleteGet、Post、Put与Delete的作用get:请求方法一般是用于数据查询,
我正在尝试在Rails上安装ruby,到目前为止一切都已安装,但是当我尝试使用rakedb:create创建数据库时,我收到一个奇怪的错误:dyld:lazysymbolbindingfailed:Symbolnotfound:_mysql_get_client_infoReferencedfrom:/Library/Ruby/Gems/1.8/gems/mysql2-0.3.11/lib/mysql2/mysql2.bundleExpectedin:flatnamespacedyld:Symbolnotfound:_mysql_get_client_infoReferencedf