| 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.返回頂部 |
| 作者:ylbtech 出處:http://ylbtech.cnblogs.com/ 本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。 |


