小程序-demo:小熊の日記


ylbtech-小程序-demo:小熊の日記

1、CHANGELOG.md

# 2016-10-12

* 更新開發者工具至`v0.10.101100`
* 修改`new`頁的數據綁定方式 & 修改多行文本框輸入時的bug

# 2016-10-13

* 完善日志編輯頁

 

2、README.md

# 微信小程序之小熊の日記 #

## 關於 ##

* 我是一名后端程序員,做這個僅僅是因為覺得微信小程序好玩;
* 沒有明確的產品意圖,東抄抄西抄抄只是為了驗證和學習微信小程序;
* 大體是想做一個個人/家庭日常記錄的app;
* 持續開發中,有興趣請持續關注

## 預覽 ##

* 概覽

<p align="center">
    <img src="./files/preview.gif" alt="截頻演示" width="100%">
</p>

## 功能特點 ##

* 功能完備,實用導向
* Server端API支持
* 涵蓋眾多組件、API使用,適用於學習微信小程序
* 多行文本模擬實現
* tab切換
* 模態框
* 本地數據組織及存儲
* 圖片預覽功能

## 使用步驟

1. 克隆代碼:

```bash
$ cd path/to/your/workspace
$ git clone https://github.com/harveyqing/BearDiary.git
```

2. 打開`微信Web開放者工具`(注意:須`v0.10.101100`及以上版本)

3. 添加項目

    * AppID:選`無AppID`
    * 項目名稱:任意填寫
    * 項目目錄:path/to/your/workspace
    * 點擊 `添加項目`

## 開發計划 ##

- [ ] 開發server端API接口
- [ ] 完成日記撰寫頁
- [ ] 添加評論、喜歡、收藏功能
- [ ] 規范`coding style`

## 小程序開發相關資源 ##

### 開發者工具下載 ###

> 最新版本 0.10.101100

