基於uni-app開發微信小程序__手牽手帶你開發【懂你找圖】項目


前戲

某一天的夜里,敲完了代碼之后便直接倒在床上睡着了,醒來時只記得夢里的一句話:“想要成為高手,就必須要大量實踐,大量做項目,必須要把自己不會的東西全部吃透,不要得過且過。”,猛然想起是一位大神前輩對我說的,工作之后每天加班,回家之后就不想學習了,總想着一把錘子搞定所有釘子,這樣是不行的,於是我就下了幾百G的項目實戰視頻,有Vue、React、Node.js、Angular、Flutter、各個框架源碼分析。。。我計划今年把它們全部干完,每做一個項目我都會寫一篇博客來記錄開發過程和收獲,我想着通過大量的項目練習來讓自己變成熟練工種,然后再去看源碼就會比較輕松;接下來就讓我牽着同學們的小手帶你們開發一款基於uni-app的微信小程序項目,項目名字叫【懂你找圖】。

 

項目介紹

做這個項目之前,同學們最好寫過2-3個移動端的頁面,有一定的JS基礎,比如map,forEach函數的使用,Promise的使用,掌握Vue的基本語法,基本的生命周期,什么是Watch?怎么使用一個Component?子傳父 / 父傳子的實現方式。

沒有基礎的同學也不要擔心,可以跟着把項目寫完,然后把不理解的地方單獨抽出來,逐個學習,然后再把項目獨立做一遍就完事了。

這個是項目做完之后的效果:

 

這個項目我會帶領同學們寫完首頁的模塊,其他模塊由於API接口還沒有寫好,暫時不做,等以后寫好了,我會馬上更新,接下來我們就進入正片環節。

 

1.項目准備

1.1開發方式

uni-app為我們提供2種開發方式:

1.使用DCloud公司提供HBuilderX工具來快速開發;

2.使用腳手架來快速開發(我們這次項目使用此方式);

 

1.2腳手架搭建項目

1.全局安裝,如果你以前安裝過就不需要重復安裝了。

npm install -g @vue/cli

2.創建項目。

vue create -p dcloudio/uni-preset-vue dnpicture

3.啟動項目(微信小程序)。

npm run dev:mp-weixin

4.在微信小程序開發者工具導入項目。

 注意導入項目的路徑。

 

 

 

1.3搭建過程中可能遇到的問題

容易出現 vue 和 vue-template-complier版本不一致的問題。

 

 根據提示重新安裝對應的vue版本即可 npm install vue@2.6.10,然后再重新運行項目 npm run dev:mp-weixin。

 

1.4安裝sass依賴

npm install sass-loader node-sass

 

2.項目搭建

2.1新增tabbar頁面

頁面名稱 路徑
首頁 home/index.vue
橫屏 horizontal/index.vue
精美視頻 video/index.vue
搜索 search/index.vue
我的 mine/index.vue

 

 

新建完頁面之后,我們再去pages.json文件里面添加頁面路徑和tabbar對應的圖片和樣式。

{
    "pages": [{
            "path": "pages/home/index",
            "style": {
                "navigationBarTitleText": "首頁"
            }
        },
        {
            "path": "pages/horizontal/index",
            "style": {
                "navigationBarTitleText": "橫屏"
            }
        },
        {
            "path": "pages/video/index",
            "style": {
                "navigationBarTitleText": "精美視頻"
            }
        },
        {
            "path": "pages/search/index",
            "style": {
                "navigationBarTitleText": "搜索"
            }
        },
        {
            "path": "pages/mine/index",
            "style": {
                "navigationBarTitleText": "我的"
            }
        }
    ],
    "globalStyle": {
        "navigationBarTextStyle": "black",
        "navigationBarTitleText": "uni-app",
        "navigationBarBackgroundColor": "#F8F8F8",
        "backgroundColor": "#F8F8F8"
    },
    "tabBar": {
        "color": "#8a8a8a",
        "selectedColor": "#d4237a",
        "backgroundColor": "#fff",
        "position": "bottom",
        "borderStyle": "black",
        "list": [{
                "pagePath": "pages/home/index",
                "text": "首頁",
                "iconPath": "./static/icon/_home.png",
                "selectedIconPath": "./static/icon/home.png"
            },
            {
                "pagePath": "pages/horizontal/index",
                "text": "橫屏",
                "iconPath": "./static/icon/_img.png",
                "selectedIconPath": "./static/icon/img.png"
            },
            {
                "pagePath": "pages/video/index",
                "text": "精美視頻",
                "iconPath": "./static/icon/_videocamera.png",
                "selectedIconPath": "./static/icon/videocamera.png"
            },
            {
                "pagePath": "pages/search/index",
                "text": "搜索",
                "iconPath": "./static/icon/_search.png",
                "selectedIconPath": "./static/icon/search.png"
            },
            {
                "pagePath": "pages/mine/index",
                "text": "我的",
                "iconPath": "./static/icon/_my.png",
                "selectedIconPath": "./static/icon/my.png"
            }
        ]
    }

}
pages.json

