【易售小程序项目】私聊功能uniapp界面实现 (买家、卖家 沟通商品信息)【后端基于若依管理系统开发】

简介: 【易售小程序项目】私聊功能uniapp界面实现 (买家、卖家 沟通商品信息)【后端基于若依管理系统开发】

效果显示

WebSocket连接

使用全局变量

本小程序在用户浏览首页的时候创建WebSocket连接,并将连接获得的WebSocket对象存储到全局变量中,方便其他页面来使用WebSocket

首先在项目的main.js文件中声明全局变量socket

Vue.prototype.$socket = null

对全局变量进行赋值

Vue.prototype.$socket = this.$socket;

后续如果需要使用全局变量,直接使用this.$socket即可

WebSocket连接细节

下面的代码中有一个headbeat方法,该方法主要用来定时给WebSocket服务器发送一个信号,告诉WebSocket服务器当前客户端还处于连接状态。当心跳停止的时候(比如客户端断网),后端服务就会将用户信息从连接中移除

/**
* 创建websocket连接
 */
initWebsocket() {
  // console.log("this.socket:" + JSON.stringify(this.$socket))
  // this.$socket == null,刚刚进入首页,还没有建立过websocket连接
  // this.$socket.readyState==0 表示正在连接当中
  // this.$socket.readyState==1 表示处于连接状态
  // this.$socket.readyState==2 表示连接正在关闭
  // this.$socket.readyState==3 表示连接已经关闭
  if (this.$socket == null || (this.$socket.readyState != 1 && this.$socket.readyState != 0)) {
    this.$socket = uni.connectSocket({
      url: "ws://10.23.17.146:8085/websocket/" + uni.getStorageSync("curUser").userName,
      success(res) {
        console.log('WebSocket连接成功', res);
      },
    })
    // console.log("this.socket:" + this.$socket)
    // 监听WebSocket连接打开事件
    this.$socket.onOpen((res) => {
      console.log("websocket连接成功")
      Vue.prototype.$socket = this.$socket;
      // 连接成功,开启心跳
      this.headbeat();
    });
    // 连接异常
    this.$socket.onError((res) => {
      console.log("websocket连接出现异常");
      // 重连
      this.reconnect();
    })
    // 连接断开
    this.$socket.onClose((res) => {
      console.log("websocket连接关闭");
      // 重连
      this.reconnect();
    })
  }
},
/**
 * 重新连接
 */
reconnect() {
  console.log("重连");
  // 防止重复连接
  if (this.lockReconnect == true) {
    return;
  }
  // 锁定,防止重复连接
  this.lockReconnect = true;
  // 间隔一秒再重连,避免后台服务出错时,客户端连接太频繁
  setTimeout(() => {
    this.initWebsocket();
  }, 1000)
  // 连接完成,设置为false
  this.lockReconnect = false;
},
// 开启心跳
headbeat() {
  console.log("websocket心跳");
  var that = this;
  setTimeout(function() {
    if (that.$socket.readyState == 1) {
      // websocket已经连接成功
      that.$socket.send({
        data: JSON.stringify({
          status: "ping"
        })
      })
      // 调用启动下一轮的心跳
      that.headbeat();
    } else {
      // websocket还没有连接成功,重连
      that.reconnect();
    }
  }, that.heartbeatTime);
},

最近和自己聊天的用户信息

界面效果

界面代码

<template>
  <view class="container">
    <scroll-view @scrolltolower="getMoreChatUserVo">
      <view v-for="(chatUserVo,index) in chatUserVoList" :key="index" @click="trunToChat(chatUserVo)">
        <view style="height: 10px;"></view>
        <view class="chatUserVoItem">
          <view style="display: flex;align-items: center;">
            <uni-badge class="uni-badge-left-margin" :text="chatUserVo.unReadChatNum" absolute="rightTop"
              size="small">
              <u--image :showLoading="true" :src="chatUserVo.userAvatar" width="50px" height="50px"
                :fade="true" duration="450">
                <view slot="error" style="font-size: 24rpx;">加载失败</view>
              </u--image>
            </uni-badge>
          </view>
          <view style="margin: 10rpx;"></view>
          <view
            style="line-height: 20px;width: 100%;display: flex;justify-content: space-between;flex-direction: column;">
            <view style="display: flex; justify-content: space-between;">
              <view>
                <view class="nickname">{{chatUserVo.userNickname}}
                </view>
                <view class="content">{{chatUserVo.lastChatContent}}</view>
              </view>
              <view class="date">{{formatDateToString(chatUserVo.lastChatDate)}}</view>
            </view>
            <!-- <view style="height: 10px;"></view> -->
            <u-line></u-line>
          </view>
        </view>
      </view>
    </scroll-view>
  </view>
