目录
一、小程序UI
1.讲述
1. 简洁明了:小程序的界面设计应该简洁明了,避免过多的视觉干扰和复杂的布局。用户可以通过一目了然的界面结构快速找到所需的功能。
2. 一致性:小程序的不同页面之间应该保持一致的界面设计,包括颜色、字体、按钮等方面的统一。这样可以提高用户的学习和使用效率,减少用户的困惑。
3. 易用性:小程序的界面设计应该考虑用户的使用习惯和心理需求,尽量减少用户的操作步骤和思考负担。例如,常用的功能应该放在显眼的位置,操作按钮应该易于点击等。
4. 可访问性:小程序的界面设计应该考虑到不同用户的特殊需求,例如视力障碍者、听力障碍者等。设计师应该通过合适的颜色对比度、字体大小等方式来提高小程序的可访问性。
5. 反馈机制:小程序的界面设计应该提供即时的反馈机制,告知用户他们的操作是否成功或失败。例如,可以通过动画、弹窗等方式来提示用户操作的结果。
2. 介绍vantWeapp
1. 提供常用UI组件:vantWeapp提供了丰富的UI组件,包括按钮、表单、列表、卡片、标签、导航、弹窗等,可以帮助开发者快速构建出美观、易用的小程序页面。
2. 提高开发效率:vantWeapp的组件具有可复用性,可以减少开发者的重复工作,提高开发效率。
3. 提供业务组件:vantWeapp还提供了一些常用的业务组件,例如地址选择器、城市选择器、日期选择器等,可以帮助开发者快速实现一些常用的业务需求。
4. 提供功能组件:vantWeapp还提供了一些功能组件,例如图片预览、下拉刷新、上拉加载等,可以帮助开发者实现一些常用的功能需求。
3. 使用vantWeapp
安装
构建
如图 :

依赖
引用
例如 :
{
    "navigationBarTitleText": "投票管理",
    "usingComponents": {
        "tabs":"/components/tabs/tabs",
        "van-button": "vant-weapp/button",
    }
}<van-button type="default">默认按钮</van-button>
<van-button type="primary">主要按钮</van-button>
<van-button type="info">信息按钮</van-button>
<van-button type="warning">警告按钮</van-button>
<van-button type="danger">危险按钮</van-button>效果图 :

二、后端
1. 后端实体对象
Vote : 投票
package com.CloudJun.ssm.model;
import lombok.Data;
@Data
public class Vote {
    private Integer id;
    private Integer meetingId;
    private String name;
    private String remark;
    private Integer state;
    private String images;
    public Vote(Integer id, Integer meetingId, String name, String remark, Integer state, String images) {
        this.id = id;
        this.meetingId = meetingId;
        this.name = name;
        this.remark = remark;
        this.state = state;
        this.images = images;
    }
    @Override
    public String toString() {
        return "Vote{" +
                "id=" + id +
                ", meetingId=" + meetingId +
                ", name='" + name + '\'' +
                ", remark='" + remark + '\'' +
                ", state=" + state +
                ", images='" + images + '\'' +
                '}';
    }
}2. 后端接口
VoteMapper : 自动生成的对象接口
package com.CloudJun.ssm.mapper;
import com.CloudJun.ssm.model.Vote;
import java.util.List;
public interface VoteMapper {
    int deleteByPrimaryKey(Integer id);
    int insert(Vote record);
    int insertSelective(Vote record);
    Vote selectByPrimaryKey(Integer id);
    int updateByPrimaryKeySelective(Vote record);
    int updateByPrimaryKey(Vote record);
    List<Vote> selectByList(Integer state);
}VoteService : 自己为了实现方法创建的接口
package com.CloudJun.ssm.service;
import com.CloudJun.ssm.model.Vote;
import java.util.List;
/**
 * @author CloudJun
 * @create  2023-10-24 14:41
 */
public interface VoteService {
    int deleteByPrimaryKey(Integer id);
    int insert(Vote record);
    int insertSelective(Vote record);
    Vote selectByPrimaryKey(Integer id);
    int updateByPrimaryKeySelective(Vote record);
    int updateByPrimaryKey(Vote record);
    List<Vote> selectByList(Integer state);
}
3. 实现类
VoteServiceimpl : 实现调用方法功能而创建的实现类
package com.CloudJun.ssm.service.impl;
import com.CloudJun.ssm.mapper.VoteMapper;
import com.CloudJun.ssm.model.Vote;
import com.CloudJun.ssm.service.VoteService;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
/**
 * @author CloudJun
 * @create  2023-10-24 14:42
 */
