微信小程序实训|基于云数据库的语文听写工具

news2024/11/19 13:29:09

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

 ▍图1 “听写小助手”页面

基于云开发的微信小程序具有众多优势,云开发模式真正解放了开发者,使得开发效率大大提升,其模式下的小程序开发和交付流程也更加便捷;云开发建立了小程序端通向腾讯云和小程序端通向微信的捷径,也为连接其他更多的腾讯云资源提供了捷径,还可以打通云到云、端到端的界限,其计算资源计费更合理,成本也更低。

在小程序互联网飞速发展的时代,教育场景被重塑,教育类小程序迎来猛增。2020年的新冠疫情为在线教育带来了新活力,推动了用户对在线教育的需求。因此,本团队基于在线教育需求研发了“听写好助手”这款小程序。

“听写好助手”是一个以语文为核心,以微信小程序为窗口,以学生及其家长为服务对象的全语音化教学平台。“听写好助手”集语音听写、错题分析、每日十词、复习提醒、个性定制、阶段复习六项功能于一身,采用语音播报模式,减少学生用眼,大大提高了学生的学习效率,同时也减轻了家长在为孩子辅导听写作业上的压力。

本案例以云开发的云数据库为基础,制作一个面向小学语文听写的微信小程序。

01、开发内容

为了实现“听写小助手”的语音播放功能,需要添加插件“微信同声传译”,具体步骤为:登录微信平台,选择“设置”→“第三方设置”→“插件管理”→“搜索插件”并完成添加。添加插件后打开“控制台”→“数据库”,将数据库文件导入数据库,从而完成了小学六年课后的所有单词的储存。最后为了前后端的用户互动需要用云函数来进行操作,为此要完成同步云函数列表以及上传并部署getContent和getUserCollectList云函数操作,重新编译后选择一年级上册的书,即可实现听写功能。同样的导入剩余的数据库集合即可实现所有书册的听写功能。

听写数据单个集合每条记录包含的字段,如图2所示。

▍图2 rn_11集合导入完成

本案例开发主要包括添加插件、数据库页面、云函数上传部署三个步骤。

1、添加插件

听写好助手的代码中使用了微信同声传译的插件,这是由于听写好助手需要将存在数据库中的文字转换成语音,要让代码正常跑起来,需要登录微信公众平台,在“设置”→“第三方设置”→“插件管理”中,添加插件“微信同声传译”,添加插件后,如图3所示。

▍图3添加插件“微信同声传译”

2、页面数据库

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

▍图4 数据库文件

3、云函数的上传部署

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

▍图5 同步云函数列表

02、项目代码

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()函数实现文字转语音,并在该页面实现了上下切换和重复播放功能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/409191.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vue3全家桶入门 (通过vue-cli脚手架搭建todolist项目环境,深入vue3.0核心知识)

目录 一、todolist项目准备 vue3.0环境搭建&#x1f344; 二、todolist基本结构 1. 定义组件&#x1f437; 2.实现todolist需要用到的四个组件 &#x1f436; 3.ref定义单个数据 &#x1f42d; 4.reactive定义对象类型的数据&#x1f439; 5. 实现todolist每个组件需要…

前端实战【ES6】你会ES6,但是你真的会用吗?

目录&#x1f31f;前言&#x1f31f;关于取值&#x1f31f;关于合并数据&#x1f31f;关于拼接字符串&#x1f31f;关于if中判断条件&#x1f31f;关于列表搜索&#x1f31f;关于扁平化数组&#x1f31f;关于获取对象属性值&#x1f31f;关于添加对象属性&#x1f31f;关于输入…

vue父子组件传值:父传子、子传父

最近项目中又需要用到父子组件&#xff0c;用了很多次之后对父子组件终于有种从善如流的感觉。会了之后再看自己写的父子组件传值的文章&#xff0c;感觉还是存在很多问题的&#xff0c;问题就不改了&#xff0c;在这篇文章做个总结和纠正吧。 父子组件就是在一个vue文件中引入…

瑞吉外卖项目:编辑员工信息与公共字段自动填充

目录 一. 编辑员工信息 1.1 需求分析 1.2 代码编写 执行流程 后端代码 二. 项目公共字段填充 2.1 问题分析 2.2 代码实现 2.3 功能完善 一. 编辑员工信息 1.1 需求分析 在员工管理列表点击编辑按钮&#xff0c;跳转至编辑页面后&#xff0c;回显员工数据进行修改。 …

前端开发利器--PxCook(像素大厨)

前端开发利器 - - PxCook&#xff08;像素大厨&#xff09;1、PxCook简述2、PxCook安装3、PxCook基本操作3.1 通过软件打开设计图3.2 常用快捷键3.3 常用工具3.4 从psd文件中直接获取数据1、PxCook简述 前端开发软件&#xff1a; PS&#xff08;Photoshop&#xff09;收费、占…

【SVG】路径<Path>标签详解,一次搞懂所有命令参数

在上一篇文章 什么是SVG&#xff1f;——SVG快速入门 中我对SVG做了基础的介绍&#xff0c;这篇文章将集中讲解<path>标签 本站链接&#xff1a;什么是SVG&#xff1f;——SVG快速入门_gxyzlxf的博客-CSDN博客 稀土掘金链接&#xff1a;什么是SVG&#xff1f;——SVG快…

npm修改默认源(默认镜像)