</template>
<script>
  import {
    listChatUserVo
  } from "@/api/market/chat.js";
  import {
    listChat
  } from "@/api/market/chat.js"
  export default {
    data() {
      return {
        chatUserVoList: [],
        page: {
          pageNum: 1,
          pageSize: 15
        },
      }
    },
    created() {
    },
    methods: {
      /**
       * 滑动到底部,自动加载新一页的数据
       */
      getMoreChatUserVo() {
        this.page.pageNum++;
        this.listChatUserVo();
      },
      listChatUserVo() {
        listChatUserVo(this.page).then(res => {
          // console.log("res:"+JSON.stringify(res.rows))
          // this.chatUserVoList = res.rows;
          for (var i = 0; i < res.rows.length; i++) {
            this.chatUserVoList.push(res.rows[i]);
          }
        })
      },
      /**
       * 格式化日期
       * @param {Object} date
       */
      formatDateToString(dateStr) {
        let date = new Date(dateStr);
        // 今天的日期
        let curDate = new Date();
        if (date.getFullYear() == curDate.getFullYear() && date.getMonth() == curDate.getMonth() && date
          .getDate() == curDate.getDate()) {
          // 如果和今天的年月日都一样,那就只显示时间
          return this.toDoubleNum(date.getHours()) + ":" + this.toDoubleNum(date.getMinutes());
        } else {
          // 如果年份一样,就只显示月日
          return (curDate.getFullYear() == date.getFullYear() ? "" : (date.getFullYear() + "-")) + this
            .toDoubleNum((
              date
              .getMonth() + 1)) +
            "-" +
            this.toDoubleNum(date.getDate());
        }
      },
      /**
       * 如果传入的数字是两位数,直接返回;
       * 否则前面拼接一个0
       * @param {Object} num
       */
      toDoubleNum(num) {
        if (num >= 10) {
          return num;
        } else {
          return "0" + num;
        }
      },
      /**
       * 转到私聊页面
       */
      trunToChat(chatUserVo) {
        let you = {
          avatar: chatUserVo.userAvatar,
          nickname: chatUserVo.userNickname,
          username: chatUserVo.userName
        }
        uni.navigateTo({
          url: "/pages/chat/chat?you=" + encodeURIComponent(JSON.stringify(you))
        })
      },
      /**
       * 接收消息
       */
      receiveMessage() {
        this.$socket.onMessage((response) => {
          // console.log("接收消息:" + response.data);
          let message = JSON.parse(response.data);
          // 收到消息,将未读消息数量加一
          for (var i = 0; i < this.chatUserVoList.length; i++) {
            if (this.chatUserVoList[i].userName == message.from) {
              this.chatUserVoList[i].unReadChatNum++;
              // 显示对方发送的最新消息
              listChat(message.from, {
                pageNum: 1,
                pageSize: 1
              }).then(res => {
                this.chatUserVoList[i].lastChatContent = res.rows[0].content
              });
              break;
            }
          }
        })
      },
    },
    onLoad(e) {
      this.receiveMessage();
    },
    onShow: function() {
      this.chatUserVoList = [];
      this.listChatUserVo();
    },
  }
