微信小程序实战,用vue3实现每日浪漫情话推荐~

简介: 微信小程序实战,用vue3实现每日浪漫情话推荐~

1、前言



之前做了个恋爱话术微信小程序,满足了日常聊天的需要,实现高情商的恋爱聊天。最近也完成了话术库的优化更新,新增30万条话术数据,同时使用了分词技术,匹配更完善。


但最近突然发现,每天早上给女朋友发一段优美情话可以让她开心一整天,但无奈自己的语言水平确实有限,不能随手拈来,着实让人有点不爽。


不过办法总比困难多,作为高情商的程序猿,来源于日常生活的需求往往是咱们最大的动力,必须盘他,所以想着在之前的恋爱话术小程序上在加一个每日情话推荐的功能。

希望这个程序对其他单身或者恋爱中的兄弟们有所帮助,也希望兄弟们关注加三连支持一波哈~~~


2、推荐接口简介



辣鸡服务器,兄弟们轻点调哈


浪漫推荐情话开放接口


接口地址:https://yin-feng.top/open/getRecommendLove


请求方式:POST


请求数据类型:application/json


响应数据类型:*/*


请求参数:


{}


响应参数:


image.png


响应示例:

[
  {
    "content": "",
    "id": 0,
    "score": 0,
    "title": ""
  }
]


3、前端页面开发



老样子,咱们还是使用uniapp框架来开发,uniapp的介绍就不多说了哈。


3.1 配置pages.json文件


我们这次打算配两个菜单,包括浪漫情话推荐和话术搜索。主要在pages.json里面就行配置。


pages.json 文件用来对 uni-app 进行全局配置,决定页面文件的路径、窗口样式、原生的导航栏、底部的原生tabbar 等。它类似微信小程序中app.json的页面管理部分。


在 pages.json 中提供 tabBar 配置,不仅仅是为了方便快速开发导航,更重要的是在App和小程序端提升性能。在这两个平台,底层原生引擎在启动时无需等待js引擎初始化,即可直接读取 pages.json 中配置的 tabBar 信息,渲染原生tab。


官方文档参考tabBar


特别注意


1.当设置 position 为 top 时,将不会显示 icon

2.tabBar 中的 list 是一个数组,只能配置最少2个、最多5个 tab,tab 按数组的顺序排序。

3.tabbar 切换第一次加载时可能渲染不及时,可以在每个tabbar页面的onLoad生命周期里先弹出一个等待雪花(hello uni-app使用了此方式)

4.tabbar 的页面展现过一次后就保留在内存中,再次切换 tabbar 页面,只会触发每个页面的onShow,不会再触发onLoad。

5.顶部的 tabbar 目前仅微信小程序上支持。需要用到顶部选项卡的话,建议不使用 tabbar 的顶部设置,而是自己做顶部选项卡。

新增浪漫情话页面配置


新增浪漫情话页面配置


{
      "path": "pages/recommend/recommend",
      "style": {
        "navigationBarTitleText": "浪漫情话",
        "backgroundColor": "#eeeeee",
        "enablePullDownRefresh": false
      }
    }


添加两个tab标签


"tabBar": {
    "color": "#7A7E83",
    "selectedColor": "#0055ff",
    "borderStyle": "black",
    "backgroundColor": "#ffffe1",
    "height":"60px",
    "fontSize":"18px",
    "list": [
      {
        "pagePath": "pages/recommend/recommend",
        "iconPath": "static/imgs/love1.png",
        "selectedIconPath": "static/imgs/love2.png",
        "text": "浪漫情话"
      },
      {
        "pagePath": "pages/index/index",
        "iconPath": "static/imgs/爱心1.png",
        "selectedIconPath": "static/imgs/爱心2.png",
        "text": "话术搜索"
      }
    ]
  }


完整的page.json文件


{
  "pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
    {
      "path": "pages/recommend/recommend",
      "style": {
        "navigationBarTitleText": "浪漫情话",
        "backgroundColor": "#eeeeee",
        "enablePullDownRefresh": false
      }
    },
    {
      "path": "pages/index/index",
      "style": {
        "navigationBarTitleText": "恋爱话术",
        "backgroundColor": "#eeeeee",
        "enablePullDownRefresh": false
      }
    }, {
      "path": "component/WebView/WebView",
      "style": {
        "navigationBarTitleText": "",
        "enablePullDownRefresh": false
      }
    }
  ],
  "tabBar": {
    "color": "#7A7E83",
    "selectedColor": "#0055ff",
    "borderStyle": "black",
    "backgroundColor": "#ffffe1",
    "height":"60px",
    "fontSize":"18px",
    "list": [
      {
        "pagePath": "pages/recommend/recommend",
        "iconPath": "static/imgs/love1.png",
        "selectedIconPath": "static/imgs/love2.png",
        "text": "浪漫情话"
      },
      {
        "pagePath": "pages/index/index",
        "iconPath": "static/imgs/爱心1.png",
        "selectedIconPath": "static/imgs/爱心2.png",
        "text": "话术搜索"
      }
    ]
  },
  "globalStyle": {
    "navigationBarTextStyle": "black",
    "navigationBarTitleText": "恋爱话术",
    "navigationBarBackgroundColor": "#ffffe1",
    "backgroundColor": "#f5ffff"
  }
}


image.png


3.2 封装api接口

主要在http.api.js里面配置,这个文件之前也包含了咱们的话术搜索接口


import service from './http.interceptor.js'
const api = {
  // 话术搜索
  getLoveChat: params => service.post('/open/getLoveChat', params),
  getBanner: () => service.post('/open/getBanner'),
  // 浪漫情话推荐
  getRecommendLove: () => service.post('/open/getRecommendLove'),
  loveScore: params => service.post('/open/loveScore', params),
}
export default api


3.3 编写浪漫情话页面


咱们还是使用vue3加uniapp的语法进行页面开发,同时为了使页面更优美,封装一个瀑布流组件进行渲染数据。下图就是咱们要实现的大致效果


image.png


3.3.1 封装瀑布流组件


<template>
  <view class="u-waterfall">
    <view id="u-left-column" class="u-column">
      <slot name="left" :leftList="leftList"></slot>
    </view>
    <view id="u-right-column" class="u-column">
      <slot name="right" :rightList="rightList"></slot>
    </view>
  </view>
</template>
<script>
  export default {
    name: "waterfall",
    props: {
      value: {
        // 瀑布流数据
        type: Array,
        required: true,
        default: function() {
          return [];
        }
      }
    },
    data() {
      return {
        leftList: [],
        rightList: [],
        tempList: []
      }
    },
    watch: {
      copyFlowList(nVal, oVal) {
        this.tempList = this.cloneData(this.copyFlowList);
        this.leftList = []
        this.rightList = []
        this.splitData();
      }
    },
    mounted() {
      this.tempList = this.cloneData(this.copyFlowList);
      this.splitData();
    },
    computed: {
      // 破坏flowList变量的引用,否则watch的结果新旧值是一样的
      copyFlowList() {
        return this.cloneData(this.value);
      }
    },
    methods: {
      async splitData() {
        if (!this.tempList.length) return;
        let leftRect = await this.$uGetRect('#u-left-column');
        let rightRect = await this.$uGetRect('#u-right-column');
        // 如果左边小于或等于右边,就添加到左边,否则添加到右边
        let item = this.tempList[0];
        // 解决多次快速上拉后,可能数据会乱的问题,因为经过上面的两个await节点查询阻塞一定时间,加上后面的定时器干扰
        // 数组可能变成[],导致此item值可能为undefined
        if (!item) return;
        if (leftRect.height < rightRect.height) {
          this.leftList.push(item);
        } else if (leftRect.height > rightRect.height) {
          this.rightList.push(item);
        } else {
          // 这里是为了保证第一和第二张添加时,左右都能有内容
          // 因为添加第一张,实际队列的高度可能还是0,这时需要根据队列元素长度判断下一个该放哪边
          if (this.leftList.length <= this.rightList.length) {
            this.leftList.push(item);
          } else {
            this.rightList.push(item);
          }
        }
        // 移除临时列表的第一项
        this.tempList.splice(0, 1);
        // 如果临时数组还有数据,继续循环
        if (this.tempList.length) {
          this.splitData();
        } else {
          // 在这里模拟触发 我们定义的全局事件 实现数据通信的目的
          let height = (leftRect.height > rightRect.height ? leftRect.height : rightRect.height) + 120
          uni.$emit('swiperHeightChange', height + 'px')
        }
      },
      // 复制而不是引用对象和数组
      cloneData(data) {
        return JSON.parse(JSON.stringify(data));
      },
    }
  }
</script>
<style lang="scss" scoped>
  @mixin vue-flex($direction: row) {
    /* #ifndef APP-NVUE */
    display: flex;
    flex-direction: $direction;
    /* #endif */
  }
  .u-waterfall {
    @include vue-flex;
    flex-direction: row;
    align-items: flex-start;
  }
  .u-column {
    @include vue-flex;
    flex: 1;
    flex-direction: column;
    height: auto;
  }
  .u-image {
    width: 100%;
  }
