发布时间:2023-07-16 08:00
本实训项目结合云开发的云数据库和 “微信同声传译”插件,制作一个可真实运营的小学生语文听写工具,页面效果如图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()函数实现文字转语音,并在该页面实现了上下切换和重复播放功能。
vscode打开自动提示_Python编程的最好搭档:VSCode 详细指南
初入微前端---qiankun学习这一篇就够了,并在vite中使用qiankun
MATLAB | 我用MATLAB制作了一款伪3D第一视角迷宫小游戏
SphereEx 正式成为 CNCF 会员,持续完善云原生环境下的数据库基础设施
Github Star 全球唯一的国产程序员!Apache 顶级项目Commiter。
【spring cloud】(一)使用idea创建可相互调用的多模块应用
Docker 学习笔记(一)——安装、以非root身份管理Docker