</script>
<style lang="scss">
  .container {
    padding: 20rpx;
    .chatUserVoItem {
      display: flex;
      margin: 0 5px;
      .nickname {
        font-weight: 700;
      }
      .content {
        color: #A7A7A7;
        font-size: 14px;
        /* 让消息只显示1行,超出的文字内容使用...来代替 */
        overflow: hidden;
        text-overflow: ellipsis;
        display: -webkit-box;
        -webkit-line-clamp: 1;
        -webkit-box-orient: vertical;
      }
      .date {
        color: #A7A7A7;
        font-size: 12px;
      }
    }
    // .uni-badge-left-margin {
    //  margin-left: 10px;
    // }
  }
</style>

最近的聊天内容太长

当最近的一条聊天内容太长的时候,页面不太美观,缺少整齐的感觉

解决的方式非常简单,只需要添加以下样式即可

.content {
  /* 让消息只显示1行,超出的文字内容使用...来代替 */
  overflow: hidden;
  text-overflow: ellipsis;
  display: -webkit-box;
  -webkit-line-clamp: 1;
  -webkit-box-orient: vertical;
}

日期时间显示

本文显示日期时间的时候,遵循以下原则:

  • 如果上次聊天时间的年月日和今天一致,那就只显示时间,即显示 时:分
  • 如果上次聊天时间的年份和今年一致,那就只显示月-日
  • 如果上面的条件都不满足,就显示年-月-日

在显示月、日、时、分的时候,如果数字是一位数字,就在前面补一个零,具体操作如方法toDoubleNum

/**
 * 格式化日期
 * @param {Object} date
 */
formatDateToString(dateStr) {
  let date = new Date(dateStr);
  // 今天的日期
  let curDate = new Date();
  if (date.getFullYear() == curDate.getFullYear() && date.getMonth() == curDate.getMonth() && date
    .getDate() == curDate.getDate()) {
    // 如果和今天的年月日都一样,那就只显示时间
    return this.toDoubleNum(date.getHours()) + ":" + this.toDoubleNum(date.getMinutes());
  } else {
    // 如果年份一样,就只显示月日
    return (curDate.getFullYear() == date.getFullYear() ? "" : (date.getFullYear() + "-")) + this
      .toDoubleNum((
        date
        .getMonth() + 1)) +
      "-" +
      this.toDoubleNum(date.getDate());
  }
},
/**
* 如果传入的数字是两位数,直接返回;
 * 否则前面拼接一个0
 * @param {Object} num
 */
toDoubleNum(num) {
  if (num >= 10) {
    return num;
  } else {
    return "0" + num;
  }
},

未读消息数量显示

未读消息数量显示使用角标组件,即uni-badge,使用该组件需要下载安装插件,下载链接,下载之前需要看广告,哈哈哈,当然有钱可以不看

显示效果如下图

<uni-badge class="uni-badge-left-margin" :text="chatUserVo.unReadChatNum" absolute="rightTop"
  size="small">
  <u--image :showLoading="true" :src="chatUserVo.userAvatar" width="50px" height="50px"
    :fade="true" duration="450">
    <view slot="error" style="font-size: 24rpx;">加载失败</view>
  </u--image>
</uni-badge>

私聊界面

界面展示

【微信公众平台模拟的手机界面】

【手机端,键盘呼出之后的聊天区域】

代码实现

<template>
  <view style="height:100vh;">
    <!-- @scrolltoupper:上滑到顶部执行事件,此处用来加载历史消息 -->
    <!-- scroll-with-animation="true" 设置滚动条位置的时候使用动画过渡,让动作更加自然 -->
    <scroll-view :scroll-into-view="scrollToView" scroll-y="true" class="messageListScrollView"
      :style="{height:scrollViewHeight}" @scrolltoupper="getHistoryChat()"
      :scroll-with-animation="!isFirstListChat" ref="chatScrollView">
      <view v-for="(message,index) in messageList" :key="message.id" :id="`message`+message.id"
        style="width: 750rpx;min-height: 60px;">
        <view style="height: 10px;"></view>
        <view v-if="message.type==0" class="messageItemLeft">
          <view style="width: 8px;"></view>
          <u--image :showLoading="true" :src="you.avatar" width="50px" height="50px" radius="3"></u--image>
          <view style="width: 7px;"></view>
          <view class="messageContent left">
            {{message.content}}
          </view>
        </view>
        <view v-if="message.type==1" class="messageItemRight">
          <view class="messageContent right">
            {{message.content}}
          </view>
          <view style="width: 7px;"></view>
          <u--image :showLoading="true" :src="me.avatar" width="50px" height="50px" radius="3"></u--image>
          <view style="width: 8px;"></view>
        </view>
      </view>
    </scroll-view>
    <view class="messageSend">
      <view class="messageInput">
        <u--textarea v-model="messageInput" placeholder="请输入消息内容" autoHeight>
        </u--textarea>
      </view>
      <view style="width:5px"></view>
      <view class="commmitButton" @click="send()">发 送</view>
    </view>
  </view>
