微信小程序-游记分享(无后台)
阅读原文时间:2021年11月20日阅读:1

游记分享

博客班级

https://edu.cnblogs.com/campus/zjcsxy/SE2020

作业要求

https://edu.cnblogs.com/campus/zjcsxy/SE2020/homework/11334

作业目标

1. 编写一个小程序,可以全新编写,也可以学习别人的小程序进行修改 2. 熟悉git代码管理流程,将源代码上传到到github 3. 在博客园班级中写一篇相应的博文

作业源代码

git@github.com:wfs2018/software-engineering.git

学号

31801131 翁芳胜

院系

浙大城市学院计算分院

本项目的灵感来自于小红书和大众点评,年轻人常常在这些平台上发布吃喝玩乐的心得和推荐。假期是时候想出去游玩却难以选择游玩地点。于是想到做一个旅游日记共享平台,可以看他人的游玩经历和心得。从而选择自己喜欢的游玩地点。由于是第一次做小程序,没有相关知识和经验,本项目只完成了首页游记展示,发布,收藏和喜欢的简单前端搭建,后续随着进一步的学习会完善相关功能。

参考: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。一句一句的理解,调试。几天下来对小程序的结构有了了解。在做小程序的时候,也遇到了一些困难,在搜素引擎和同学们的帮助下也都解决了。虽然最后做的很粗糙,但是在这个过程中,基本上完成了小程序的入门,也知道了怎么去解决遇到的问题。这次的作业也让我明白,在开发方面还有很多需要学习的知识。需要更加努力