</style>


3.3.2 template代码编写


这里在引入瀑布流组件之后可直接在html里面使用


<template>
  <view>
    <view class="change" @click="changeContent">
      没找到想要的?赶紧点击这里
      <text class="change-btn">换一批</text>
      <view class="card">
        点击下方卡片可直接复制
      </view>
    </view>
    <waterfall :value="dataList">
      <template v-slot:left="left">
        <view v-for="item in left.leftList" :key="item.id" class="left-content" @click="copy(item)">
          <view class="item">
            {{item.content}}
          </view>
        </view>
      </template>
      <template v-slot:right="right">
        <view v-for="item in right.rightList" :key="item.id" class="right-content" @click="copy(item)">
          <view class="item">
            {{item.content}}
          </view>
        </view>
      </template>
    </waterfall>
  </view>
</template>


3.3.3 js核心方法编写


关键代码也不多,主要是一些请求数据的方法和响应式变量的定义。


<script>
  import {
    toRefs,
    reactive,
    onMounted
  } from 'vue'
  import waterfall from '../../component/waterfall/index.vue'
  import api from '../../axios/http.api.js'
  export default {
    name: 'content',
    components: {
      waterfall
    },
    setup() {
      const state = reactive({
        dataList: [],
        loading: false,
      })
      const methods = reactive({
        getRecommendLove: async () => {
          state.loading = true
          let res = await api.getRecommendLove()
          state.loading = false
          if (res.code) {
            uni.showToast({
              title: res.msg,
              icon: 'none',
              position: 'top'
            })
          }
          state.dataList = res.data
        },
        copy(item) {
          // 复制话术到剪切板
          uni.setClipboardData({
            data: item.content
          })
          methods.score(item.id)
        },
        // 换一批
        changeContent() {
          methods.getRecommendLove()
        },
        // 打分
        async score(id) {
          let res = await api.loveScore({
            id
          })
          if (res.code) {
            uni.showToast({
              title: res.msg,
              icon: 'none',
              position: 'top'
            })
          }
        }
      })
      onMounted(() => {
        methods.getRecommendLove()
        // 分享菜单
        wx.showShareMenu({
          withShareTicket: true,
          menus: ['shareAppMessage', 'shareTimeline']
        })
      })
      return {
        ...toRefs(state),
        ...toRefs(methods)
      }
    }
  }