</template>
<script>
  import {
    getUserProfileVo
  } from "@/api/user";
  import {
    listChat
  } from "@/api/market/chat.js"
  let socket;
  export default {
    data() {
      return {
        webSocketUrl: "",
        socket: null,
        messageInput: '',
        // 我自己的信息
        me: {},
        // 对方信息
        you: {},
        scrollViewHeight: undefined,
        messageList: [],
        // 底部滑动到哪里
        scrollToView: '',
        page: {
          pageNum: 1,
          pageSize: 15
        },
        isFirstListChat: true,
        loadHistory: false,
        // 消息总条数
        total: 0,
      }
    },
    created() {
      this.me = uni.getStorageSync("curUser");
    },
    beforeDestroy() {
      console.log("执行销毁方法");
      this.endChat();
    },
    onLoad(e) {
      // 设置初始高度
      this.scrollViewHeight = `calc(100vh - 20px - 44px)`;
      this.you = JSON.parse(decodeURIComponent(e.you));
      uni.setNavigationBarTitle({
        title: this.you.nickname,
      })
      this.startChat();
      this.listChat();
      this.receiveMessage();
    },
    onReady() {
      // 监听键盘高度变化,以便设置输入框的高度
      uni.onKeyboardHeightChange(res => {
        let keyBoardHeight = res.height;
        console.log("keyBoardHeight:" + keyBoardHeight);
        this.scrollViewHeight = `calc(100vh - 20px - 44px - ${keyBoardHeight}px)`;
        this.scrollToView = '';
        setTimeout(() => {
          this.scrollToView = 'message' + this.messageList[this
            .messageList.length - 1].id;
        }, 150)
      })
    },
    methods: {
      /**
       * 发送消息
       */
      send() {
        if (this.messageInput != '') {
          let message = {
            from: this.me.userName,
            to: this.you.username,
            text: this.messageInput
          }
          // console.log("this.socket.send:" + this.$socket)
          // 将组装好的json发送给服务端,由服务端进行转发
          this.$socket.send({
            data: JSON.stringify(message)
          });
          this.total++;
          let newMessage = {
            // code: this.messageList.length,
            type: 1,
            content: this.messageInput
          };
          this.messageList.push(newMessage);
          this.messageInput = '';
          this.toBottom();
        }
      },
      /**
       * 开始聊天
       */
      startChat() {
        let message = {
          from: this.me.userName,
          to: this.you.username,
          text: "",
          status: "start"
        }
        // 告诉服务端要开始聊天了
        this.$socket.send({
          data: JSON.stringify(message)
        });
      },
      /**
       * 结束聊天
       */
      endChat() {
        let message = {
          from: this.me.userName,
          to: this.you.username,
          text: "",
          status: "end"
        }
        // 告诉服务端要结束聊天了
        this.$socket.send({
          data: JSON.stringify(message)
        });
      },
      /**
       * 接收消息
       */
      receiveMessage() {
        this.$socket.onMessage((response) => {
          // console.log("接收消息:" + response.data);
          let message = JSON.parse(response.data);
          let newMessage = {
            // code: this.messageList.length,
            type: 0,
            content: message.text
          };
          this.messageList.push(newMessage);
          this.total++;
          // 让scroll-view自动滚动到最新的数据那里
          // this.$nextTick(() => {
          //  // 滑动到聊天区域最底部
          //  this.scrollToView = 'message' + this.messageList[this
          //    .messageList.length - 1].id;
          // });
          this.toBottom();
        })
      },
      /**
       * 查询对方和自己最近的聊天数据
       */
      listChat() {
        return new Promise((resolve, reject) => {
          listChat(this.you.username, this.page).then(res => {
            for (var i = 0; i < res.rows.length; i++) {
              this.total = res.total;
              if (res.rows[i].fromWho == this.me.userName) {
                res.rows[i].type = 1;
              } else {
                res.rows[i].type = 0;
              }
              // 将消息放到数组的首位
              this.messageList.unshift(res.rows[i]);
            }
            if (this.isFirstListChat == true) {
              // this.$nextTick(function() {
              //  // 滑动到聊天区域最底部
              //  this.scrollToView = 'message' + this.messageList[this
              //    .messageList.length - 1].id;
              // })
              this.toBottom();
              this.isFirstListChat = false;
            }
            resolve();
          })
        })
      },
      /**
       * 滑到最顶端,分页加一,拉取更早的数据
       */
      getHistoryChat() {
        // console.log("获取历史消息")
        this.loadHistory = true;
        if (this.messageList.length < this.total) {
          // 当目前的消息条数小于消息总量的时候,才去查历史消息
          this.page.pageNum++;
          this.listChat().then(() => {})
        }
      },
      /**
       * 滑动到聊天区域最底部
       */
      toBottom() {
        // 让scroll-view自动滚动到最新的数据那里
        this.scrollToView = '';
        setTimeout(() => {
          // 滑动到聊天区域最底部
          this.scrollToView = 'message' + this.messageList[this
            .messageList.length - 1].id;
        }, 150)
      }
    }
  }