- [windows 64](https://servicewechat.com/wxa-dev-logic/download_redirect?type=x64&from=mpwiki&t=1476434677599)
- [windows 32](https://servicewechat.com/wxa-dev-logic/download_redirect?type=ia32&from=mpwiki&t=1476434677599)
- [mac](https://servicewechat.com/wxa-dev-logic/download_redirect?type=darwin&from=mpwiki&t=1476434677599)

### 開發者文檔 ###

- [微信官方文檔](https://mp.weixin.qq.com/debug/wxadoc/dev/)

### 最好的資源集 ###

- [justjavac/awesome-wechat-weapp](https://github.com/justjavac/awesome-wechat-weapp)

## Anyway, 歡迎PR ##

## LICENSE ##

[MIT](./LICENSE)

3、

1.返回頂部
0、
     
 
1、app.js
// app.js

const config = require('config');
const diaries = require('demo/diaries');

App({

  onLaunch: function () {
  },

  // 獲取用戶信息
  getUserInfo: function(cb) {
    var that = this;

    if (this.globalData.userInfo) {
      typeof cb == 'function' && cb(this.globalData.userInfo)
    } else {
      // 先登錄
      wx.login({
        success: function() {
          wx.getUserInfo({
            success: (res) => {
              that.globalData.userInfo = res.userInfo;
              typeof cb == 'function' && cb(that.globalData.userInfo)
            }
          })
        }
      })
    }
  },

  // 獲取本地全部日記列表
  getDiaryList(cb) {
    var that = this;

    if (this.globalData.diaryList) {
      typeof cb == 'function' && cb(this.globalData.diaryList);
    } else {
      let list = [];

      this.getLocalDiaries(storage => {
        // 本地緩存數據
        for (var k in storage) {
          list.push(storage[k]);
        }
      });

      // 本地假數據
      list.push(...diaries.diaries);
      that.globalData.diaryList = list;
      typeof cb == 'function' && cb(that.globalData.diaryList)
    }
  },

  // 獲取本地日記緩存
  getLocalDiaries(cb) {
    var that = this;

    if (this.globalData.localDiaries) {
      typeof cb == 'function' && cb(this.globalData.localDiaries);
    } else {
      wx.getStorage({
        key: config.storage.diaryListKey,
        success: (res) => {
          that.globalData.localDiaries = res.data;
          typeof cb == 'function' && cb(that.globalData.localDiaries);
        },
        fail: (error) => {
          that.globalData.localDiaries = {};
          typeof cb == 'function' && cb(that.globalData.localDiaries);
        }
      });
    }
  },

  // 獲取當前設備信息
  getDeviceInfo: function(callback) {
    var that = this;

    if (this.globalData.deviceInfo) {
      typeof callback == "function" && callback(this.globalData.deviceInfo)
    } else {
      wx.getSystemInfo({
        success: function(res) {
          that.globalData.deviceInfo = res;
          typeof callback == "function" && callback(that.globalData.deviceInfo)
        }
      })
    }
  },

  globalData: {
    // 設備信息,主要用於獲取屏幕尺寸而做適配
    deviceInfo: null,

    // 本地日記緩存列表 + 假數據
    // TODO 真實數據同步至服務端,本地只做部分緩存
    diaryList: null,

    // 本地日記緩存
    localDiaries: null,

    // 用戶信息
    userInfo: null,
  }

})
2、app.json
{
  "pages":[
    "pages/list/list",
    "pages/mine/mine",
    "pages/new/new",
    "pages/entry/entry"
  ],
  "window":{
    "backgroundTextStyle": "light",
    "navigationBarBackgroundColor": "#39b5de",
    "navigationBarTitleText": "小熊の日記",
    "navigationBarTextStyle": "white",
    "backgroundColor": "#eceff4"
  },
  "tabBar": {
    "color": "#858585",
    "selectedColor": "#39b5de",
    "backgroundColor": "#ffffff",
    "borderStyle": "black",
    "list":[
      {
        "pagePath": "pages/list/list",
        "iconPath": "images/icons/mark.png",
        "selectedIconPath": "images/icons/markHL.png",
        "text": "印記"
      },
      {
        "pagePath": "pages/mine/mine",
        "iconPath": "images/icons/mine.png",
        "selectedIconPath": "images/icons/mineHL.png",
        "text": "我的"
      }
    ]
  },
  "debug": true
}
3、app.wxss
/**
 app.wxss
 全局樣式
**/

  page {
  width: 100%;
  height: 100%;
  padding: 0;
  background-color: #eceff4;
  font-size: 30rpx;
  font-family: -apple-system-font, 'Helvetica Neue', Helvetica, 'Microsoft YaHei', sans-serif;

}
3、config.js
// 全局配置

module.exports = {
    /** 騰訊地圖 **/
    map: {
        baseUrl: 'https://apis.map.qq.com/ws',
        key: '2TCBZ-IM7K5-XHCIZ-QXLRT-CIT4J-DEFSM',
    },

    /** 輸入框控件設置 **/
    input: {
        charWidth: 14,  // 單個字符的寬度,in rpx
    },

    /** 本地存儲 **/
    // TODO 數據通過API全部存儲於服務端
    storage: {
        diaryListKey: 'bearDiaryList',
    }
};
4、project.config.json
{
    "description": "項目配置文件。",
    "packOptions": {
        "ignore": []
    },
    "setting": {
        "urlCheck": true,
        "es6": true,
        "postcss": true,
        "minified": true,
        "newFeature": true
    },
    "compileType": "miniprogram",
    "libVersion": "2.2.3",
    "appid": "wx7d22ab7088f2db6a",
    "projectname": "BearDiary",
    "isGameTourist": false,
    "condition": {
        "search": {
            "current": -1,
            "list": []
        },
        "conversation": {
            "current": -1,
            "list": []
        },
        "game": {
            "currentL": -1,
            "list": []
        },
        "miniprogram": {
            "current": -1,
            "list": []
        }
    }
}
5、images
6、
2. pages返回頂部
1、demo
-diaries.js
var diaries = [
  {
    meta: {  // 內容元數據
      cover: "http://m.chanyouji.cn/index-cover/64695-2679221.jpg?imageView2/1/w/620/h/330/format/jpg",
      avatar: "https://pic4.zhimg.com/e515adf3b_xl.jpg",
      title: "此刻靜好,願幸福長存",
      meta: "2016.10.17",
      create_time: "2016.10.18 11:57:27",
      nickName: "肥肥的小狗熊",
    },
    list: [
      {
        type: "TEXT",
        content: '9月11日,15年的911事件使這天蒙上了特殊的意義。2016年的這一天,懷着激動的心情,開啟了高原尋秘之旅,向着那聖潔之地出發。全程自駕近2000公里,雨崩徒步80公里,完成覲見之旅。',
        poi: {
          longitude: 117.2,
          latitude: 37.5,
          name: "北京市",
        },
        description: "",
        id: 1,
        commentNum: 0,
        likeNum: 0,
      },
      {
          type: "IMAGE",
          content: 'http://p.chanyouji.cn/1473699595/1740E45C-D5AF-497E-A351-06E5BA22B1A3.jpg',
          poi: {
              longitude: 117.2,
              latitude: 37.5,
              name: "深圳市",
          },
          description: "深圳寶安國際機場",
          id: 2,
          commentNum: 1,
          likeNum: 5,
      },
      {
        type: "IMAGE",
        content: 'http://p.chanyouji.cn/1473699603/7C3B253F-0A31-4754-B042-E04115F2C931.jpg',
        poi: {
          longitude: 117.2,
          latitude: 37.5,
          name: "麗江三義機場",
        },
        description: "麗江三義機場",
        id: 2,
        commentNum: 1,
        likeNum: 5,
      },
      {
        type: "TEXT",
        content: ' 玉水寨在白沙溪鎮,是納西族中部地區的東巴聖地,是麗江古城的溯源。\n\nTips:門票50元/人,游玩時間2小時。',
        poi: {
          longitude: 117.2,
          latitude: 37.5,
          name: "玉水寨",
        },
        description: "",
        id: 1,
        commentNum: 0,
        likeNum: 0,
      },
      {
        type: "IMAGE",
        content: 'http://p.chanyouji.cn/1473685830/2A48B40F-1F11-498D-ABD2-B0EDCD09F776.jpg',
        poi: {
          longitude: 117.2,
          latitude: 37.5,
          name: "玉水寨",
        },
        description: "陽光下的玉水寨",
        id: 2,
        commentNum: 1,
        likeNum: 5,
      },
      {
        type: "VIDEO",
        content: 'http://flv.bn.netease.com/videolib3/1605/22/auDfZ8781/HD/auDfZ8781-mobile.mp4',
        poi: {
          longitude: 117.2,
          latitude: 37.5,
          name: "深圳寶安國際機場",
        },
        description: "",
        id: 2,
        commentNum: 10,
        likeNum: 200,
      },
    ]
  },
  {
    meta: {  // 內容元數據
      cover: "http://m.chanyouji.cn/articles/625/ca9e50f74e273041e3a399bf5528f7b5.jpg?imageView2/1/w/620/h/330/format/jpg",
      avatar: "https://pic4.zhimg.com/e515adf3b_xl.jpg",
      title: "夢想實現的地方-馬達加斯加第二季",
      meta: "2013.8.10",
      create_time: "2016.10.18 11:57:27",
      nickName: "小鬧鍾",
    },
    list: [
      {
        type: "TEXT",
        content: '2012年十一,我和朋友一行五人第一次登上這個被非洲大陸拋棄的島嶼,看到了可愛的狐猴,憨態可掬的變色龍,明信片一樣的猴面包樹,天真的孩子淳朴的人民,結識了我們生命中一個重要的朋友導游小溫(可以加地接小溫QQ或微信咨詢:109300820),從此,我們愛上了這片土地。馬達加斯加是一個海島,一年分成旱季和雨季,沒有特別的低溫或者高溫季節,幾乎全年都適合旅游,只是觀賞的重點略有不同而已。 \n導游小溫向我們介紹,在這里,每年的7月到9月,可以近距離觀看座頭鯨,於是,我們從那時開始期待這個夏季的到來。',
        poi: {
          longitude: 117.2,
          latitude: 37.5,
          name: "塔那那利佛",
        },
        description: "",
        id: 1,
        commentNum: 0,
        likeNum: 0,
      },
      {
        type: "TEXT",
        content: '第一天 8月10日 天氣晴\n\n長時間的飛行,多少會有一些枯燥,然而只要你願意,依然可以看到心中的那片風景。 \n嗨!別郁悶了,和我一起到三萬英尺的高空來看雲。 \n喜歡飛機起飛的剎那間,加速再加速直到脫離開地球的引力沖向自由的天空。喜歡像鳥一樣俯視地面的視角,高高在上,笑看人間。 \n天,藍,雲,白。機窗外的雲時而像珍珠點點,時而像棉絮團團。\n夕陽將至,雲和機翼被鍍上一層華麗的金。 \n金紅色的陽光與藍色的天空最終合成出一片淡淡的紫,絢麗而夢幻。',
        poi: {
          longitude: 117.2,
          latitude: 37.5,
          name: "塔那那利佛",
        },
        description: "",
        id: 1,
        commentNum: 0,
        likeNum: 0,
      },
      {
        type: "IMAGE",
        content: 'http://p.chanyouji.cn/64695/1377177446705p182j2oa9031j1p0b5vpuvj1voj2.jpg-o',
        poi: {
          longitude: 117.2,
          latitude: 37.5,
          name: "塔那那利佛",
        },
        description: "",
        id: 2,
        commentNum: 1,
        likeNum: 5,
      },
    ]
  }
]

module.exports = {
  diaries: diaries,
}
2、services
-geo.js
// 基於騰訊地圖API的地理位置功能封裝

const config = require('../config.js');
const request = require('request.js');

const statusCodeMap = {  // 請求失敗原因映射
    110: '請求來源未被授權',
    301: '請求參數信息有誤',
    311: 'key格式錯誤',
    306: '請求有護持信息請檢查字符串',
}

module.exports = {

    // 地圖API請求方法
    mapRequest(method, params, callback) {
        var url = [config.map.baseUrl, method, 'v1/'].join('/');
        let param = Object.assign({'key': config.map.key}, params);
        let queryString = Object.keys(param).map(q => [q, param[q]].join('=')).join('&');
        url += '?' + queryString;

        request({'method': 'GET', 'url': url}).then(resp => {
            if (resp.status != 0) {
                console.log('請求錯誤:' + (statusCodeMap[resp.status] || resp.message));
                request
            }

            return callback(resp);
        }).catch(err => {console.log(err);});
    },

    // 格式化地理位置
    formatLocation(loc) {
        return [loc.latitude, loc.longitude].map(f => f.toString()).join(',');
    },
}
-request.js
// 對微信網絡請求的異步封裝

module.exports = (options) => {
    return new Promise((resolve, reject) => {
        options = Object.assign(options, {
            success(result) {
                if (result.statusCode === 200) {
                    resolve(result.data);
                } else {
                    reject(result);
                }
            },

            fail: reject,
        });

        wx.request(options);
    });
};
3、utills
-input.js
// 輸入框相關處理函數

module.exports = {

  // 計算字符串長度(英文占一個字符,中文漢字占2個字符)
  strlen(str) {
    var len = 0;
    for (var i = 0; i < str.length; i++) {
      var c = str.charCodeAt(i);
      if ((c >= 0x0001 && c <= 0x007e) || (c >= 0xff60 && c <= 0xff9f)) {
        len++;
      } else {
        len += 2;
      }
    }
    return len;
  },
}
-utill.js
// 工具函數

function formatTime(date) {
  var year = date.getFullYear()
  var month = date.getMonth() + 1
  var day = date.getDate()

  var hour = date.getHours()
  var minute = date.getMinutes()
  var second = date.getSeconds();


  return [year, month, day].map(formatNumber).join('.') + ' ' + [hour, minute, second].map(formatNumber).join(':')
}

function formatNumber(n) {
  n = n.toString()
  return n[1] ? n : '0' + n
}

// 將一維數組轉為二維數組
function listToMatrix(list, elementPerSubArray) {
  let matrix = [], i, k;

  for (i = 0, k = -1; i < list.length; i += 1) {
    if (i % elementPerSubArray === 0) {
      k += 1;
      matrix[k] = [];
    }

    matrix[k].push(list[i]);
  }

  return matrix;
}

module.exports = {
  formatTime: formatTime,
  listToMatrix: listToMatrix,
}
4、
3.返回頂部
1、entry
a) .js
// entry.js

const toolbar = [
  '../../images/nav/download.png', '../../images/nav/fav.png',
  '../../images/nav/share.png', '../../images/nav/comment.png',
];
const app = getApp();

Page({
  data: {
    // 當前日志詳情
    diary: undefined,

    // 右上角工具欄
    toolbar: toolbar,

    // 圖片預覽模式
    previewMode: false,

    // 當前預覽索引
    previewIndex: 0,

    // 多媒體內容列表
    mediaList: [],
  },

  // 加載日記
  getDiary(params) {
    console.log("Loading diary data...", params);

    var id = params["id"], diary;
    app.getDiaryList(list => {
      if (typeof id === 'undefined') {
        diary = list[0];
      } else {
        diary = list[id];
      }
    });

    this.setData({
      diary: diary,
    });
  },

  // 過濾出預覽圖片列表
  getMediaList() {
    if (typeof this.data.diary !== 'undefined' &&
      this.data.diary.list.length) {
      this.setData({
        mediaList: this.data.diary.list.filter(
          content => content.type === 'IMAGE'),
      })
    }
  },

  // 進入預覽模式
  enterPreviewMode(event) {
    let url = event.target.dataset.src;
    let urls = this.data.mediaList.map(media => media.content);
    let previewIndex = urls.indexOf(url);

    this.setData({previewMode: true, previewIndex});
  },

  // 退出預覽模式
  leavePreviewMode() {
    this.setData({previewMode: false, previewIndex: 0});
  },

  onLoad: function(params) {
    this.getDiary(params);
    this.getMediaList();
  },

  onHide: function() {
  },
})
b) .json
c) .wxml
<!-- dairy.wxml -->

<!-- 單條內容 -->
<template name="content-item">
  <block wx:if="{{content.type == 'TEXT'}}">
    <view style="margin-top:30rpx">
      <text wx:if="{{content.type == 'TEXT'}}" class="text">{{content.content}}</text>
    </view>
  </block>
  <block wx:if="{{content.type == 'IMAGE'}}">
    <image class="media" mode="aspectFill" src="{{content.content}}" bindtap="enterPreviewMode" data-src="{{content.content}}"></image>
    <view style="margin-top: 10rpx">{{content.description}}</view>
  </block>
  <block wx:if="{{content.type == 'VIDEO'}}">
    <video class="media" src="{{content.content}}"></video>
    <view style="margin-top: 10rpx">{{content.description}}</view>
  </block>
  <template is="content-footer" data="{{content}}"></template>
</template>

<!-- 日記正文footer -->
<template name="content-footer">
  <view class="footer">
    <view class="left">
      <image mode="aspectFit" src="../../images/icons/poi.png"></image>
      <text style="margin-left:10rpx;">{{content.poi.name}}</text>
    </view>
    <view class="right">
      <image mode="aspectFit" src="../../images/icons/comment.png"></image>
      <view>{{content.commentNum}}</view>
    </view>
    <view class="right">
      <image mode="aspectFit" src="../../images/icons/like.png"></image>
      <view>{{content.likeNum}}</view>
    </view>
  </view>
</template>

<view class="container">
  <view class="header" style="background-image:url({{diary.meta.cover}})">
    <!--頂部固定工具欄-->
    <view class="toolbar">
      <image class="item" mode="aspectFit" wx:for="{{toolbar}}" src="{{item}}"></image>
    </view>

    <!--日記meta信息區-->
    <view class="title">
      <image class="avatar" mode="aspectFit" src="{{diary.meta.avatar}}"> </image>
      <view class="desc">
          <view class="item">{{diary.meta.title}}</view>
          <view class="item">{{diary.meta.meta}}</view>
      </view>
    </view>
  </view>

  <!--日記正文-->
  <view wx:for="{{diary.list}}" wx:for-item="content" class="content">
    <template is="content-item" data="{{content}}"></template>
  </view>

  <view id="footer">
    <view class="container">
      <view class="item" style="font-size:50rpx;">
        <view style="display:inline-block">THE</view>
        <view style="display:inline-block;margin-left:10rpx;color:#2EA1CA;">END</view>
      </view>
      <view class="item" style="font-size:24rpx;color:gray">DESIGNED BY 小鬧鍾</view>
    </view>
  </view>
</view>

<!-- 預覽模式 -->
<swiper class="swiper-container" duration="400" current="{{previewIndex}}" bindtap="leavePreviewMode" style="display:{{previewMode ? 'block' : 'none'}};">
  <block wx:for="{{mediaList}}" wx:for-item="media">
    <swiper-item>
      <image src="{{media.content}}" mode="aspectFit"></image>
    </swiper-item>
  </block>
</swiper>
d) .wxss
/** item.wxss **/

.container {
  font-size: 26rpx;
}

.header {
  height: 400rpx;
}

.toolbar {
  height: 60rpx;
  position: fixed;
  top: 0;
  right: 0;
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  align-items: center;
}

.toolbar .item {
  width: 40rpx;
  height: 40rpx;
  margin: 10rpx 20rpx;
}

.title {
  height: 120rpx;
  position: absolute;
  top: 280rpx;
}

.title .avatar {
  margin: 20rpx;
  width: 80rpx;
  height: 80rpx;
  border-radius: 40rpx;
  float: left;
}

.title .desc {
  height: 100rpx;
  width: 630rpx;
  margin: 10rpx 0;
  float: right;
  color: white;
  display: flex;
  flex-direction: column;
}

.desc .item {
  height: 50%;
  padding: 12rpx 0;
}

.content {
  padding: 10rpx;
  border-bottom: 1px solid #E5E7ED;
}

.content .text {
  line-height: 42rpx;
}

.content .media {
  width: 730rpx;
}

.content .footer {
  height: 60rpx;
  margin-top: 10rpx;
  font-size:22rpx;
  color: #70899D;
}

.content .footer image {
  width: 20rpx;
  height: 20rpx;
}

.content .footer .left {
  display: inline-block;
  height: 40rpx;
  margin: 10rpx 0;
}

.content .footer .right {
  display: flex;
  justify-content: space-between;
  align-items: center;
  float: right;
  height: 40rpx;
  margin-left: 20rpx;
  background-color: #E5E7ED;
  font-size: 20rpx;
}

.content .footer .right image {
  margin: 10rpx;
}

.content .footer .right text {
  font-size: 20rpx;
  padding:10rpx 10rpx 10rpx 0;
}

#footer {
  width: 100%;
  height: 300rpx;
  display: flex;
  justify-content: center;
  align-items: center;
}

