投票创建页面实现
文件选择上传组件 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小图标
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"></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)"></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"></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 */;