public class VoteServiceimpl implements VoteService {
    @Autowired
    private VoteMapper voteMapper;
    @Override
    public int deleteByPrimaryKey(Integer id) {
        return voteMapper.deleteByPrimaryKey(id);
    }
    @Override
    public int insert(Vote record) {
        return voteMapper.insert(record);
    }
    @Override
    public int insertSelective(Vote record) {
        return voteMapper.insertSelective(record);
    }
    @Override
    public Vote selectByPrimaryKey(Integer id) {
        return voteMapper.selectByPrimaryKey(id);
    }
    @Override
    public int updateByPrimaryKeySelective(Vote record) {
        return 0;
    }
    @Override
    public int updateByPrimaryKey(Vote record) {
        return 0;
    }
    @Override
    public List<Vote> selectByList(Integer state) {
        return voteMapper.selectByList(state);
    }
}
4. 请求处理类
VoteController : 处理前端发送的请求及处理数据,并且给予回馈数据到前端
package com.CloudJun.ssm.wxcontroller;
import com.CloudJun.ssm.mapper.VoteMapper;
import com.CloudJun.ssm.model.Info;
import com.CloudJun.ssm.model.Vote;
import com.CloudJun.ssm.util.ResponseUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
 * @author CloudJun
 * @create  2023-10-24 14:24
 */
@RestController
@RequestMapping("/wx/vote")
public class VoteController {
    @Autowired
    private VoteMapper voteMapper;
    @RequestMapping("/add")
    public Object Add (Vote vote){
        int i = voteMapper.insertSelective(vote);
        return ResponseUtil.ok();
    }
    @RequestMapping("/list")
    public Object list (Integer state){
        List<Vote> votes = voteMapper.selectByList(state);
        return ResponseUtil.ok(votes);
    }
    @RequestMapping("/update")
    public Object update (Vote vote){
        int i = voteMapper.updateByPrimaryKey(vote);
        return ResponseUtil.ok();
    }
}
三、前端
1 . 定义路径
// 以下是业务服务器API地址
 // 本机开发API地址
var WxApiRoot = 'http://localhost:8080/oapro/wx/';
// 测试环境部署api地址
// var WxApiRoot = 'http://192.168.191.1:8080/oapro/wx/';
// 线上平台api地址
//var WxApiRoot = 'https://www.oa-mini.com/demo/wx/';
module.exports = {
  IndexUrl: WxApiRoot + 'home/index', //首页数据接口
  SwiperImgs: WxApiRoot+'swiperImgs',
  MettingInfos: WxApiRoot+'meeting/list',
  MettingVote : WxApiRoot+'info/list',
  MettingVoteAdd : WxApiRoot+'vote/add',//增加投票
  MettingVoteList : WxApiRoot+'vote/list',//获取投票信息
  MettingVoteupdate : WxApiRoot+'vote/update',//确认投票
  AuthLoginByWeixin: WxApiRoot + 'auth/login_by_weixin', //微信登录
  UserIndex: WxApiRoot + 'user/index', //个人页面用户相关信息
  AuthLogout: WxApiRoot + 'auth/logout', //账号登出
  AuthBindPhone: WxApiRoot + 'auth/bindPhone' //绑定微信手机号
};2. 页面引用
list.json
{
    "navigationBarTitleText": "投票管理",
    "usingComponents": {
        "tabs":"/components/tabs/tabs",
        "van-button": "vant-weapp/button",
        "van-notice-bar": "vant-weapp/notice-bar/index",
        "van-toast": "vant-weapp/toast/index"
    }
}3. 页面
list.wxml
<!--pages/vote/list/list.wxml-->
<tabs tabList="{{tabs}}"  bindtabsItemChange="tabsItemChange">
</tabs>
<view style="height: 100rpx;"></view>
<view class="{{componentStatus[0] ? '' : 'hidden'}}">
<!-- 发起投票版块 -->
<view class="publish">
<form bindsubmit='votesubmit'>
  <view class="img">