#footer .container {
  height: 100rpx;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: center;
}

#footer .container .item {
  height: 50%;
  display: flex;
  justify-content: center;
  align-items: center;
}

.swiper-container {
  position: fixed;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  background-color: #000;
}

.swiper-container image {
  width: 100%;
  height: 100%;
}
e)
2、list
a) .js
// index.js
// 日記聚合頁

const config = require("../../config");

var app = getApp();

Page({

  data: {
    // 日記列表
    // TODO 從server端拉取
    diaries: null,

    // 是否顯示loading
    showLoading: false,

    // loading提示語
    loadingMessage: '',
  },

  /**
   * 生命周期函數--監聽頁面加載
   */
  onLoad() {
    this.getDiaries();
  },

  /**
   * 獲取日記列表
   * 目前為本地緩存數據 + 本地假數據
   * TODO 從服務端拉取
   */
  getDiaries() {
    var that = this;
    app.getDiaryList(list => {
      that.setData({diaries: list});
    })
  },

  // 查看詳情
  showDetail(event) {
    wx.navigateTo({
      url: '../entry/entry?id=' + event.currentTarget.id,
    });
  }
})
b) .json
c) .wxml
<!--list.wxml-->

<scroll-view scroll-y="true">
  <view wx:for="{{diaries}}" wx:for-index="idx" class="item-container" bindtap="showDetail" id="{{idx}}">
    <image mode="aspectFit" src="{{item.meta.cover}}" class="cover"></image>
    <view class="desc">
      <view class="left">
        <view style="font-size:32rpx;margin:10rpx 0;">{{item.meta.title}}</view>
        <view style="font-size:24rpx;color:darkgray">{{item.meta.meta}}</view>
      </view>
      <view class="right">
        <image mode="aspectFit" src="{{item.meta.avatar}}"></image>
        <text style="font-size:24rpx;margin-top:10rpx;color:darkgray">{{item.meta.nickName}}</text>
      </view>
    </view>
  </view>