</script>
<style lang="scss">
  .messageListScrollView {
    background: #F5F5F5;
    overflow: auto;
    .messageItemLeft {
      display: flex;
      align-items: flex-start;
      justify-content: flex-start;
      .messageContent {
        max-width: calc(750rpx - 10px - 50px - 15px - 10px - 50px - 15px);
        padding: 10px;
        // margin-top: 10px;
        border-radius: 7px;
        font-family: sans-serif;
        // padding: 10px;
        // 让view只包裹文字
        width: auto;
        // display: inline-block !important;
        // display: inline;
        // 解决英文字符串、数字不换行的问题
        word-break: break-all;
        word-wrap: break-word;
      }
    }
    .messageItemRight {
      display: flex;
      align-items: flex-start;
      justify-content: flex-end;
      .messageContent {
        max-width: calc(750rpx - 10px - 50px - 15px - 10px - 50px - 15px);
        padding: 10px;
        // margin-top: 10px;
        border-radius: 7px;
        font-family: sans-serif;
        // padding: 10px;
        // 让view只包裹文字
        width: auto;
        // display: inline-block !important;
        // display: inline;
        // 解决长英文字符串、数字不换行的问题
        word-wrap: break-word;
      }
    }
    .right {
      background-color: #94EA68;
    }
    .left {
      background-color: #ffffff;
    }
  }
  .messageSend {
    display: flex;
    background: #ffffff;
    padding-top: 5px;
    padding-bottom: 15px;
    .messageInput {
      border: 1px #EBEDF0 solid;
      border-radius: 5px;
      width: calc(750rpx - 65px);
      margin-left: 5px;
    }
    .commmitButton {
      height: 38px;
      border-radius: 5px;
      width: 50px;
      display: flex;
      align-items: center;
      justify-content: center;
      color: #ffffff;
      background: #3C9CFF;
    }
  }
</style>

英文长串不换行问题

这个问题属于是整串英文被以为是一个单词了,所以没有换行,看下面的句子,英文单词可以比较短的,所以会自动换行

解决这个问题只需要添加下面的css即可

// 解决长英文字符串、数字不换行的问题
word-wrap: break-word;

下面是添加之后的效果

聊天区域自动滑动到底部