</script>


3.3.4 css样式代码编写


咱们毕竟不是专业的前端,所以样式就随便应付一下就行,兄弟们不要介意哈

<style lang="less" scoped>
  .change {
    width: 100%;
    height: 120px;
    border-bottom: 2px #aaaaff solid;
    border-radius: 0 0 50px 50px;
    background-size: 100% 100%;
    opacity: 0.8;
    padding-top: 45px;
    text-align: center;
    font-size: 20px;
    color: #000000;
    background-image: url(../../static/imgs/img4.png);
    .change-btn {
      display: inline-block;
      padding: 2px;
      color: red;
    }
    .card {
      padding-top: 12px;
      font-size: 13px;
    }
  }
  .left-content {
    margin: 15px 5px 15px 10px;
    padding: 8px;
    border-radius: 6px;
    background: #aaffff linear-gradient(44deg, #aaffff 0%, #F4F8FF 100%);
    font-size: 16px;
    letter-spacing: 5px;
    .item {
      margin-bottom: 10px;
    }
  }
  .right-content {
    margin: 15px 10px 15px 5px;
    padding: 8px;
    border-radius: 6px;
    background: #aaffff linear-gradient(44deg, #aaffff 0%, #F4F8FF 100%);
    font-size: 16px;
    letter-spacing: 5px;
    .item {
      margin-bottom: 10px;
    }
  }