</scroll-view>
d) .wxss
/** list.wxss **/

.item-container {
  margin: 10rpx 20rpx;
  position: relative;
}

.cover {
  width: 100%;
  height: 400rpx;
  display: block;
}

.desc {
  margin: 10rpx 0;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-bottom: 20rpx;
  border-bottom: 1px solid #E5E7ED;
}

.desc .left {
}

.desc .right {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.right image{
  display: block;
  width: 60rpx;
  height: 60rpx;
  border-radius: 30rpx;
}
e)
3、mine
a) .js
// mine.js

// 自定義標簽
var iconPath = "../../images/icons/"
var tabs = [
    {
        "icon": iconPath + "mark.png",
        "iconActive": iconPath + "markHL.png",
        "title": "日記",
        "extraStyle": "",
    },
    {
        "icon": iconPath + "collect.png",
        "iconActive": iconPath + "collectHL.png",
        "title": "收藏",
        "extraStyle": "",
    },
    {
        "icon": iconPath + "like.png",
        "iconActive": iconPath + "likeHL.png",
        "title": "喜歡",
        "extraStyle": "",
    },
    {
        "icon": iconPath + "more.png",
        "iconActive": iconPath + "moreHL.png",
        "title": "更多",
        "extraStyle": "border:none;",
    },
]
var userInfo = {
    avatar: "https://pic4.zhimg.com/e515adf3b_xl.jpg",
    nickname: "小鬧鍾",
    sex: "♂",  // 0, male; 1, female
    meta: '1篇日記',
}


