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/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。 |