在聊天的时候,无论是发送一条新的消息,还是接收到一条新的消息,聊天区域都需要自动滑动到最新的消息那里。本文使用scroll-view组件来包裹显示聊天消息,在scroll-view组件中,可以通过给scroll-into-view属性赋值来指定聊天区域所显示到的位置。使用时需要注意如下问题:

  • 需要先给每一条消息设置一个id属性,id属性存储的内容不能以数字开头,因此本文在id之间拼接了一个字符串’message’
  • scroll-view需要被设置好高度,本文通过绑定一个变量来设置高度,如:style="{height:scrollViewHeight}",因为手机端使用小程序打字时键盘呼出会影响聊天区域的高度

后续通过给scrollToView设置不同的值即可控制聊天区域的滑动,比如每接收到一条新的消息,就调用toBottom方法,该方法通过设置scrollToView为'message' + this.messageList[this.messageList.lengthh - 1].id将聊天区域滑动到最新的消息处。需要注意的是,在进行该值的设置之前,需要延迟一段时间,否则滑动可能不成功,本文延迟150ms,读者也可以探索不同的值,该值不能太大或者太小。


通过设置scroll-view的属性scroll-with-animation的值为true,可以让消息区域在滑动的时候使用动画过渡,这样滑动更加自然。


键盘呼出,聊天区域收缩,聊天区域滑动到底部

当键盘呼出时,需要将聊天区域的高度减去键盘的高度。同时将scrollToView赋值为最后一条消息的id。需要注意的是,在设置scrollToView之前,需要先将scrollToView设置为空字符串,否则滑动效果可能不成功

onReady() {
  // 监听键盘高度变化,以便设置输入框的高度
  uni.onKeyboardHeightChange(res => {
    let keyBoardHeight = res.height;
    console.log("keyBoardHeight:" + keyBoardHeight);
    this.scrollViewHeight = `calc(100vh - 20px - 44px - ${keyBoardHeight}px)`;
    this.scrollToView = '';
    setTimeout(() => {
      this.scrollToView = 'message' + this.messageList[this
        .messageList.length - 1].id;
    }, 150)
  })
},

通知WebSocket服务器哪两个用户开始聊天

为了便于后端在存储聊天数据的时候辨别消息是否为已读状态。比如,在小王开始聊天之前,需要先告诉后端:“小王要开始和小明聊天了”,如果正好小明也告诉后端:“我要和小王聊天了”,那小王发出去的消息就会被设置为已读状态,因为他们两个此时此刻正在同时和对方聊天,那小王发出去的消息就默认被小明看到了,因此设置为已读状态

/**
* 开始聊天
*/
startChat() {
  let message = {
    from: this.me.userName,
    to: this.you.username,
    text: "",
    status: "start"
  }
  // 告诉服务端要开始聊天了
  this.$socket.send({
    data: JSON.stringify(message)
  });
},
/**
 * 结束聊天
 */