1 npm修改默认源(默认镜像) 首先如果之前安装过node.js的需要先卸载&#xff0c;删除npm原始默认安装目录&#xff08;在cmd命令面板执行 npm config ls 查看npm默认路径&#xff09; 在cmd命令面板中输入npm config ls 查看npm默认路径 删除操作如下所示&#xff1a; 重新安…

js实现base64,url,blob之间的相互转换

一般来说前端展示图片会通过三种方式&#xff1a; url、base64、blob 1.url: 一般来说&#xff0c;图片的显示还是建议使用url的方式比较好。 let url "http://xxxxxx" 2.base64&#xff1a; 如果图片较大&#xff0c;图片的色彩层次比较丰富&#xff0c;则不适合…

【原创】基于JavaWeb的医院预约挂号系统(医院挂号管理系统毕业设计)

项目介绍&#xff1a;后端采用JspServlet。前端使用的是Layui的一个网站模板。开发一个在线的医院预约挂号系统。从角色的划分&#xff0c;包括用户、医生、管理员。功能模块上包括了公告发布、医院信息查看、医院医生信息查看、预约医生、病例记录、挂号审核、图表统计等模块。…

css常见居中方法总结

最近跟着网上的教程做了几个网页项目&#xff0c;做的过程中关于居中涉及到了好几种方法&#xff0c;遂想将其总结归纳下来&#xff0c;一是理清自己的思路&#xff0c;二是希望能分享给需要帮助的小伙伴们。 话不多数&#xff0c;直奔主题。 本次涉及到的居中方法有七种&…

微信小程序嵌入 H5 页面(web-view)

在开发微信小程序的时候&#xff0c;我们有时候会遇到将 H5 页面嵌入到小程序页面中的情况&#xff1b;微信小程序自带的 web-view 组件相当于 HTML 页面中的 iframe &#xff0c;方便我们在微信小程序中打开一个 H5 页面&#xff1b; 官网描述&#xff1a; 承载网页的容器&a…

微前端(无界)

前言&#xff1a;微前端已经是一个非常成熟的领域了&#xff0c;但开发者不管采用哪个现有方案&#xff0c;在适配成本、样式隔离、运行性能、页面白屏、子应用通信、子应用保活、多应用激活、vite 框架支持、应用共享等用户核心诉求都或存在问题&#xff0c;或无法提供支持。本…

vue项目中引入Luckysheet

Luckysheet 介绍 Luckysheet &#xff0c;一款纯前端类似excel的在线表格&#xff0c;功能强大、配置简单、完全开源。 实现功能 格式设置 样式 (修改字体样式&#xff0c;字号&#xff0c;颜色或者其他通用的样式)条件格式 (突出显示所关注的单元格或单元格区域&#xff1…

微信小程序(5)——如何制作好看的表格

✅ 因为 “表格” 在日常统计中无处不在&#xff0c;所以今天来做一做。但是微信小程序不支持 table 标签&#xff0c;我准备用 “上一篇——Flex布局” 学的 flex 来实现一下。 文章目录一、从“html的table”到 “微信小程序的table”二、统一格式的表格三、非统一格式的表格…

【vuex】unknown action type:home/categoryList报错

记录一下今天遇到的bug。在做项目时&#xff0c;想进行vuex模块化开发。 在src/store下暂且建了两个小仓库home和search src/store/index.js import {createStore} from vuex; // 引入小仓库 import home from /store/home/index import search from /store/search/index// 对…

vue3.0运行npm run dev 报错Cannot find module ‘node:url‘

目录 一、问题描述&#xff1a; 二、原因 三、解决方案 一、问题描述&#xff1a; 学习vue3.0&#xff08; Vue.js - 渐进式 JavaScript 框架 | Vue.js&#xff09;的时候一直使用的家里电脑&#xff0c;项目搭建运行一直没问题&#xff0c;公司近期用vue3.0写项目 npm init…

(前后端都开源)Activiti Flowable Camunda json转bpmn 仿钉钉流程设计器 vue2vue3 Ant Design Wflow-web smart-flow-design

仿钉钉流程设计器前后端源码 2022年10月17日,重磅开源! 话不多说上码云项目链接,各位觉得有帮助可以点一个star 本项目是基于这个Flowable6.7.2实现的, 后面会开一个Activiti567的分支 本项目在码云地址: Flowable-Activiti仿钉钉流程设计器前后台源码--工作流中台项目-基于…

第一次尝试制作一个钓鱼网站,小白教程,超细!

**声明&#xff1a;小白一枚&#xff0c;写下来为了记录和学习交流&#xff0c;大神不喜勿喷。 **大体思路&#xff1a;仿页面&#xff0c;社工诱导用户填写信息&#xff0c;提交传入后端&#xff0c;后端获取信息并存储&#xff0c;传回“服务器繁忙”或虚假信息并重定向到真…

js 各种时间格式的转换

js 各种时间格式的转换 时间格式示例中国标准时间Fri Mar 18 2022 14:24:45 GMT0800(中国标准时间)部分可读字符串Fri Mar 18 2022格林威治时间Fri,18 Mar 2022 06:24:45 GMT现在用的时间标准UTCFri Mar 18 2022 06:24:45 GMTIOS标准时间&#xff08;JSON时间格式&#xff09;…

Echarts常用配置项

一、常用配置项描述 title:{}//标题组件 tooltip:{},//提示框组件 yAxis:[],//y轴 xAxis:[],//x轴 legend:{},//图例组件 grid:{},//内绘网格 toolbox:{},//工具 series:[],//数据有关 calculable:true//可计…