Page({

    // data
    data: {
        // 展示的tab標簽
        tabs: tabs,

        // 當前選中的標簽
        currentTab: "tab1",

        // 高亮的標簽索引
        highLightIndex: "0",

        // 模態對話框樣式 
        modalShowStyle: "",

        // 待新建的日記標題
        diaryTitle: "",

        // TODO 用戶信息
        userInfo: userInfo,
    },

    // 隱藏模態框
    hideModal() {
        this.setData({modalShowStyle: ""});
    },

    // 清除日記標題
    clearTitle() {
        this.setData({diaryTitle: ""});
    },

    onShow: function() {
        this.hideModal();
        this.clearTitle();
    },

    // 點擊tab項事件
    touchTab: function(event){
        var tabIndex = parseInt(event.currentTarget.id);
        var template = "tab" + (tabIndex + 1).toString();

        this.setData({
            currentTab: template,
            highLightIndex: tabIndex.toString()
        }
        );
    },

    // 點擊新建日記按鈕
    touchAdd: function (event) {
        this.setData({
            modalShowStyle: "opacity:1;pointer-events:auto;"
        })
    },

    // 新建日記
    touchAddNew: function(event) {
        this.hideModal();

        wx.navigateTo({
            url: "../new/new?title=" + this.data.diaryTitle,
        });
    },

    // 取消標題輸入
    touchCancel: function(event) {
        this.hideModal();
        this.clearTitle();
    }, 

    // 標題輸入事件
    titleInput: function(event) {
        this.setData({
            diaryTitle: event.detail.value,
        })
    }
})
b) .json
c) .wxml
<!--mine.wxml-->

<template name="tab1">
    <view>
    </view>
</template>

<template name="tab2">
    <view>
    </view>
</template>

<template name="tab3">
    <view>
    </view>
</template>

<template name="tab4">
    <view>
    </view>
</template>