接下來我們需要在App.vue中全局引入字體圖標文件。

<script>
    export default {
        onLaunch: function() {
        },
        onShow: function() {
        },
        onHide: function() {
        }
    }
</script>

<style>
    @import "./styles/iconfont.wxss";
    @import "./styles/base.wxss";
</style>
App.vue

引入成功之后,就可以看到如下效果啦。

 

 

 注意:要記得把icon和styles文件夾放到項目中去哦。

styles文件加放到和App.vue同層級目錄下,icon文件夾放入static文件夾里面。

 

2.2 uni-ui介紹

文檔: https://uniapp.dcloud.io/component/README?id=uniui 

uni-ui是DCloud提供的一個跨端ui庫,它是基於vue組件的、flex布局的、無dom的跨全端ui框架。

 uni-ui不包括基礎組件,它是基礎組件的補充:

 數字角標、日歷、卡片、折疊面板、倒計時、抽屜、懸浮按鈕、收藏按鈕、底部購物導航、宮格、圖標、索引列表、列表、加載更多、自定義導航欄、通告欄、數字輸入框、分頁器、彈出層、評分、搜索欄、分段器、步驟條、滑動操作、輪播圖指示點、標簽。

 

3.首頁模塊開發准備

3.1 功能分析

1.修改導航欄外觀

2.使用分段器組件搭建子頁面

3.封裝自己的異步請求

 

