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

1、CHANGELOG.md

# --

* 更新开发者工具至`v0.10.101100`
* 修改`new`页的数据绑定方式 & 修改多行文本框输入时的bug # -- * 完善日志编辑页

2、README.md

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

## 关于 ##

* 我是一名后端程序员,做这个仅仅是因为觉得微信小程序好玩;
* 没有明确的产品意图,东抄抄西抄抄只是为了验证和学习微信小程序;
* 大体是想做一个个人/家庭日常记录的app;
* 持续开发中,有兴趣请持续关注 ## 预览 ## * 概览 <p align="center">
<img src="./files/preview.gif" alt="截频演示" width="100%">
</p> ## 功能特点 ## * 功能完备,实用导向
* Server端API支持
* 涵盖众多组件、API使用,适用于学习微信小程序
* 多行文本模拟实现
* tab切换
* 模态框
* 本地数据组织及存储
* 图片预览功能 ## 使用步骤 . 克隆代码: ```bash
$ cd path/to/your/workspace
$ git clone https://github.com/harveyqing/BearDiary.git
``` . 打开`微信Web开放者工具`(注意:须`v0.10.101100`及以上版本) . 添加项目 * AppID:选`无AppID`
* 项目名称:任意填写
* 项目目录:path/to/your/workspace
* 点击 `添加项目` ## 开发计划 ## - [ ] 开发server端API接口
- [ ] 完成日记撰写页
- [ ] 添加评论、喜欢、收藏功能
- [ ] 规范`coding style` ## 小程序开发相关资源 ## ### 开发者工具下载 ### > 最新版本 0.10. - [windows ](https://servicewechat.com/wxa-dev-logic/download_redirect?type=x64&from=mpwiki&t=1476434677599)
- [windows ](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:;
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:;
right:;
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:;
top:;
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:;
left:;
bottom:;
right:;
background: rgba(0, 0, 0, .5);
z-index:;
opacity:;
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:;
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:;
right:;
bottom:;
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:;
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/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

小程序-demo:小熊の日记的更多相关文章

  1. 近期热门微信小程序demo源码下载汇总

    近期微信小程序demo源码下载汇总,乃小程序学习分析必备素材!点击标题即可下载: 即速应用首发!原创!电商商场Demo 优质微信小程序推荐 -秀人美女图 图片下载.滑动翻页 微信小程序 - 新词 GE ...

  2. 微信小程序DEMO初体验

    小程序虽然被炒的很热,但是绝大部分人却从未亲自体验过,在2017年的上班第一天,献上一个小程序DEMO,您可以体验! 注意:由于微信限制,只能使用扫一扫来体验下方小程序DEMO. DEMO首页截图如下 ...

  3. 微信小程序demo

    微信小程序demo github地址 去年小程序刚发布时特别火,赶潮流做了个demo.感觉小程序开发还是比较简单的,主要是官方文档写得比较好,遗憾的是很多API需要微信认证才能使用. 由于小程序包大小 ...

  4. 微信小程序demo-环球小镇

    微信小程序-环球小镇说明:实现了环球小镇(huanqiuxiaozhen.com)移动端商城客户端部分功能,包括首页,分类,购物车,帐户,品牌列表,商品详情等功能.    项目下载:http://bb ...

  5. 番外篇!全球首个微信应用号开发教程!小程序 DEMO 视频奉上!

    大家好,我是博卡君.经过国庆节的七天假期,相信很多朋友都已经研究出自己的小程序 demo 了吧?我最近也利用休息时间关注了一下网上关于小程序开发的讨论,今天就利用这个番外篇谈谈自己对小程序的一些想法吧 ...

  6. 微信小程序-阅读小程序demo

    今天和朋友聊天说到小程序,然后看在看书,然后我们就弄了个小读书的demo,然后现在分享一下. 一.先来上图: 二.然后下面是详细的说明  首先先说下边的tabBar,项目采用json格式的数据配置,不 ...

  7. 微信小程序demo理解

    p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Verdana } p.p2 { margin: 0.0px 0.0px 0.0px 0.0p ...

  8. 微信小程序demo豆瓣图书

    最近微信小程序被炒得很火热,本人也抱着试一试的态度下载了微信web开发者工具,开发工具比较简洁,功能相对比较少,个性化设置也没有.了解完开发工具之后,顺便看了一下小程序的官方开发文档,大概了解了小程序 ...

  9. 微信小程序——demo合集及简单的文档解读【五】

    官方Demo https://github.com/wechat-miniprogram/miniprogram-demo 其他Demo https://www.cnblogs.com/ytkah/p ...

随机推荐

  1. sequence(bzoj 1367)

    Description Input Output 一个整数R Sample Input 794820141518 Sample Output 13 HINT 所求的Z序列为6,7,8,13,14,15 ...

  2. CodeForces - 754B Ilya and tic-tac-toe game

    简单搜索 判断是否能在最后一步下棋得到胜利 问题转化为 是否有可以胜利的x的摆法 那么就只有两种情况 1.有两个x相连 并且 在端点还有.可以落子 那么就可以在最后一步 胜利 2.两个x中间恰好有一个 ...

  3. Codeforces932D. Tree

    n<=400000个在线操作:树上插入一个某点权.父亲为某点的点:查询这样的最长点序列:序列的某个数必须是上一个数的祖先之一:序列的点权和不能超过x:序列的某个点的点权必须不小于上一个,且相邻两 ...

  4. c/s程序版本自动升级的问题,如何判断client端版本号是否最新,然后从指定ftp服务器down

    c/s程序版本自动升级的问题,如何判断client端版本号是否最新,然后从指定ftp服务器down http://blog.csdn.net/delphizhou/article/details/30 ...

  5. Mysql数据库的事物

    一 .事物的特性:ACID 数据库的事务必须具备ACID特性,ACID是指 Atomicity(原子性).Consistensy(一致性).Isolation(隔离型)和Durability(持久性) ...

  6. 获取鼠标位置的几个通用的JS函数

    原文:http://www.open-open.com/code/view/1421401009218 /*两个通用函数,用于获取鼠标相对于整个页面的当前位置*/ function getX(e) { ...

  7. Cg入门6:函数2

    内建函数分为四类: 1.数学函数 2.几何函数 3.纹理函数 4.导数函数:事实上就是片段函数

  8. [Angular] Communicate Between Components Using Angular Dependency Injection

    Allow more than one child component of the same type. Allow child components to be placed within the ...

  9. libevent HTTP client 的实现

    my_conn_ = evhttp_connection_base_new(ev_base_,ev_dns_,host,port); struct evhttp_request *http_req; ...

  10. 动态标绘演示系统1.4.3(for ArcGIS Flex)

    标绘有API文档啦! 在线浏览 ------------------------------------------------------------------------------------ ...