<view>
  <!--一個全屏模態對話框-->
  <view class="modal" style="{{modalShowStyle}}">
    <view class="dialog">
      <view class="modal-item" style="display:flex;justify-content:center;align-items:center;">
      請輸入日記標題
      </view>
      <view class="modal-item" style="margin:0 auto;width:90%;">
        <input type="text" bindinput="titleInput" style="background-color:white;border-radius:2px;" value="{{diaryTitle}}" placeholder="請輸入日記標題"></input>
      </view>
      <view class="modal-button" style="width:100%">
        <view style="color:green;border-right:1px solid #E5E7ED;" bindtap="touchAddNew">確定</view>
        <view bindtap="touchCancel">取消</view>
      </view>
    </view>
  </view>

  <view class="header">
    <view class="profile">
      <image class="avatar" mode="aspectFit" src="{{userInfo.avatar}}"></image>
      <view class="description">
        <view class="item">
          <view style="margin-right:5px">{{userInfo.nickname}}</view>
          <view>{{userInfo.sex}}</view>
        </view>
        <view class="item">{{userInfo.meta}}</view>
      </view>
      <image class="add" mode="aspectFill" src="../../images/icons/add.png" bindtap="touchAdd"></image>
    </view>

    <view class="tablist">
      <view wx:for="{{tabs}}" wx:for-index="idx" class="tab" bindtap="touchTab" style="{{item.extraStyle}}" id="{{idx}}">
        <view class="content" style="color:{{highLightIndex == idx ? '#54BFE2' : ''}};">
          <image class="image" mode="aspectFit" src="{{highLightIndex == idx ? item.iconActive : item.icon}}"></image>
          <view style="margin-top:2px;">{{item.title}}</view>
        </view>
      </view>
    </view>
  </view>

    <template is="{{currentTab}}"></template>
</view>
d) .wxss
/**mine.wxss**/

.header {
  height: 130px;
  background: white;
}

.header .profile {
  height: 50%;
}

.profile .avatar {
  width: 50px;
  height: 50px;
  float: left;
  margin: 7.5px 10px;
  border-radius: 25px;
}

.profile .description {
  display: inline-block;
  margin: 7.5px auto;
  height: 50px;
}

.description .item {
    height: 50%;
    display: flex;
    align-items: center;
}

.profile .add {
    float: right;
    margin: 15px 10px;
    height: 35px;
    width: 35px;
}

.header .tablist {
    height: 50%;
    display: flex;
    justify-content: space-between;
    align-items: center;
}

.tablist .tab {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 50px;
    width: 25%;
    margin: 7.5px 0px;
    border-right: 1px solid #eceff4;
}

.tab .content{
    width: 25px;
    height: 50px;
    font-size: 12px;
    color: #B3B3B3;
}

.tab .image {
    width: 25px;
    height: 25px;
    margin-top: 10px;
}

.modal {
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    right: 0;
    background: rgba(0, 0, 0, .5);
    z-index: 99999;
    opacity: 0;
    transition: opacity 400ms ease-in;
    pointer-events: none;
    display: flex;
    justify-content: center;
    align-items: center;
}

.modal .dialog {
    width: 84%;
    height: 28%;
    background-color: #eceff4;
    border-radius: 4px;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
}

.dialog .modal-item {
    height: 33.3%;
    width: 100%;
}

.modal-button {
    height: 100rpx;
    margin-bottom: 0;
    display: flex;
    flex-direction: row;
    justify-content: space-between;
}

.modal-button view {
    width: 50%;
    border-top: 1px solid #E5E7ED;
    display: flex;
    justify-content: center;
    align-items: center;
}
e)
4、new
a) .js
// new.js
// TODO 並不是所有非中文字符寬度都為中文字符寬度一半,需特殊處理
// TODO 由於文本框聚焦存在bug,故編輯模式待實現

const input = require('../../utils/input');
const config = require('../../config');
const geo = require('../../services/geo');
const util = require('../../utils/util');

const RESOLUTION = 750;  // 微信規定屏幕寬度為750rpx
const MARGIN = 10;  // 寫字面板左右margin
const ROW_CHARS = Math.floor((RESOLUTION - 2 * MARGIN) / config.input.charWidth);
const MAX_CHAR = 1000;  // 最多輸1000字符

// 內容布局
const layoutColumnSize = 3;

// 日記內容類型
const TEXT = 'TEXT';
const IMAGE = 'IMAGE';
const VIDEO = 'VIDEO';

const mediaActionSheetItems = ['拍照', '選擇照片', '選擇視頻'];
const mediaActionSheetBinds = ['chooseImage', 'chooseImage', 'chooseVideo'];

var app = getApp();

