本项目的灵感来自于小红书和大众点评,年轻人常常在这些平台上发布吃喝玩乐的心得和推荐。
假期是时候想出去游玩却难以选择游玩地点。
于是想到做一个旅游日记共享平台,可以看他人的游玩经历和心得。
从而选择自己喜欢的游玩地点。
由于是第一次做小程序,没有相关知识和经验,本项目只完成了首页游记展示,发布,收藏和喜欢的简单前端搭建,后续随着进一步的学习会完善相关功能。
参考:https://github.com/harveyqing/BearDiary.git
{
"pages": [
"pages/list/list",
"pages/mine/mine",
"pages/new/new",
"pages/entry/entry"
],
"window": {
"backgroundTextStyle": "light",
"navigationBarBackgroundColor": "#5566aa",
"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,
"sitemapLocation": "sitemap.json"
}
各页面代码
首页
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="#ffffff">
<!--顶部固定工具栏-->
<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">游记</view>
<view style="display:inline-block;margin-left:10rpx;color:#2EA1CA;">分享</view>
</view>
<view class="item" style="font-size:24rpx;color:gray">分享旅程,分享心情</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>
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() {
},
})
我的页面
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>
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://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3049066004,1582117064&fm=26&gp=0.jpg",
nickname: "wfs",
sex: "♂", // 0, male; 1, female
meta: '10篇日记',
}
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,
})
}
})
新建日记页面
<!--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>
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, // // 自动换行的则处于输入模式
};
列表
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>
JS
const config = require("../../config");
var app = getApp();
Page({
data: {
// 日记列表
// TODO 从server端拉取
diaries: null,
// 是否显示loading
showLoading: false,
// loading提示语
loadingMessage: '',
},
/**
* 生命周期函数--监听页面加载
*/
onLoad() {
this.getDiaries();
},
/**
* 获取日记列表
*/
getDiaries() {
var that = this;
app.getDiaryList(list => {
that.setData({diaries: list});
})
},
// 查看详情
showDetail(event) {
wx.navigateTo({
url: '../entry/entry?id=' + event.currentTarget.id,
});
}
})
总结
这是第一次编写小程序,刚刚听到作业的时候很震惊一周要做一个小程序。
从头开始学习html,css知识的同时,去参考完整的demo。
一句一句的理解,调试。
几天下来对小程序的结构有了了解。
在做小程序的时候,也遇到了一些困难,在搜素引擎和同学们的帮助下也都解决了。
虽然最后做的很粗糙,但是在这个过程中,基本上完成了小程序的入门,也知道了怎么去解决遇到的问题。
这次的作业也让我明白,在开发方面还有很多需要学习的知识。
需要更加努力
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)