<view class="img_left" >
<image class="imgs"  src="{{imageUrl=='' ? '/static/persons/15.gif':imageUrl }}" mode="aspectFit" bindtap="handleUploadImage"></image>
<input hidden="true"  type="text" name="images" value="{{imageUrl}}" />
</view>
</view>
  <view class="meeting_id">
  <view class="meeting_title">所属会议 : </view>
  <picker bindchange="meetingChange" name="meetingId" value="{{array[meeting_id].id}}"    range-key="title" range="{{array}}">
    <view class="meeting_text" >{{array[meeting_id].title==null?'请选择会议':array[meeting_id].title}}</view>
  </picker>
</view>
<view class="meeting_id" id="meeting_id">
  <view class="meeting_title">投票标题 : </view>
   <input class="vote_text" placeholder="请输入标题" type="text" name="name" />
</view>
<view class="textarea" id="vote_text">
  <view class="meeting_textarea">投票说明 : </view>
   <textarea class="vote_textarea"  name="remark"  type="text" ></textarea>
</view>
<view class="vote_button">
  <button class="vote_button_empty" type="default" formType="reset"  plain="true">清空内容</button>
  <view style="width: 70rpx;"></view>
  <button class="vote_button_submit" type="primary" formType="submit" plain="true">确认发起</button>
</view>
</form>
</view>
</view>
  <view class="{{componentStatus[1] ? '' : 'hidden'}}">
    <van-notice-bar
  left-icon="flower-o"
  mode="closeable"
  color="#6d9d9e"
  delay="100"
  backgroundColor="#e4ecec"
  text="无论我们能活多久,我们能够享受的只有无法分割的此刻,不会回头,离弦的箭、逝去的生活和失去的机会。此外别无其他。"
/>
<!-- 已参与投票 -->
<block wx:for-items="{{engage}}" wx:for-item="item" wx:key="item.id">
<view class="vote_carryout">
<image class="vote_carryout_img" src="{{item.images}}"></image>
<view class="vote_carryout_text">
<view style="width: 450rpx;display: flex;flex-direction:column;">
<text class="vote_carryout_text_f" >{{item.name}}</text>
<text class="vote_carryout_text_t">{{item.remark}}</text>
</view>
<button class="vote_carryout_text_btn" bindtap="voteparticipate"  data-item="{{item.id}}"  size="mini">参与</button>
</view>
</view>
</block>
  </view>
  <view class="{{componentStatus[2] ? '' : 'hidden'}}">
  
    <van-notice-bar
  left-icon="flower-o"
  mode="closeable"
  color="#6d9d9e"
  delay="100"
  backgroundColor="#e4ecec"
  text="无论我们能活多久,我们能够享受的只有无法分割的此刻,不会回头,离弦的箭、逝去的生活和失去的机会。此外别无其他。"
/>
<!-- 未参与投票 -->
<block wx:for-items="{{not}}" wx:for-item="item" wx:key="item.id">
<view class="vote_carryout">
<image class="vote_carryout_img" src="{{item.images==null?'/static/persons/15.gif':item.images}}"></image>
<view class="vote_carryout_text">
<view style="width: 450rpx;display: flex;flex-direction:column;">
<text class="vote_carryout_text_f" >{{item.name}}</text>
<text class="vote_carryout_text_t">{{item.remark}}</text>
</view>
<button class="vote_carryout_text_btn" bindtap="Votenotbtn"  size="mini">已参与</button>
</view>
</view>
</block>
  
  </view>
  <view class="{{componentStatus[3] ? '' : 'hidden'}}">
  
    <van-notice-bar
  left-icon="flower-o"
  mode="closeable"
  color="#6d9d9e"
  delay="100"
  backgroundColor="#e4ecec"
  text="无论我们能活多久,我们能够享受的只有无法分割的此刻,不会回头,离弦的箭、逝去的生活和失去的机会。此外别无其他。"
/>
<!-- 未参与投票 -->
<block wx:for-items="{{lists}}" wx:for-item="item" wx:key="item.id">
<view class="vote_carryout">
<image class="vote_carryout_img" src="{{item.images==null?'/static/persons/15.gif':item.images}}"></image>
<view class="vote_carryout_text">
<view style="width: 450rpx;display: flex;flex-direction:column;">
<text class="vote_carryout_text_f" >{{item.name}}</text>
<text class="vote_carryout_text_t">{{item.remark}}</text>
</view>
<button class="vote_carryout_text_btn"  size="mini">{{item.state==0?'未参与':'已参与'}}</button>
</view>
</view>
</block>
 
  </view>