Page({

  data: {
    // 日記對象
    diary: {
      meta: {},
      list: [],
    },

    // 日記內容布局列表(2x2矩陣)
    layoutList: [],

    // 是否顯示loading
    showLoading: false,

    // loading提示語
    loadingMessage: '',

    // 頁面所處模式
    showMode: 'common',

    // 輸入框狀態對象
    inputStatus: {
      row: 0,
      column: 0,
      lines: [''],
      mode: 'INPUT',
      auto: false,  // 是否有自動換行
    },

    // 當前位置信息
    poi: null,

    // 點擊`圖片`tab的action-sheet
    mediaActionSheetHidden: true,

    // 多媒體文件插入action-sheet
    mediaActionSheetItems: mediaActionSheetItems,

    // 多媒體文件插入項點擊事件
    mediaActionSheetBinds: mediaActionSheetBinds,

    // 是否顯示底部tab欄
    showTab: true,
  },

  // 顯示底部tab
  showTab() {
    this.setData({showTab: true});
  },

  // 隱藏底部tab
  hideTab() {
    this.setData({showTab: false});
  },

  // 顯示loading提示
  showLoading(loadingMessage) {
    this.setData({showLoading: true, loadingMessage});
  },

  // 隱藏loading提示
  hideLoading() {
    this.setData({showLoading: false, loadingMessage: ''});
  },

  // 數據初始化
  init() {
    this.getPoi();
    this.setMeta();
  },

  // 設置日記數據
  setDiary(diary) {
    let layout = util.listToMatrix(diary.list, layoutColumnSize);
    this.setData({diary: diary, layoutList: layout});
    this.saveDiary(diary);
  },

  // 保存日記
  // TODO sync to server
  saveDiary(diary) {
    const key = config.storage.diaryListKey;

    app.getLocalDiaries(diaries => {
      diaries[diary.meta.title] = diary;
      wx.setStorage({key: key, data: diaries});
    })
  },

  // 頁面初始化
  onLoad: function(options) {
    if (options) {
      let title = options.title;
      if (title) {this.setData({
        'diary.meta.title': title,
        'diary.meta.create_time': util.formatTime(new Date()),
        'diary.meta.cover': ''
      });}
    }

    this.init();
  },

  // 頁面渲染完成
  onReady: function(){
    wx.setNavigationBarTitle({title: '編輯日記'});
  },

  onShow:function(){
    // 頁面顯示
  },

  onHide:function(){
    // 頁面隱藏
  },

  onUnload:function(){
    // 頁面關閉
    console.log('頁面跳轉中...');
  },

  // 清除正在輸入文本
  clearInput() {
    this.setData({inputStatus: {
      row: 0,
      common: 0,
      lines: [''],
      mode: 'INPUT',
      auto: false,
    }});
  },

  // 結束文本輸入
  inputDone() {
    let text = this.data.inputStatus.lines.join('\n');
    let diary = this.data.diary;

    if (text) {
      diary.list.push(this.makeContent(TEXT, text, ''));
      this.setDiary(diary);
    }

    this.inputCancel();
  },

  // 進入文本編輯模式
  inputTouch(event) {
    this.setData({showMode: 'inputText'});
  },

  // 取消文本編輯
  inputCancel() {
    this.setData({showMode: 'common'});
    this.clearInput();
  },

  // 文本輸入
  textInput(event) {
    console.log(event);
    let context = event.detail;

    // 輸入模式
    if (this.data.inputStatus.mode === 'INPUT') {
      if (context.value.length != context.cursor) {
        console.log('用戶輸入中...');
      } else {
        let text = context.value;
        let len = input.strlen(text);
        let lines = this.data.inputStatus.lines;
        let row = this.data.inputStatus.row;
        let [extra, extra_index] = [[['']], 0];
        let hasNewLine = false;
        console.log('當前文本長度: ' + len);

        // 當前輸入長度超過規定長度
        if (len >= ROW_CHARS) {
          // TODO 此處方案不完善
          // 一次輸入最好不超過兩行
          hasNewLine = true;
          while (input.strlen(text) > ROW_CHARS) {
            let last = text[text.length - 1];

            if (input.strlen(extra[extra_index] + last) > ROW_CHARS) {
              extra_index += 1;
              extra[extra_index] = [''];
            }

            extra[extra_index].unshift(last);
            text = text.slice(0, -1);
          }
      }

      lines[lines.length - 1] = text;
      if (hasNewLine) {
        extra.reverse().forEach((element, index, array) => {
          lines.push(element.join(''));
          row += 1;
        });
      }

      let inputStatus = {
        lines: lines,
        row: row,
        mode: 'INPUT',
        auto: true,  // // 自動換行的則處於輸入模式
      };

      this.setData({inputStatus});
      }
    }
  },

  // 文本框獲取到焦點
  focusInput(event) {
    let isInitialInput = this.data.inputStatus.row == 0 &&
                         this.data.inputStatus.lines[0].length == 0;
    let isAutoInput = this.data.inputStatus.mode == 'INPUT' &&
                      this.data.inputStatus.auto == true;
    let mode = 'EDIT';

    if (isInitialInput || isAutoInput) {
      mode = 'INPUT';
    }

    this.setData({'inputStatus.mode': mode});
  },

  // 點擊多媒體插入按鈕
  mediaTouch() {
    this.setData({
      showTab: false,
      mediaActionSheetHidden: false,
    });
  },

  mediaActionSheetChange(event) {
    this.setData({
      showTab: true,
      mediaActionSheetHidden: true,
    })
  },

  // 將內容寫入至日記對象
  writeContent(res, type) {
    let diary = this.data.diary;

    if (type === IMAGE) {
      res.tempFilePaths.forEach((element, index, array) => {
        // TODO 內容上傳至服務器
        diary.list.push(this.makeContent(type, element, ''))
      });
    }

    if (type === VIDEO) {
      // TODO 內容上傳至服務器
      diary.list.push(this.makeContent(type, res.tempFilePath, ''))
    }

    // 設置日記封面
    if (type === IMAGE && !this.data.diary.meta.cover) {
      this.setData({'diary.meta.cover': res.tempFilePaths[0]});
    }

    this.setDiary(diary);
    this.hideLoading();
    this.showTab();
  },

  // 從相冊選擇照片或拍攝照片
  chooseImage() {
    let that = this;

    wx.chooseImage({
      count: 9,  // 最多選9張
      sizeType: ['origin', 'compressed'],
      sourceType: ['album', 'camera'],

      success: (res) => {
        this.setData({mediaActionSheetHidden: true});
        this.showLoading('圖片處理中...');
        that.writeContent(res, IMAGE);
      }
    })
  },

  // 從相冊選擇視頻文件
  chooseVideo() {
    let that = this;

    wx.chooseVideo({
      sourceType: ['album'],  // 僅從相冊選擇
      success: (res) => {
        this.setData({mediaActionSheetHidden: true});
        this.showLoading('視頻處理中...');
        that.writeContent(res, VIDEO);
      }
    })
  },

  // 獲得當前位置信息
  getPoi() {
    var that = this;
    wx.getLocation({
      type: 'gcj02',
      success: function(res) {
        geo.mapRequest(
          'geocoder',
          {'location': geo.formatLocation(res)},
          loc => {
            let poi = {
              'latitude': res.latitude,
              'longitude': res.longitude,
              'name': loc.result.address,
            };
            that.setData({poi: poi});
          })
      }
    })
  },

  // 構造日記內容對象
  makeContent(type, content, description) {
    return {
      type: type,
      content: content,
      description: description,
      poi: this.data.poi,
    };
  },

  // 構造日記meta信息
  setMeta() {
    var that = this;
    app.getUserInfo(info => {
      that.setData({
        'diary.meta.avatar': info.avatarUrl,
        'diary.meta.nickName': info.nickName,
      })
    })
  },

})
b) .json
c) .wxml
<!--new.wxml-->