</style>


image.png


4、发布微信小程序



发布流程参考这篇博客恋爱话术微信小程序


image.png


5、总结



源码地址在这哈,会保持一直开源,希望兄弟们多多支持,下了源码的老铁麻烦点个star哈


// 下了源码的老铁麻烦点个star哈
https://gitee.com/yinfeng-code/love-chat-wx.git
目录
相关文章
|
16天前
|
人工智能 搜索推荐 安全
从零到一:微信机器人开发的实战心得
从零到一:微信机器人开发的实战心得
51 2
|
1月前
|
小程序 安全 搜索推荐
【微信小程序开发实战项目】——个人中心页面的制作
本文介绍了如何设计和实现一个网上花店的微信小程序,包括个人中心、我的订单和我的地址等功能模块。个人中心让用户能够查看订单历史、管理地址和与客服互动。代码示例展示了`own.wxml`、`own.wxss`和`own.js`文件,用于构建个人中心界面,包括用户信息、订单链接、收藏、地址、客服和版本信息。我的订单部分展示了订单详情,包括商品图片、名称、销量、价格和订单状态,用户可以查看和管理订单。我的地址功能允许用户输入和编辑收货信息,包括联系人、性别、电话、城市和详细地址。每个功能模块都附有相应的WXML和WXSS代码,以及简洁的样式设计。
71 0
【微信小程序开发实战项目】——个人中心页面的制作
|
1月前
|
小程序 开发者
uniapp实战 —— 开发微信小程序的调试技巧
uniapp实战 —— 开发微信小程序的调试技巧
70 1
|
1月前
|
前端开发 小程序
【微信小程序-原生开发】实用教程20 - 生成海报(实战范例为生成活动海报,内含生成指定页面的小程序二维码,保存图片到手机,canvas 系列教程)
【微信小程序-原生开发】实用教程20 - 生成海报(实战范例为生成活动海报,内含生成指定页面的小程序二维码,保存图片到手机,canvas 系列教程)
120 0
|
1月前
|
小程序
【微信小程序】实战案例 -- 向订阅用户发送消息(范例:报名提醒)
【微信小程序】实战案例 -- 向订阅用户发送消息(范例:报名提醒)
80 0
|
1月前
|
小程序 前端开发
【微信小程序-原生开发】TDesign 实战模板——聊天气泡
【微信小程序-原生开发】TDesign 实战模板——聊天气泡
23 0
|
1月前
|
小程序
【微信小程序-原生开发】TDesign 实战模板——带性别图标的头像
【微信小程序-原生开发】TDesign 实战模板——带性别图标的头像
35 0
|
1月前
|
小程序 定位技术
【微信小程序-原生开发】TDesign 实战模板——个人信息编辑页
【微信小程序-原生开发】TDesign 实战模板——个人信息编辑页
24 0
|
6天前
|
存储 小程序 JavaScript
|
6天前
|
小程序 前端开发 安全

热门文章

最新文章