3.2 搭建子頁面

  • 首頁模塊分為4個部分,分別是 推薦、分類、最新、專輯
  • 新建自定義組件來代替上述的4個頁面
    • home-recommend
    • home-category
    • home-new
    • home-album

 3.2.1 分段器介紹

 分段器是指uni-ui中的一個組件,其實就是俗稱的標簽頁,tab欄(https://ext.dcloud.net.cn/plugin?id=54)

3.2.2 分段器使用

<template>
  <view>
    <view>
      <uni-segmented-control
        :current="current"
        :values="items.map(v=>v.title)"
        @clickItem="onClickItem"
        style-type="text"
        active-color="#d21974"
      ></uni-segmented-control>
      <view class="content">
        <view v-if="current === 0">
        </view>
        <view v-if="current === 1">
        </view>
        <view v-if="current === 2">
        </view>
        <view v-if="current === 3">
        </view>
      </view>
    </view>
  </view>
</template>

<script>
import { uniSegmentedControl } from "@dcloudio/uni-ui";
export default {
  components: {
    uniSegmentedControl
  },
  data() {
    return {
      items: [
        { title: "推薦" },
        { title: "分類" },
        { title: "最新" },
        { title: "專輯" }
      ],
      current: 0
    };
  },
  methods: {
    onClickItem(e) {
      if (this.current !== e.currentIndex) {
        this.current = e.currentIndex;
      }
    }
  }
};
</script>

<style lang="scss">

</style>
分段器的使用

 

3.3 封裝自己的異步請求

為什么要封裝?

  1. 原生的請求不支持promise;
  2. uni-api的請求不能夠方便的添加請求中效果;
  3. uni-api的請求返回值是個數組,不方便取值;

封裝的思路

  1. 基於原生promise來封裝;
  2. 掛載到Vue的原型上;
  3. 通過this.request的方式來使用;
/* 
  基於原生promise封裝request
  發請求之前顯示'加載中...'
  請求完成之后隱藏'加載中...'
*/

export default (params) => {
  uni.showLoading({
    title: '加載中'
  });
  return new Promise((resolve, reject)=>{
    wx.request({
      ...params,
      success(res) {
        resolve(res.data);
      },
      fail(err) {
        reject(err);
      },
      complete(){
        uni.hideLoading();
      }
    })
  })
}
request

在main.js里面將request函數掛載到Vue的原型上

 

 

4.首頁-推薦模塊開發

4.1 功能介紹

  1. 數據動態渲染;
  2. moment.js的使用;
  3. 基於scroll-view的分頁加載;

4.2 實現過程

首頁推薦這個頁面非常簡單,沒有任何技術含量。。。

首先我們把靜態頁面寫出來,然后發送請求獲取數據然后使用v-for指令循環渲染數據,渲染圖片的時候注意接口有沒有帶rule這個屬性,如果有需要把thumb屬性和rule進行拼接,這里約定好<Height>的值為300,然后注意一下圖像要使用widthFix還是aspectFill,這些都是非常基礎的知識,大家可以自行到微信小程序的官方開發文檔里面找到,如果你不懂,還不願意自己去找資料學習,那我也沒辦法啦。

日期部分使用的是moment.js庫,下面是他的文檔地址:

http://momentjs.cn/docs/#/displaying/

 

接下來說一說分頁,無非就是把最頂級的view 標簽改成scroll-view標簽,加上一個scroll-y屬性,再加上一個觸底事件@scrolltolower=“handleScrollToLower”即可,這些東西uni-app官網都有,由於這個項目是使用uni-app來開發,所以很多API和組件都需要在uni-app文檔和微信小程序的文檔穿插查找,搞技術嘛,就是要往上面砸時間,耐心點就完事了。

分頁部分的邏輯其實很簡單:

頁面觸底之后,發送請求獲取數據,skip的值等於自身加上limit的值,然后limit的值加上30條,需要注意的是在觸底之后發送請求之前要判斷是否還有新的limit數據,可以在data里面設置一個狀態,比如hasLimit:true,然后在請求函數里面判斷一下是否還有新數據返回,如果沒有的話就將hasLimit的值改為false並且提示用戶。

<template>
  <scroll-view
    scroll-y
    @scrolltolower="handleScrollToLower"
    class="container"
    v-if="recommentList.length!==0"
  >
    <!-- 推薦部分圖片 start -->
    <view class="recomment-wrap">
      <view class="item" v-for="item in recommentList" :key="item.id">
        <image :src="item.thumb" mode="widthFix" />
      </view>
    </view>
    <!-- 推薦部分圖片 end -->

    <!-- 月份圖片部分 start -->
    <view class="month-wrap">
      <view class="month-title">
        <view class="month-info">
          <view class="month-time">
            <text class="day">{{monthList.day}} /</text>
            <text class="month">{{monthList.month}} 月</text>
          </view>
          <view class="month-text">{{monthList.title}}</view>
        </view>
        <view class="month-more">更多></view>
      </view>
      <view class="month-content">
        <view class="item" v-for="item in monthList.items" :key="item.id">
          <image :src="item.thumb + item.rule.replace('$<Height>',300)" mode="aspectFill" />
        </view>
      </view>
    </view>
    <!-- 月份圖片部分 end -->

    <!-- 熱門部分 start -->
    <view class="hot-wrap">
      <view class="hot-title">
        <text class="title-text">熱門</text>
      </view>
      <view class="hot-content">
        <view class="hot-item" v-for="item in hotList" :key="item.id">
          <image :src="item.thumb" mode="widthFix" />
        </view>
      </view>
    </view>
    <!-- 熱門部分 end -->
  </scroll-view>
</template>

<script>
export default {
  data() {
    return {
      params: {
        limit: 30,
        order: "hot",
        skip: 0
      },
      recommentList: [],
      monthList: [],
      hotList: [],
      hasLimit: true
    };
  },
  mounted() {
    this.getList();
  },
  methods: {
    getList() {
      this.request({
        url: "http://157.122.54.189:9088/image/v3/homepage/vertical",
        data: this.params
      }).then(data => {
        if (this.monthList.length === 0) {
          this.recommentList = data.res.homepage[1].items;
          this.monthList = data.res.homepage[2];
          this.monthList.day = this.moment(this.monthList.stime).format("DD");
          this.monthList.month = this.moment(this.monthList.stime).format("MM");
        }
        if (data.res.vertical.length === 0) {
          this.hasLimit = false;
          uni.showToast({
            title: "沒有更多數據啦",
            icon: "none"
          });
          return;
        }
        this.hotList = [...this.hotList, ...data.res.vertical];
        this.params.skip += this.params.limit;
        this.params.limit += 30;
      });
    },
    handleScrollToLower() {
      if (this.hasLimit) {
        this.getList();
      } else {
        uni.showToast({
          title: "沒有更多數據啦",
          icon: "none"
        });
      }
    }
  }
};
</script>

<style lang="scss">
.container {
  height: calc(100vh - 35px);
}
/* 推薦圖片部分*/
.recomment-wrap {
  display: flex;
  flex-wrap: wrap;
  > .item {
    width: 50%;
    image {
      border: 3rpx solid #fff;
    }
  }
}

/* 月份圖片部分 */
.month-wrap {
  .month-title {
    display: flex;
    justify-content: space-between;
    padding: 20rpx 20rpx;
    .month-info {
      display: flex;
      font-weight: bold;
      .month-time {
        color: $color;
        .day {
          font-size: 32rpx;
        }

        .month {
          font-size: 26rpx;
        }
      }

      .month-text {
        margin-left: 20rpx;
        color: #666;
        font-size: 32rpx;
      }
    }

    .month-more {
      font-size: 28rpx;
      color: $color;
    }
  }

  .month-content {
    display: flex;
    flex-wrap: wrap;
    .item {
      width: 33.33%;
      image {
        border: 5rpx solid #fff;
      }
    }
  }
}

/* 熱門部分 */
.hot-wrap {
  .hot-title {
    padding: 20rpx;
    text.title-text {
      padding-left: 14rpx;
      color: $color;
      border-left: 10rpx solid $color;
      font-size: 28rpx;
      font-weight: bold;
    }
  }

  .hot-content {
    display: flex;
    flex-wrap: wrap;
    .hot-item {
      width: 33.33%;
      image {
        border: 5rpx solid #fff;
      }
    }
  }
}
</style>
首頁推薦組件代碼

后續還要加上跳轉功能,到時候會將跳轉抽離成一個公共組件,到時在下文補充。

 

5.首頁-專輯模塊開發

 5.1 功能介紹

  1. swiper輪播圖部分
  2. 專輯列表部分

5.2 實現過程

輪播圖的部分直接使用微信小程序官方提供的swiper組件,注意swiper組件默認寬度100%,高度是150px,而且swiper必須和swiper-item配對出現,否則會出問題,下面是小程序基礎教程和官方文檔:

基礎教程:https://www.cnblogs.com/replaceroot/p/11262929.html

官方文檔:https://developers.weixin.qq.com/miniprogram/dev/component/swiper.html

搞定了輪播之后就很容易了,寫一下靜態頁面,發下請求然后渲染數據,注意對分頁數據的判斷就行啦,對你們來說絕對是小菜一碟,代碼如下:

<template>
  <scroll-view scroll-y="true" @scrolltolower="handleScrollToLower" class="album-wrap">
    <!-- 輪播圖部分 start -->
    <swiper class="swiper" indicator-dots="true" autoplay="true" interval="3000" circular="true">
      <swiper-item v-for="item in banner" :key="item.id">
        <image :src="item.thumb" mode="widthFix" />
      </swiper-item>
    </swiper>
    <!-- 輪播圖部分 end -->

    <!-- 專輯列表部分 start -->
    <view class="album-list">
      <navigator
        :url="`/pages/album/index?id=${item.id}`"
        class="album-item"
        v-for="item in album"
        :key="item.id"
      >
        <view class="album-image">
          <image :src="item.cover" mode="aspectFill" />
        </view>
        <view class="album-info">
          <view class="alubm-name ellipsis">{{item.name}}</view>
          <view class="alubm-desc ellipsis">{{item.desc}}</view>
          <view class="attention">
            <view class="attention-btn">+關注</view>
          </view>
        </view>
      </navigator>
    </view>
    <!-- 專輯列表部分 end -->

  </scroll-view>
</template>

<script>
export default {
  data() {
    return {
      banner: [],
      album: [],
      params: {
        limit: 30,
        skip: 0,
        order: "new"
      },
      hasLimit: true
    };
  },
  methods: {
    getList() {
      this.request({
        url: "http://157.122.54.189:9088/image/v1/wallpaper/album",
        data: this.params
      }).then(data => {
        if (this.album.length === 0) {
          this.banner = data.res.banner;
        }
        if (data.res.album.length === 0) {
          this.hasLimit = false;
          uni.showToast({
            title: "沒有更多的數據啦!",
            icon: "none"
          });
          return;
        }
        this.album = [...this.album, ...data.res.album];
        this.params.skip += this.params.limit;
        this.params.limit += 30;
      });
    },
    handleScrollToLower() {
      if (this.hasLimit) {
        this.getList();
      } else {
        uni.showToast({
          title: "沒有更多數據啦!",
          icon: "none"
        });
      }
    }
  },
  mounted() {
    this.getList();
  }
};
</script>

<style lang="scss">
/* 公共樣式 */
.ellipsis {
  text-overflow: ellipsis;
  overflow: hidden;
  white-space: nowrap;
}

/* 專輯輪播圖部分 */
.album-wrap {
  height: calc(100vh - 35px);
  .swiper {
    height: 320rpx;
    image {
    }
  }
}

/* 專輯列表部分 */
.album-list {
  padding: 10rpx;
  .album-item {
    display: flex;
    padding: 10rpx;
    border-bottom: 3rpx solid #d5d5d5;
    .album-image {
      flex: 1;
      image {
        width: 200rpx;
        height: 200rpx;
      }
    }

    .album-info {
      flex: 3;
      margin-left: 40rpx;
      overflow: hidden;
      .alubm-name {
        color: #000;
      }

      .alubm-desc {
        color: #666;
      }

      .attention {
        display: flex;
        justify-content: flex-end;
        margin-top: 10rpx;
        .attention-btn {
          padding: 0 5rpx;
          border: 3rpx solid $color;
          color: $color;
        }
      }
    }
  }
}
</style>
home-album

 

6.專輯詳情模塊開發

6.1 功能分析

  1. 頭部背景圖部分
  2. 專輯詳情列表圖片部分

6.2 實現過程

實現的過程也非常簡單,首先放一張image圖片當作背景圖片,圖片里面的文字都知道怎么做吧,直接用定位就完事了。

下面也是一樣套路,先寫靜態頁面,然后發請求,注意下圖片的寬高,和mode模式就行了,具體的代碼如下:

<template>
  <view class="album-detail-wrap">
    <!-- 專輯詳情背景部分 start -->
    <view class="album-background">
      <image :src="album.cover" mode="widthFix" />
      <view class="album-info">
        <view class="album-name">{{album.name}}</view>
        <view class="attention">
          <view class="attention-btn">關注專輯</view>
        </view>
      </view>
    </view>
    <!-- 專輯詳情背景部分 end -->

    <!-- 列表部分 start -->
    <view class="album-list">
      <view class="album-title">
        <view class="author">
          <image :src="album.user.avatar" mode="aspectFill" />
          <text class="author-name">{{album.user.name}}</text>
        </view>
        <text class="album-desc">{{album.desc}}</text>
      </view>
      <view class="album-content">
        <view class="alubm-item" v-for="item in wallpaper" :key="item.id">
          <image :src="item.thumb + item.rule.replace('$<Height>',300)" mode="aspectFill" />
        </view>
      </view>
    </view>
    <!-- 列表部分 end -->
  </view>
</template>

<script>
export default {
  data() {
    return {
      params: {
        limit: 30,
        skip: 0,
        order: "new",
        first: "1"
      },
      id: "",
      album: [],
      wallpaper: [],
      hasLimit: true
    };
  },
  methods: {
    getList() {
      if(this.album.length==0) {
        this.params.first = '1';
      }
      this.request({
        url: `http://157.122.54.189:9088/image/v1/wallpaper/album/${this.id}/wallpaper`,
        data: this.params
      }).then(data => {
        if(this.album.length === 0) {
          this.album = data.res.album;
        }
        if(data.res.wallpaper.length===0) {
          this.hasLimit = false;
          uni.showToast({
            title: '沒有數據啦',
            icon: 'none'
          });
          return;
        }
        this.params.first = 0;
        this.wallpaper = [...this.wallpaper,...data.res.wallpaper];
        this.params.skip += this.params.limit;
        this.params.limit += 30;
      });
    }
  },
  onReachBottom() {
    if(this.hasLimit) {
      this.getList();
    }else {
      uni.showToast({
        title: '沒有數據啦!',
        icon: 'none'
      });
    }
  },
  onLoad(options) {
    this.id = options.id;
    this.getList();
  }
};
</script>

<style lang="scss">
.album-detail-wrap {
  /* 專輯詳情背景圖部分 */
  .album-background {
    position: relative;
    image {
    }

    .album-info {
      position: absolute;
      bottom: 5%;
      display: flex;
      align-items: center;
      justify-content: space-between;
      width: 100%;
      padding: 0 20rpx;
      .album-name {
        color: #fff;
        font-size: 32rpx;
      }

      .attention {
        display: flex;
        justify-content: center;
        align-items: center;
        .attention-btn {
          padding: 10rpx 15rpx;
          background-color: $color;
          color: #fff;
          border-radius: 10rpx;
          font-size: 26rpx;
        }
      }
    }
  }

  /* 專輯詳情列表部分 */
  .album-list {
    .album-title {
      padding: 10rpx;
      .author {
        display: flex;
        image {
          width: 60rpx;
          height: 60rpx;
        }

        text.author-name {
          line-height: 60rpx;
          margin-left: 5rpx;
          color: #000;
        }
      }

      text.album-desc {
        color: #666;
        font-size: 26rpx;
      }
    }

    .album-content {
      display: flex;
      flex-wrap: wrap;
      .alubm-item {
        width: 33.33%;
        image {
          height: 200rpx;
          border: 3rpx solid #fff;
        }
      }
    }
  }
}
</style>
album/index.vue

最后有個小坑需要注意下,小程序里面的view標簽不支持文本中的換行符,如果某些特殊場景中后台返回的文本里面包含換行符就直接使用text標簽就完事了。

 

7.圖片詳情模塊開發

 7.1 功能分析

  1. 封裝超鏈接組件
  2. 發送請求獲取數據
  3. 使用moment.js處理特殊時間格式
  4. 封裝手勢滑動組件
  5. 調用API下載圖片

7.2 實現過程

在components組件文件夾下面新建一個goDetail.vue的自定義組件

<template>
  <view @click="handleClick">
    <slot></slot>
  </view>
</template>

<script>
export default {
  props: {
    list: Array,
    index: Number
  },
  methods: {
    handleClick() {
      /* 
        1 將數據緩存下來 使用getApp()全局緩存方式
        2 實現點擊跳轉頁面
      */
     getApp().globalData.imgList = this.list;
     getApp().globalData.imgIndex = this.index;
     uni.navigateTo({
        url: "/pages/imgDetail/index"
     });
    }
  }
};
</script>

<style>
</style>
goDetail.vue

這個地方用到了微信小程序的全局緩存數據的方法,我們把數據緩存在App.vue文件中,使用的時候直接通過getApp().globalData.屬性的方法獲取數據即可。

 

 具體發請求獲取數據渲染頁面的部分自行看代碼學習吧。

項目github地址:https://github.com/C4az6/dnpicture.git

項目API文檔:https://www.showdoc.cc/414855720281749?page_id=3678621017219602

這個項目教程就此結束。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM