第11讲投票创建后端实现

投票创建页面实现

文件选择上传组件 uni-file-picker 扩展组件 安装

https://ext.dcloud.net.cn/plugin?name=uni-file-picker

日期选择器uni-datetime-picker组件 安装

https://ext.dcloud.net.cn/plugin?name=uni-datetime-picker

iconfont小图标

https://www.iconfont.cn/

iconfont.css

c 复制代码
@font-face {
  font-family: 'iconfont';  /* Project id 3888696 */
  src: url('//at.alicdn.com/t/c/font_3888696_rjermxmgmb.woff2?t=1680049466852') format('woff2'),
       url('//at.alicdn.com/t/c/font_3888696_rjermxmgmb.woff?t=1680049466852') format('woff'),
       url('//at.alicdn.com/t/c/font_3888696_rjermxmgmb.ttf?t=1680049466852') format('truetype');
}

.share {
	font-family: iconfont;
	margin-left: 20rpx;
	font-size: 26rpx;
	color: blue;
}

.uploadImg{
	font-family: iconfont;
	font-size: 56rpx;
	color: #acacac;
}

.smallUploadImg{
	font-family: iconfont;
	font-size: 36rpx;
	color: #acacac;
}

.removeOption{
	font-family: iconfont;
	font-size: 38rpx;
	color: red;
	padding-right: 10px;
}

.addOption{
	font-family: iconfont;
	font-size: 38rpx;
	padding-right: 10px;
}

.chooseOption{
	font-family: iconfont;
	font-size: 26rpx;
}

.voteListItem{
	font-family: iconfont;
	font-size: 26rpx;
}

.voteManageItem{
	font-family: iconfont;
	font-size: 46rpx;
	color: blue;
	padding-bottom: 8px;
}

前端代码:

html 复制代码
<template>
	<view class="word_vote">
		<view class="cover_img">
			<view class="title_tip">
				<view class="cover">
					封面图(可以不上传)
				</view>
				<view class="tip">
					( 宽高比:650 × 300 )
				</view>
			</view>
			<view class="upload_img">
				<uni-file-picker 
				@select="selectCoverFileFunc($event)"
				:auto-upload="false" 
				limit="1"
				:del-icon="false" 
				disable-preview 
				file-mediatype="image" 
				:imageStyles="coverImageStyles">
					<view class="upload">
						<text class="uploadImg">&#xe727;</text>
					</view>
				</uni-file-picker>
			</view>
		</view>
		
		<view class="basic_settings">
			<view class="title_tip">
				<view class="title">
					基础设置
				</view>
			</view>
			<view class="settings">
				<view class="title">
					<input type="text"  v-model="title" placeholder="填写投票标题"  placeholder-style="color:#bababa;font-size:16px"/>
				</view>
				<view class="explanation">
					<textarea v-model="explanation" placeholder="投票说明 (非必填)" placeholder-style="color:#bababa;font-size:14px"></textarea>
				</view>
			</view>
		</view>
		
		<view class="vote_options_settings">
			<view class="title_tip">
				<view class="title">
					投票选项设置
				</view>
			</view>
			<view class="option_list">
				<view class="option_item" v-for="(item,index) in options" :key="item.id">
					<text class="removeOption" @click="removeOption(item.id)">&#xe618;</text><input type="text" v-model="item.name" placeholder="输入选项名称" placeholder-style="color:#bababa;font-size:14px">
				</view>
			</view>
			<view class="option_add" @click="addOption()">
				<text class="addOption">&#xe660;</text><text>添加选项</text>
			</view>
		</view>
		
		<view class="vote_rules_settings">
			<view class="title_tip">
				<view class="title">
					投票规则设置
				</view>
			</view>	
			<view class="rule_list">
				<view class="rule_item">
					<text>投票截止时间</text>
					<view >
						<uni-datetime-picker 
							:border="false" 
							:clear-icon="false" 
							v-model="voteEndTime"
							:start="startDate"
							:end="endDate"
							></uni-datetime-picker>
					</view>
				</view>
			</view>
		</view>
	</view>
	
	<view class="vote_btn" >
		<button type="primary" @click="submitVote">发起投票</button>
	</view>
</template>

<script>
	import {getBaseUrl, requestUtil} from "../../utils/requestUtil.js"
	import {isEmpty} from "../../utils/stringUtil.js"
	import {timeFormat} from "../../utils/dateUtil.js"
	export default{
		data(){
			const curDate=new Date();
			const vv=new Date(curDate.getTime()+24*60*60*1000);
			return{
				title:'',
				explanation:'',
				coverImageFileName:'',
				coverImageStyles: {
					width:"700rpx",
					height:"400rpx",
					border:false
				},
				voteEndTime:timeFormat(vv),
				options:[
					{
						id:1,
						name:''
					},
					{
						id:2,
						name:''
					}
				]
			}
		},
		computed:{
			startDate(){
				return new Date();
			},
			endDate(){
				const curDate=new Date();
				const vv=new Date(curDate.getTime()+24*60*60*1000*365);
				return vv;
			}
		},
		methods:{
			addOption:function(){
				var option={
					id:this.options[this.options.length-1].id+1,
					name:''
				}
				this.options.push(option);
			},
			removeOption:function(id){
				const index=this.options.findIndex(v=>v.id===id)
				this.options.splice(index,1);
			},
			selectCoverFileFunc:function(e){
				console.log(e.tempFilePaths[0])
				uni.uploadFile({
					header:{token:uni.getStorageSync("token")},
					url:getBaseUrl()+"/vote/uploadCoverImage",
					filePath:e.tempFilePaths[0],
					name:"coverImage",
					success: (res) => {
						let result=JSON.parse(res.data);
						if(result.code==0){
							this.coverImageFileName=result.coverImageFileName;
						}
					}
				})
			}
		}
	}
</script>

<style lang="scss">
	@import "/common/css/iconfont.css";
	.word_vote{
		padding: 20px;
		padding-bottom: 70px;
		.cover_img{
			.title_tip{
				margin-left: 10rpx;
				font-size: 26rpx;
				color: gray;
				display: flex;
				justify-content: space-between;
			}
			.upload_img{
				border-radius: 5px;
				margin-top: 20rpx;
				width:100%;
				height: 360rpx;
				background-color: white;
				display: flex;
				align-items: center;
				justify-content: center;
				.upload{
					margin: 10rpx;
					background-color: #f4f5f7;
					width:90%;
					height: 80%;
					display: flex;
					align-items: center;
					justify-content: center;
				}
			}
		}
		
		.basic_settings{
			margin-top: 20px;
			.title_tip{
				margin-left: 10rpx;
				font-size: 26rpx;
				color: gray;
				margin-bottom: 10px;
				.title{
					
				}
			}
			.settings{
				
				border-radius: 5px;
				background-color: white;
				.title{
					padding: 10px;
					input{
						font-size: 1.3rem;
						border-bottom: 1px solid #e4e4e4;
						padding-bottom: 15px;
					}
				}
				.explanation{
					padding: 10px;
					textarea{
						height: 100px;
					}
				}
			}
			
		}
		
		.vote_options_settings{
			margin-top: 20px;
			.title_tip{
				margin-left: 10rpx;
				font-size: 26rpx;
				color: gray;
				margin-bottom: 10px;
				.title{
					
				}
			}
			.option_list{
				.option_item{
					margin-top: 10px;
					border-radius: 5px;
					background-color: white;
					padding: 10px;
					display: flex;
					
				}
			}
			.option_add{
				margin-top: 10px;
				border-radius: 5px;
				background-color: white;
				padding: 10px;
				display: flex;
				color:blue;
				font-size:14px
			}
		}
		
		.vote_rules_settings{
			margin-top: 20px;
			.title_tip{
				margin-left: 10rpx;
				font-size: 26rpx;
				color: gray;
				margin-bottom: 10px;
				.title{
					
				}
			}
			.rule_list{
				border-radius: 5px;
				background-color: white;
				.rule_item{
					display: flex;
					justify-content: space-between;
					padding: 12px;
					border-bottom: 1px solid #e4e4e4;
					font-size: 28rpx;
					align-items: center;
					height: 45rpx;
				}
			}
		}
		
	}
	
	.vote_btn{
		height: 120rpx;
		width: 100%;
		background-color: white;
		position: fixed;
		bottom: 0;
		border-top: 1px solid #e4e4e4;
		button{
			margin: 10px;
		}
	}
</style>

后端:

bash 复制代码
coverImagesFilePath: D://uniapp/coverImgs/

封面上传:

bash 复制代码
/**
 * 上传封面图片
 * @param coverImage
 * @return
 * @throws Exception
 */
@RequestMapping("/uploadCoverImage")
public Map<String,Object> uploadCoverImage(MultipartFile coverImage)throws Exception{
    System.out.println("filename:"+coverImage.getName());
    Map<String,Object> resultMap=new HashMap<>();
    if(!coverImage.isEmpty()){
        // 获取文件名
        String originalFilename = coverImage.getOriginalFilename();
        String suffixName=originalFilename.substring(originalFilename.lastIndexOf("."));
        String newFileName= DateUtil.getCurrentDateStr()+suffixName;
        FileUtils.copyInputStreamToFile(coverImage.getInputStream(),new File(coverImagesFilePath+newFileName));
        resultMap.put("code",0);
        resultMap.put("msg","上传成功");
        resultMap.put("coverImageFileName",newFileName);
    }
    return resultMap;
}

vote

bash 复制代码
package com.java1234.entity;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import lombok.Data;

import java.util.Date;
import java.util.List;

/**
 * 投票实体
 * @author java1234_小锋 (公众号:java1234)
 * @site www.java1234.vip
 * @company 南通小锋网络科技有限公司
 */
@TableName("t_vote")
@Data
public class Vote {

    private Integer id; // 编号

    private String title; // 标题

    private String explanation; // 投票说明

    private String coverImage; // 封面图片

    @JsonSerialize(using=CustomDateTimeSerializer.class)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date voteEndTime; // 投票结束时间

    private String openid; // 投票发起人openid

    @TableField(select=false,exist = false)
    private List<VoteItem> voteItemList;

    @TableField(select=false,exist = false)
    private WxUserInfo wxUserInfo;

    private Integer type=1; // 1 文字投票  2 图片投票

}

VoteMapper

bash 复制代码
package com.java1234.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.java1234.entity.Vote;

/**
 * 投票Mapper接口
 */
public interface VoteMapper extends BaseMapper<Vote> {
}

VoteItemMapper

bash 复制代码
package com.java1234.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.java1234.entity.VoteItem;

/**
 * 投票选项Mapper接口
 */
public interface VoteItemMapper extends BaseMapper<VoteItem> {
}

IVoteItemService

bash 复制代码
package com.java1234.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.java1234.entity.VoteItem;

/**
 * 投票选项Service接口
 */
public interface IVoteItemService extends IService<VoteItem> {
}

IVoteService

bash 复制代码
package com.java1234.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.java1234.entity.Vote;

/**
 * 投票Service接口
 */
public interface IVoteService extends IService<Vote> {
}

IVoteItemServiceImpl

bash 复制代码
package com.java1234.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.java1234.entity.VoteItem;
import com.java1234.mapper.VoteItemMapper;
import com.java1234.service.IVoteItemService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;


/**
 * 投票选项Service实现类
 * @author java1234_小锋 (公众号:java1234)
 * @site www.java1234.vip
 * @company 南通小锋网络科技有限公司
 */
@Service("voteItemService")
public class IVoteItemServiceImpl extends ServiceImpl<VoteItemMapper,VoteItem> implements IVoteItemService {

    @Autowired
    private VoteItemMapper voteItemMapper;

}

IVoteServiceImpl

bash 复制代码
package com.java1234.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.java1234.entity.Vote;
import com.java1234.mapper.VoteMapper;
import com.java1234.service.IVoteService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * 投票Service实现类
 * @author java1234_小锋 (公众号:java1234)
 * @site www.java1234.vip
 * @company 南通小锋网络科技有限公司
 */
@Service("voteService")
public class IVoteServiceImpl extends ServiceImpl<VoteMapper, Vote> implements IVoteService {

    @Autowired
    private VoteMapper voteMapper;
}

VoteController

bash 复制代码
package com.java1234.controller;

import com.java1234.entity.R;
import com.java1234.entity.Vote;
import com.java1234.entity.VoteItem;
import com.java1234.service.IVoteItemService;
import com.java1234.service.IVoteService;
import com.java1234.util.DateUtil;
import com.java1234.util.JwtUtils;
import io.jsonwebtoken.Claims;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 投票Controller控制器
 * @author java1234_小锋 (公众号:java1234)
 * @site www.java1234.vip
 * @company 南通小锋网络科技有限公司
 */
@RestController
@RequestMapping("/vote")
public class VoteController {

    @Value("${coverImagesFilePath}")
    private String coverImagesFilePath;

    @Autowired
    private IVoteService voteService;

    @Autowired
    private IVoteItemService voteItemService;

    /**
     * 上传封面图片
     * @param coverImage
     * @return
     * @throws Exception
     */
    @RequestMapping("/uploadCoverImage")
    public Map<String,Object> uploadCoverImage(MultipartFile coverImage)throws Exception{
        System.out.println("filename:"+coverImage.getName());
        Map<String,Object> resultMap=new HashMap<>();
        if(!coverImage.isEmpty()){
            // 获取文件名
            String originalFilename = coverImage.getOriginalFilename();
            String suffixName=originalFilename.substring(originalFilename.lastIndexOf("."));
            String newFileName= DateUtil.getCurrentDateStr()+suffixName;
            FileUtils.copyInputStreamToFile(coverImage.getInputStream(),new File(coverImagesFilePath+newFileName));
            resultMap.put("code",0);
            resultMap.put("msg","上传成功");
            resultMap.put("coverImageFileName",newFileName);
        }
        return resultMap;
    }

    /**
     * 添加投票
     * @param vote
     * @param token
     * @return
     */
    @RequestMapping("/add")
    @Transactional
    public R add(@RequestBody Vote vote, @RequestHeader String token){
        Claims claims = JwtUtils.validateJWT(token).getClaims();
        vote.setOpenid(claims.getId());
        voteService.save(vote);
        List<VoteItem> voteItemList = vote.getVoteItemList();
        for(VoteItem voteItem:voteItemList){
            voteItem.setVoteId(vote.getId());
            voteItem.setNumber(0);
            voteItemService.save(voteItem);
        }
        return R.ok();
    }
}

前端验证以及提交:

javascript 复制代码
submitVote:async function(e){
    // 验证
    if(isEmpty(this.title)){
        uni.showToast({
            icon:"error",
            title:"请填写投票标题"
        })
        return;
    }
    // 投票选项片段 至少2个选项
    let resultOptions=this.options.filter(function(value,index,self){  // 过滤掉名称为空的投票选项
        console.log("value="+value.name)
        return !isEmpty(value.name)
    })
    console.log("xx"+JSON.stringify(resultOptions));
    console.log("length="+resultOptions.length)
    if(resultOptions.length<2){
        uni.showToast({
            icon:"error",
            title:"请至少填写两个投票选项"
        })
        return;
    }
    // 提交表单
    let form={
        title:this.title,
        coverImage:this.coverImageFileName,
        explanation:this.explanation,
        voteEndTime:this.voteEndTime,
        voteItemList:resultOptions,
        type:1
    }
    const result=await requestUtil({url:"/vote/add",data:form,method:"post"});
    if(result.code==0){
        console.log("发布成功")
        uni.showToast({
            icon:"success",
            title:"投票发起成功!"
        })
    }
}

dateUtil.js

bash 复制代码
// 判断传入日期是否和当前日期比较 
 const judgeDate=(toDate)=>{
	return new Date().getTime()-new Date(toDate).getTime();
}


var timeFormat = function (msTime) {
    let time = new Date(msTime);
    let yy = time.getFullYear();
    let MM = time.getMonth() + 1;
    let dd = time.getDate();
    let hh = time.getHours() < 10 ? "0" + time.getHours() : time.getHours();
    let min =
        time.getMinutes() < 10 ? "0" + time.getMinutes() : time.getMinutes();
    let sec =
        time.getSeconds() < 10 ? "0" + time.getSeconds() : time.getSeconds();
    return yy + "-" + MM + "-" + dd + " " + hh + ":" + min + ":" + sec;
}

export {timeFormat,judgeDate}

数据库

bash 复制代码
/*
SQLyog Ultimate v11.33 (64 bit)
MySQL - 5.7.18-log : Database - db_vote3
*********************************************************************
*/


/*!40101 SET NAMES utf8 */;

/*!40101 SET SQL_MODE=''*/;

/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
CREATE DATABASE /*!32312 IF NOT EXISTS*/`db_vote3` /*!40100 DEFAULT CHARACTER SET utf8 */;

USE `db_vote3`;

/*Table structure for table `t_vote` */

DROP TABLE IF EXISTS `t_vote`;

CREATE TABLE `t_vote` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(600) DEFAULT NULL,
  `explanation` varchar(3000) DEFAULT NULL,
  `cover_image` varchar(600) DEFAULT NULL,
  `vote_end_time` datetime DEFAULT NULL,
  `openid` varchar(600) DEFAULT NULL,
  `type` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

/*Data for the table `t_vote` */

insert  into `t_vote`(`id`,`title`,`explanation`,`cover_image`,`vote_end_time`,`openid`,`type`) values (1,'2','2','20230516092542000000157.jpg','2023-05-17 09:25:33','o30ur5JpAsAUyGBkR0uW4IxvahR8',1);

/*Table structure for table `t_vote_item` */

DROP TABLE IF EXISTS `t_vote_item`;

CREATE TABLE `t_vote_item` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `vote_id` int(11) DEFAULT NULL,
  `name` varchar(600) DEFAULT NULL,
  `image` varchar(600) DEFAULT NULL,
  `number` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

/*Data for the table `t_vote_item` */

insert  into `t_vote_item`(`id`,`vote_id`,`name`,`image`,`number`) values (1,1,'1',NULL,0),(2,1,'2',NULL,0);

/*Table structure for table `t_wxuserinfo` */

DROP TABLE IF EXISTS `t_wxuserinfo`;

CREATE TABLE `t_wxuserinfo` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `openid` varchar(90) DEFAULT NULL,
  `nick_name` varchar(150) DEFAULT NULL,
  `avatar_url` varchar(600) DEFAULT NULL,
  `register_date` datetime DEFAULT NULL,
  `last_login_date` datetime DEFAULT NULL,
  `status` char(3) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8;

/*Data for the table `t_wxuserinfo` */

insert  into `t_wxuserinfo`(`id`,`openid`,`nick_name`,`avatar_url`,`register_date`,`last_login_date`,`status`) values (7,'o30ur5PiPOr52bBsetXcIV93NL-U','小锋四号@java1234','20230410102248000000487.jpg','2023-04-10 10:21:30','2023-05-04 21:20:38','0'),(9,'o30ur5JpAsAUyGBkR0uW4IxvahR0','微信用户','default.png','2023-04-30 07:42:19','2023-04-30 07:42:19','1'),(10,'1',NULL,NULL,NULL,NULL,'1'),(11,'2',NULL,NULL,NULL,NULL,'1'),(12,'3',NULL,NULL,NULL,NULL,'1'),(13,'4',NULL,NULL,NULL,NULL,'1'),(14,'5',NULL,NULL,NULL,NULL,'1'),(15,'6',NULL,NULL,NULL,NULL,'1'),(16,'7',NULL,NULL,NULL,NULL,'1'),(17,'8',NULL,NULL,NULL,NULL,'1'),(19,'o30ur5JpAsAUyGBkR0uW4IxvahR8','小锋111@java1234','20230514112056000000757.jpeg','2023-05-11 08:44:11','2023-05-16 09:25:31','0');

/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
相关推荐
譕痕5 天前
JSONObject与JSONArray封装数据格式区别
java·json
A黄俊辉A5 天前
uniapp webview中实现 app和内嵌的H5双向通信
vue.js·json
MoonMoonLee5 天前
小程序配置
小程序
微信开发api5 天前
微信iPad协议怎么用?基于协议的二次开发实践
微信·机器人·ipad
wechatbot8885 天前
极客互动企业微信 SCRM 自动运营平台效果实测
java·微信·企业微信·rpa
懂软件的胡子个哥5 天前
微信机器人为什么需要“回复候选”而不是所有 AI 内容直接发送
运维·微信·自动化·wechatapi·个人微信号二次开发
2501_916007475 天前
苹果商店iOS应用上架费用全面解析:开发者计划与审核费用计算
android·ios·小程序·https·uni-app·iphone·webview
伞伞悦读5 天前
【第37期】Python JSON 与配置详解:序列化、反序列化、嵌套结构和配置文件
开发语言·python·json
JQweryuiop5 天前
中小型游戏陪练工作室数字化管理实践:基于七彩屋电竞护航小程序订单、履约与绩效系统的落地复盘
大数据·游戏·小程序·架构
Mikko75 天前
jackson-databind 升到 2.21.6 就安全了吗?jackson-core 是另一个坐标,它那条 high 全局库至今没收
java·后端·安全·json