小程序-demo:小熊の日记
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.返回顶部 |
// 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,
} })
{
"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
}
/**
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; }
// 全局配置 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',
}
};
{
"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": []
}
}
}
2. pages返回顶部 |
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,
}
// 基于腾讯地图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(',');
},
}
// 对微信网络请求的异步封装 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);
});
};
// 输入框相关处理函数 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;
},
}
// 工具函数 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,
}
3.返回顶部 |
// 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() {
},
})
<!-- 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>
/** 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%;
}
// 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,
});
}
})
<!--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>
/** 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;
}
// 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,
})
}
})
<!--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>
/**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;
}
// 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,
})
})
}, })
<!--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>
/** 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;
}
4.返回顶部 |
5.返回顶部 |
6.返回顶部 |
作者:ylbtech 出处:http://ylbtech.cnblogs.com/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。 |
小程序-demo:小熊の日记的更多相关文章
- 近期热门微信小程序demo源码下载汇总
近期微信小程序demo源码下载汇总,乃小程序学习分析必备素材!点击标题即可下载: 即速应用首发!原创!电商商场Demo 优质微信小程序推荐 -秀人美女图 图片下载.滑动翻页 微信小程序 - 新词 GE ...
- 微信小程序DEMO初体验
小程序虽然被炒的很热,但是绝大部分人却从未亲自体验过,在2017年的上班第一天,献上一个小程序DEMO,您可以体验! 注意:由于微信限制,只能使用扫一扫来体验下方小程序DEMO. DEMO首页截图如下 ...
- 微信小程序demo
微信小程序demo github地址 去年小程序刚发布时特别火,赶潮流做了个demo.感觉小程序开发还是比较简单的,主要是官方文档写得比较好,遗憾的是很多API需要微信认证才能使用. 由于小程序包大小 ...
- 微信小程序demo-环球小镇
微信小程序-环球小镇说明:实现了环球小镇(huanqiuxiaozhen.com)移动端商城客户端部分功能,包括首页,分类,购物车,帐户,品牌列表,商品详情等功能. 项目下载:http://bb ...
- 番外篇!全球首个微信应用号开发教程!小程序 DEMO 视频奉上!
大家好,我是博卡君.经过国庆节的七天假期,相信很多朋友都已经研究出自己的小程序 demo 了吧?我最近也利用休息时间关注了一下网上关于小程序开发的讨论,今天就利用这个番外篇谈谈自己对小程序的一些想法吧 ...
- 微信小程序-阅读小程序demo
今天和朋友聊天说到小程序,然后看在看书,然后我们就弄了个小读书的demo,然后现在分享一下. 一.先来上图: 二.然后下面是详细的说明 首先先说下边的tabBar,项目采用json格式的数据配置,不 ...
- 微信小程序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 ...
- 微信小程序demo豆瓣图书
最近微信小程序被炒得很火热,本人也抱着试一试的态度下载了微信web开发者工具,开发工具比较简洁,功能相对比较少,个性化设置也没有.了解完开发工具之后,顺便看了一下小程序的官方开发文档,大概了解了小程序 ...
- 微信小程序——demo合集及简单的文档解读【五】
官方Demo https://github.com/wechat-miniprogram/miniprogram-demo 其他Demo https://www.cnblogs.com/ytkah/p ...
随机推荐
- sequence(bzoj 1367)
Description Input Output 一个整数R Sample Input 794820141518 Sample Output 13 HINT 所求的Z序列为6,7,8,13,14,15 ...
- CodeForces - 754B Ilya and tic-tac-toe game
简单搜索 判断是否能在最后一步下棋得到胜利 问题转化为 是否有可以胜利的x的摆法 那么就只有两种情况 1.有两个x相连 并且 在端点还有.可以落子 那么就可以在最后一步 胜利 2.两个x中间恰好有一个 ...
- Codeforces932D. Tree
n<=400000个在线操作:树上插入一个某点权.父亲为某点的点:查询这样的最长点序列:序列的某个数必须是上一个数的祖先之一:序列的点权和不能超过x:序列的某个点的点权必须不小于上一个,且相邻两 ...
- c/s程序版本自动升级的问题,如何判断client端版本号是否最新,然后从指定ftp服务器down
c/s程序版本自动升级的问题,如何判断client端版本号是否最新,然后从指定ftp服务器down http://blog.csdn.net/delphizhou/article/details/30 ...
- Mysql数据库的事物
一 .事物的特性:ACID 数据库的事务必须具备ACID特性,ACID是指 Atomicity(原子性).Consistensy(一致性).Isolation(隔离型)和Durability(持久性) ...
- 获取鼠标位置的几个通用的JS函数
原文:http://www.open-open.com/code/view/1421401009218 /*两个通用函数,用于获取鼠标相对于整个页面的当前位置*/ function getX(e) { ...
- Cg入门6:函数2
内建函数分为四类: 1.数学函数 2.几何函数 3.纹理函数 4.导数函数:事实上就是片段函数
- [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 ...
- libevent HTTP client 的实现
my_conn_ = evhttp_connection_base_new(ev_base_,ev_dns_,host,port); struct evhttp_request *http_req; ...
- 动态标绘演示系统1.4.3(for ArcGIS Flex)
标绘有API文档啦! 在线浏览 ------------------------------------------------------------------------------------ ...