4. 页面美化
list.wxss
/* pages/vote/list/list.wxss */
.hidden {
  display: none;
}
.img {
  height: 450rpx;
  /* display: flex; */
  /* border-radius: 6px; */
  /* align-items: center; */
}
.imgs {
  height: 420rpx;
  width: 419rpx;
  margin-left: 105rpx;
  box-shadow: 0 0 20px rgb(117, 241, 241);
  border-radius: 10rpx;
}
.img_left {
  margin-top: 28rpx;
  height: 450rpx;
  width: 600rpx;
  margin-left: 57rpx;
  /* border-radius: 23rpx; */
  /* background-color: deeppink; */
}
.publish {
  width: 100%;
}
.meeting_id {
  height: 100rpx;
  background-color: rgb(230, 243, 243);
  display: flex;
  /* border-radius: 6px; */
  align-items: center;
}
.meeting_title {
  margin-left: 20rpx;
}
.meeting_text {
  /* background-color: aqua; */
  margin-left: 20rpx;
  margin-top: 10rpx;
  padding-left: 20rpx;
  font-size: 14px;
  height: 50rpx;
  width: 520rpx;
}
#meeting_id {
  border-top: 2px solid #fafafa;
}
#vote_text {
  display: flex;
  align-items: center;
}
.vote_text {
  margin-left: 35rpx;
}
.meeting_textarea {
  margin-left: 20rpx;
  width: 440rpx;
}
.vote_textarea {
  height: 160rpx;
  width: 490px;
  padding-right: 10rpx;
  padding-top: 10rpx;
  padding-left: 10rpx;
  border: 2px solid #c0f0f1;
  border-radius: 20rpx;
}
.textarea {
  border-top: 2px solid #fafafa;
  background-color: rgb(238, 247, 247);
}
.vote_button {
  border-top: 4px solid #fafafa;
  display: flex;
  width: 80%;
  align-items: center;
  margin-left: 73rpx;
}
.vote_carryout{
  background-color: rgba(150, 250, 250, 0.315);
  box-shadow: 0 0 20px rgb(117, 241, 241);
  margin: 20rpx 30rpx 0rpx 70rpx;
  border-radius: 20rpx;
  height: 460rpx;
  width: 620rpx;
}
.vote_carryout_img{ 
  height: 350rpx;
  width: 620rpx;
  border-radius: 20rpx;
}
.vote_carryout_text{
display: flex;
height: 100rpx;
border-radius: 20rpx;
}
.vote_carryout_text_f{
  margin-left: 50rpx;
  height: 40rpx;
  font-size: 12px;
}
.vote_carryout_text_t{
  margin-left: 50rpx;
  height: 40rpx;
  color: rgba(116, 114, 114, 0.89);
  font-size: 12px;
}
.vote_carryout_text_btn{
  margin-top: 10rpx;
  height: 80rpx;
  font-size: 12px;
  size: 12px;
  background-color: rgb(174, 249, 252);
}5. 数据
list.js
// pages/vote/list/list.js
var app = getApp();
const api = require('../../../config/api');
const util = require('../../../utils/util.js');
Page({
  /**
   * 页面的初始数据
   */
  data: {
    tabs: ['发起投票', '未参与', '已参与', '全部投票'],//顶部导航栏
    componentStatus: [true, false, false, false],//用于处理内容显示
    array: ['美国', '中国', '巴西', '日本'],
    engage:[],//未参与的投票
    not:[],//已参与的投票
    lists:[],//全部投票信息
    meeting_id: '请选择会议',
    imageUrl: '',//图片路径
    votename: '123'
  },
  //选择所属会议的事件
  meetingChange(e) {
    // console.log(e.detail.value) 
    this.setData({
      meeting_id: e.detail.value
    })
  },
  //初始会议信息
  meetingini() {
    util.request(api.MettingVote).then(res => {
      this.setData({
        array: res.data.infoList
      })
    }).catch(res => {
      console.log('服器没有开启,使用模拟数据!')
    })
  },
  //初始化未参与的投票
  Voteiniengage() {
    util.request(api.MettingVoteList,{state:0}).then(res => {
      // console.log(res);
      this.setData({
        engage: res.data
      })
    }).catch(res => {
      console.log('服器没有开启,使用模拟数据!')
    })
  },
    //初始化已参与的投票
    Voteininot() {
      util.request(api.MettingVoteList,{state:1}).then(res => {
        // console.log(res);
        this.setData({
          not: res.data
        })
      }).catch(res => {
        console.log('服器没有开启,使用模拟数据!')
      })
    },
    //初始化全部投票信息
    VoteiniList() {
      util.request(api.MettingVoteList).then(res => {
        // console.log(res);
        this.setData({
          lists: res.data
        })
      }).catch(res => {
        console.log('服器没有开启,使用模拟数据!')
      })
    },
    //参与投票的点击事件
    voteparticipate(id){
      // console.log(id.target.dataset.item)
      wx.showModal({
        title: '提示',
        content: '你是否要贡献出你宝贵的一票?',
        success: function (res) {
          if (res.confirm) {
            //这里是点击了确定以后
            util.request(api.MettingVoteupdate, {id:id.target.dataset.item}).then(res => {
              // console.log(res)
              if (res.errno == 0) {
                wx.showToast({
                  title: '成功投票!!',
                  icon: 'none',
                  duration: 1000//持续的时间
                })
              }
            }).catch(res => {
              console.log('服器没有开启,发布失败')
            })
          } else {//这里是点击了取消以后
            console.log('用户点击取消')
          }
        }
      })
    },
  //发起投票的事件
  votesubmit(data) {
    console.log(data.detail.value)
    wx.showModal({
      title: '提示',
      content: '确定发起该投票吗?',
      success: function (res) {
        if (res.confirm) {
          //这里是点击了确定以后
          util.request(api.MettingVoteAdd, data.detail.value).then(res => {
            // console.log(res)
            if (res.errno == 0) {
              wx.showToast({
                title: '发布投票成功',
                icon: 'none',
                duration: 1500//持续的时间
              })
            }
          }).catch(res => {
            console.log('服器没有开启,发布失败')
          })
        } else {//这里是点击了取消以后
          console.log('用户点击取消')
        }
      }
    })
  },
  //已参与按钮的点击事件
  Votenotbtn() {
    wx.showToast({
      title: '已进行投票,不可再投',
      icon: 'none',
      duration: 1000//持续的时间
    })
  },
  //图片选择器
  handleUploadImage() {
    wx.chooseImage({
      count: 1,
      success: (res) => {
        const imagePath = res.tempFilePaths[0];
        // 处理选择的图片路径
        // console.log('选择的图片路径:', imagePath);
        this.setData({
          imageUrl: imagePath // 更新图片路径,触发重新渲染
        });
      }
    });
  },
  /**
   * 生命周期函数--监听页面加载
   */
  onLoad(options) {
    this.meetingini();
    this.Voteiniengage();
    this.Voteininot();
    this.VoteiniList();
  },
  /**
   * 生命周期函数--监听页面初次渲染完成
   */
  onReady() {
  },
  /**
   * 生命周期函数--监听页面显示
   */
  onShow() {
  },
  /**
   * 生命周期函数--监听页面隐藏
   */
  onHide() {
  },
  /**
   * 生命周期函数--监听页面卸载
   */
  onUnload() {
  },
  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh() {
  },
  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom() {
  },
  /**
   * 用户点击右上角分享
   */
  onShareAppMessage() {
  },
  //点击导航栏进行切换下方内容
  tabsItemChange(e) {
    let index = e.detail.index;
    //全部的组件赋值为false
    const lists = [false, false, false, false];
    //将所点击的组件赋值为true
    lists[index] = true;
    this.setData({
      componentStatus: lists // 更新 data 中的 componentStatus 属性值
    });
  },
  // tabsItemChange(e){
  //     let index = e.detail.index;
  //     console.log('vote.index='+index)
  //     if(index==1 || index==2){
  //         if (app.globalData.hasLogin) {
  //         }else{
  //             wx.navigateTo({
  //               url: '/pages/auth/login/login',
  //             })
  //         }
  //     }
  // }
})6. 效果展示
发起投票

参与投票

查看所有投票