<template name="common">
  <scroll-view class="container" scroll-y="true">
    <view class="common-container">
      <view class="item-group" wx:for="{{layoutList}}" wx:for-item="group">
        <block wx:for="{{group}}" wx:for-item="item">
          <block wx:if="{{item.type == 'TEXT'}}">
            <view class="album-item content-text">
              <view>{{item.content}}</view>
            </view>
          </block>
          <block wx:elif="{{item.type == 'IMAGE'}}">
            <image src="{{item.content}}" class="album-item" mode="aspectFill"></image>
          </block>
          <block wx:elif="{{item.type == 'VIDEO'}}">
            <video class="album-item" src="{{item.content}}"></video>
          </block>
        </block>
      </view>
    </view>
  </scroll-view>

  <view class="tabbar" style="display:{{showTab ? 'flex' : 'none'}};">
    <view class="item" bindtap="inputTouch">
      <image class="icon" mode="aspectFit" src="../../images/tabbar/text.png"></image>
    </view>
    <view class="item" bindtap="mediaTouch">
      <image class="icon" mode="aspectFit" src="../../images/tabbar/image.png"></image>
    </view>
    <view class="item">
      <image class="icon" mode="aspectFit" src="../../images/tabbar/more.png"></image>
    </view>
  </view>

  <action-sheet hidden="{{mediaActionSheetHidden}}" bindchange="mediaActionSheetChange">
    <block wx:for-items="{{mediaActionSheetItems}}" wx:for-index="id">
      <action-sheet-item class="action-item" bindtap="{{mediaActionSheetBinds[id]}}">
        {{item}}
      </action-sheet-item>
    </block>
    <action-sheet-cancel class='action-cacel'>取消</action-sheet-cancel>
  </action-sheet>
</template>

<template name="inputText">
  <view class="input-container">
    <view style="height:47rpx" wx:for="{{inputStatus.lines}}" wx:for-index="idx">
      <input type="text" data-index="{{idx}}" placeholder="" bindinput="textInput" bindchange="textInputChange" value="{{item}}" auto-focus="{{idx == inputStatus.row ? true : false}}" bindfocus="focusInput"/>
    </view>
  </view>
  <view class="tabbar">
    <view class="item" style="width:50%" bindtap="inputCancel">
      <image class="icon" mode="aspectFit" src="../../images/tabbar/cancel.png"></image>
    </view>
    <view class="item" style="width:50%" bindtap="inputDone">
      <image class="icon" mode="aspectFit" src="../../images/tabbar/ok.png"></image>
    </view>
  </view>
</template>

<view style="width:100%;height:100%">
  <block wx:if="{{showMode == 'common'}}">
    <template is="{{showMode}}" data="{{showTab: showTab, mediaActionSheetHidden: mediaActionSheetHidden, mediaActionSheetItems: mediaActionSheetItems, mediaActionSheetBinds: mediaActionSheetBinds, layoutList: layoutList}}"></template>
  </block>
  <block wx:if="{{showMode == 'inputText'}}">
    <template is="{{showMode}}" data="{{inputStatus}}"></template>
  </block>
  <loading hidden="{{!showLoading}}" bindchange="hideLoading">
    {{loadingMessage}}
  </loading>
</view>
d) .wxss
/** new.wxss **/

.container {
  height: 91%;
}

.common-container {
  margin: 0.1rem;
}

.item-group {
  display: flex;
  align-items: center;
}

.album-item {
  flex-direction: column;
  margin: 0.1rem;
  background: white;
  width: 6.4rem;
  height: 6.4rem;
}

.content-text{
  justify-content: center;
  align-items: center;
  display: flex;
}

.content-text view {
  overflow: hidden;
  text-overflow: ellipsis;
  font-size: 10px;
  line-height: 15px;
}

.tabbar {
  position: fixed;
  width: 100%;
  height: 8%;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: white;
  display: flex;
  flex-direction: row;
  justify-content: space-between;
}

.tabbar .item {
  width: 33.33%;
  display: flex;
  justify-content: center;
  align-items: center;
}

.item .icon {
  width: 50rpx;
  height: 50rpx;
}

.input-container {
  height: 80%;
  background-color: #eceff4;
  background-image: linear-gradient(#E1E6EA .1em, transparent .1em);
  background-size: 100% 48rpx;
  padding: 0;
  box-sizing: border-box;
  margin: 0 10rpx;
}

.input-container input{
  height: 47rpx;
  max-height: 47rpx;
  font-size: 28rpx;
  margin: 0px;
}

.action-item, .action-cacel {
  font-size: 30rpx;
  color: #39b5de;
}
e)
5、
a) .js
b) .json
c) .wxml
d) .wxss
e)
6、
4.返回頂部
 
5.返回頂部
0、
1、
 
6.返回頂部
 
warn 作者:ylbtech
出處:http://ylbtech.cnblogs.com/
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。


免責聲明!

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



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