endChat() {
  let message = {
    from: this.me.userName,
    to: this.you.username,
    text: "",
    status: "end"
  }
  // 告诉服务端要结束聊天了
  this.$socket.send({
    data: JSON.stringify(message)
  });
},
目录
相关文章
|
3天前
|
移动开发 小程序 前端开发
几千怎么部署搭建校园服务平台,校园圈子论坛系统小程序搭建,校园系统源码,多种功能一体的综合性校园平台
校园圈子论坛是一款集交友、二手市场、聊天等多功能于一体的校园社交平台,支持App、小程序和H5三端交付。学生可处理闲置物品、结识新朋友,通过算法匹配兴趣相投的用户。平台提供分享邀请机制,支持自动绑定推荐关系,并设有奖励机制。开发过程中需注重数据安全与系统稳定性,确保功能兼容及性能优化。
30 5
|
3天前
|
移动开发 小程序 前端开发
使用php开发圈子系统特点,如何获取圈子系统源码,社交圈子运营以及圈子系统的功能特点,圈子系统,允许二开,免费源码,APP 小程序 H5
开发一个圈子系统(也称为社交网络或社群系统)可以是一个复杂但非常有趣的项目。以下是一些关键特点和步骤,帮助你理解如何开发、获取源码以及运营一个圈子系统。
43 3
|
6天前
|
小程序 安全 搜索推荐
陪玩小程序的搭建解析与功能需求
陪玩小程序是为玩家提供专业陪玩服务的应用,嵌入社交或游戏平台,具备智能匹配、实时聊天、预约服务等功能,支持便捷高效的游戏体验。源码交付时需提供详细文档、技术支持及定制开发服务,确保客户能顺利维护和升级。选择陪玩小程序时应关注功能需求、用户体验、安全性和成本效益,以确保最佳使用效果。
34 0
|
3天前
|
小程序 安全 网络安全
清晰易懂!陪玩系统源码搭建的核心功能,陪玩小程序、陪玩app的搭建步骤!
陪玩系统源码包含多种约单方式、实时语音互动、直播间与聊天室、大神申请与抢单、动态互动与社交及在线支付与评价等核心功能。搭建步骤包括环境准备、源码上传与解压、数据库配置、域名与SSL证书绑定、伪静态配置及后台管理。注意事项涵盖源码安全性、二次开发、合规性和技术支持。确保平台安全、合规并提供良好用户体验是关键。
|
1月前
|
人工智能 小程序 搜索推荐
uni app下开发AI运动小程序解决方案
本文介绍了在小程序中实现AI运动识别的解决方案。该方案依托于UNI平台,通过高效便捷的插件形式,实现包括相机抽帧控制、人体识别、姿态识别等在内的多项功能,无需依赖后台服务器,大幅提高识别效率和用户体验。方案内置多种运动模式,支持自定义扩展,适用于AI健身、云上赛事、AI体测等多场景,适合新开发和存量改造项目。
|
1月前
|
小程序 前端开发 JavaScript
在线课堂+工具组件小程序uniapp移动端源码
在线课堂+工具组件小程序uniapp移动端源码
37 0
在线课堂+工具组件小程序uniapp移动端源码
|
1月前
|
小程序 数据挖掘 UED
开发1个上门家政小程序APP系统,都有哪些功能?
在快节奏的现代生活中,家政服务已成为许多家庭的必需品。针对传统家政服务存在的问题,如服务质量不稳定、价格不透明等,我们历时两年开发了一套全新的上门家政系统。该系统通过完善信用体系、提供奖励机制、优化复购体验、多渠道推广和多样化盈利模式,解决了私单、复购、推广和盈利四大痛点,全面提升了服务质量和用户体验,旨在成为家政行业的领导者。
|
2月前
|
移动开发 小程序 数据可视化
基于npm CLI脚手架的uniapp项目创建、运行与打包全攻略(微信小程序、H5、APP全覆盖)
基于npm CLI脚手架的uniapp项目创建、运行与打包全攻略(微信小程序、H5、APP全覆盖)
365 3
|
2月前
|
存储 自然语言处理 小程序
微信小程序多语言切换神器:简繁体切换功能完全指南
随着全球化的发展,支持多种语言的应用程序愈发重要。本文介绍了如何在微信小程序中实现简体与繁体字体之间的切换功能,以满足不同地区用户的需求。通过创建utils文件夹并编写相应的转换函数,开发者可以方便地实现语言切换,从而提升用户体验。文章中还附带了示例代码和效果图,帮助读者更好地理解和应用这一功能。
112 0
微信小程序多语言切换神器:简繁体切换功能完全指南
|
2月前
|
小程序 API
微信小程序更新提醒uniapp
在小程序开发中,版本更新至关重要。本方案利用 `uni-app` 的 `uni.getUpdateManager()` API 在启动时检测版本更新,提示用户并提供立即更新选项,自动下载更新内容,并在更新完成后重启小程序以应用新版本。适用于微信小程序,确保用户始终使用最新版本。以下是实现步骤: ### 实现步骤 1. **创建更新方法**:在 `App.vue` 中创建 `updateApp` 方法用于检查小程序是否有新版本。 2. **测试**:添加编译模式并选择成功状态进行模拟测试。
60 0
微信小程序更新提醒uniapp

热门文章

最新文章