项目功能演示视频地址:
https://www.bilibili.com/video/BV1754y1q7cv (本项目的功能都在这里有介绍呀,小编自制,比较简陋,喜欢的博友可以点击看看,点个赞呀!)
项目简介和背景
在农村里的独居老人,常年儿女不在家、身体日渐消瘦的情况下,买生活用品、买药成为了困难事,在城镇化的影响下,农村里有越来越多的独居老人,但却少有相应的志愿平台可以为独居老人带来温暖。Hener App顺势而生!线上报名、线下活动,操作简单,Hener让志愿服务开展在我们的身边,为老人带来福音!
项目创新点
- 对志愿者而言,可以随时随地报名志愿活动,即使报名人数满了,只要在活动未结束之前仍可成为参与者参与活动,志愿时数根据志愿者参与的时间实时统计。
- 对村委而言,可以实时审核志愿者或参与者提交的活动申请,通过富文本编辑器编辑发布活动,编辑发布相关村的通告,让志愿者们清晰了解活动详情,村里动态,导入导出轻松管理老人信息,同时也可以导出志愿者个人志愿时数作为志愿活动证明凭据。
- 对管理员而言,一键点击审核按钮改变志愿者和村委的注册审核状态即可,同时管理一切后台权限。
技术栈
客户端技术栈:vue-cli3+Vant UI
服务端技术栈:koa2+mysql+sequelize
核心业务功能(共有三个角色)
角色:志愿者
- 登录、注册
- 查看审核结果
- 浏览公告、注意事项
- 查看活动详情、报名/参与活动、取消报名/参与
- 浏览通告、点赞、取消点赞
- 编辑个人资料、修改密码、查看活动审核进度/详情
角色:村委
- 活动管理(增删改查、分页查询)
- 活动审核(分页搜索、删除)
- 老人管理(增删改查、导入导出)
- 通告管理(增删改查)
角色:管理员
- 管理、审核用户(志愿者和村委)、上传头像
- 管理角色(增删改查)
安全要求
- 所有角色必须通过身份验证
前端树组件
核心组件图
1.志愿者-首页
2.志愿者模块
3.参与者模块
4.通告
展示通告详情、点赞、取消点赞
5.我的
6.审核记录
全部/未审核/已审核/审核失败—按状态搜索
方法:watch+filter+includes+swich-case
根据不同的状态,展示不同的进度条
方法:switch-case语句
7.注意事项和通告
方法:Vuex异步读取数据+后台使用fs模块读取json文件,再把json文件转换为对象JSON.parse()
核心技术Vuex
目录文件
index.js 通过modules分为两个模块Volunteer.js(志愿者页面) 和 Committee.js (村委页面)
通过mutations动态设置
1.Header标题不同的模块设置不同的标题
2.动态设置底部路由,志愿者登录进入志愿者端,村委登录进入村委端
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
//导入committee和volunteer
import committeeModule from '@/store/committee.js'
import volunteerModule from '@/store/volunteer.js'
//存放共有数据
var state = {
leftArrow:false,//左上角返回按钮,默认不显示(首页,通告,我的)
indexPage:'/volunteer', //首页的uri,默认为志愿者页面
announcePage:'/volunteer/announce', //通告的uri,默认志愿者节目
personalPage:'/volunteer/personal', //我的uri,默认志愿者节目
title:'首页' //标题
}
var mutations = {
setLeftArrow(state,val) { //是否显示返回按钮
state.leftArrow = val
},
setIndexPage(state,uri) { //更换首页uri
state.indexPage = uri
},
setAnnouncePage(state,uri) { //更换通告的uri
state.announcePage = uri
},
setPersonalPage(state,uri) { //更换我的uri
state.personalPage = uri
},
setTitle(state, val) { //设置当前标题
state.title = val
}
}
var actions = {}
//各自数据
var modules = {
volunteer:volunteerModule,
committee: committeeModule
}
var vuex = new Vuex.Store({
state,mutations,actions,modules
})
export default vuex
volunteer.js
Mutations+Actions: 编写异步方法调用接口,在组件中,通过 this.$store.dispatch()在created()初始化调用,在相应的 methods调用this.$store.state.volunteer获取数据
import api from '@/api/user/index'
import { Toast } from 'vant'
var state = {
message: { //用户信息
account: '获取中...',
user_name: '获取中...',
role: '获取中...',
user_photo:'获取中...',
user_sex:'获取中...',
user_village:'获取中...'
},
announcesCol:[], //返回通告列表--升序
announces: [], //返回通告列表--降序
currentannounce:{},//返回当前通告
activitys: [], //获取活动老人信息--参与者模块
isUpload:{}, //获取活动提交的状态值
activityList:[], //获取一个用户所提交的所有活动
activityNotive:[], //获取活动公告
attentions:[], //获取志愿者注意事项
approval:{} //获取当前通告点赞情况
}
var mutations = {
setVolunteerMessage(state, msg) { //设置志愿者信息
state.message = msg
},
vm_getAnnouncesCol:function(state,val){ //获取所有通告--升序
state.announcesCol = val
},
vm_getAnnounces: function (state, val) { //获取所有通告--降序
state.announces = val
},
vm_getCurrentAnnounce:function(state, val) { //获取当前通告
state.currentannounce = val
},
vm_getActivity:function(state,val) { //获取活动老人信息--参与者模块
state.activitys = val
},
vm_getIsUpload:function(state,val) {
state.isUpload = val
},
vm_getOneUserAllActivity:function(state,val) { //获取一个用户所提交的所有活动
state.activityList = val
},
vm_getActivityNotive: function(state,val) { //获取活动公告
state.activityNotive = val
},
vm_getAttention: function(state,val) { //获取志愿者注意事项
state.attentions = val
},
vm_getOneApproval: function(state,val) { //获取当前通告点赞情况
state.approval = val
}
}
var actions = {
//获取通告列表--升序
va_getAnnouncesCol:function({commit}) {
api.getannouncescol().then(res => {
commit('vm_getAnnouncesCol', res.announces)
})
},
//获取通告列表--降序
va_getAnnounces: function ({commit}) {
api.getannounces().then(res => {
commit('vm_getAnnounces', res.announces)
})
},
//获取当前通告
va_getCurrentAnnounce:function ({commit}, params) {
api.getcurrentannounce(params).then(res => {
commit('vm_getCurrentAnnounce',res.announce)
})
},
//获取活动与老人信息
va_getActivity:function({commit}, params) {
api.getallactivity().then(res => {
commit('vm_getActivity', res.activities)
})
},
//参与活动
va_postParticipantActivity:function({commit}, params) {
api.postparticipantactivity(params).then(res => {
commit('vm_getIsUpload', res)
if(res.code == -1){
Toast(res.message);
}else if(res.code == 1){
Toast(res.message);
}
})
},
//获取个人提交的未审核的活动--申请记录
va_getuseractivity:function({commit},params) {
api.getuseractivity(params).then(res => {
commit('vm_getOneUserAllActivity', res.activities)
})
},
//取消报名/参与活动
va_deleteapplicationactivity:function({commit},params) {
api.deleteapplicationactivity(params).then(res => {
commit('vm_getIsUpload', res)
if(res.code == 1){
Toast(res.message)
}
})
},
//获取活动公告
va_getactivitynotive:function({commit}) {
api.getactivitynotive().then(res => {
if(res.code == 1){
commit('vm_getActivityNotive', res.message.slice(0,3))
}
})
},
//获取志愿者注意事项
va_getattention:function({commit}) {
api.getattention().then(res => {
if(res.code == 1){
commit('vm_getAttention', res.message)
}
})
},
//点赞
va_postapproval:function({commit}, params) {
api.postapproval(params).then(res =>{
commit('vm_getOneApproval', res.approval)
if(res.code == 1){
Toast(res.message)
}
})
},
//获取当前通告点赞情况
va_getoneapproval:function({commit}, params) {
api.getoneapproval(params).then( res => {
if(res.code == 1) {
commit('vm_getOneApproval', res.approval)
}else if(res.code == -1) {
commit('vm_getOneApproval', res.approval)
}
})
},
//取消点赞
va_putapproval: function ({ commit }, params) {
api.putapproval(params).then(res => {
commit('vm_getOneApproval', res.approval)
if (res.code == 1) {
Toast(res.message)
}
})
},
}
export default {
state, mutations, actions
}
核心技术 vue-router
目录文件
index.js 通过modules分为两个模块Volunteer.js 和 Committee.js
index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
//导入志愿者、村委的路由配置器
import volunteer from './volunteer.js'
import committee from './committee.js'
import store from '@/store/index.js'
//导入其他路由
import loginIndex from '@/components/login/index.vue'
import login from '@/components/login/login.vue'
import register from '@/components/register/register.vue'
import verify from '@/components/verify/verify.vue'
import verifyResult from '@/components/verify/result.vue'
import verifyBack from '@/components/verify/verifyBack.vue'
import volunteerIndex from '@/components/volunteer/volunteer.vue'
import committeeIndex from '@/components/committee/committee.vue'
//导入错误处理组件
import error from '@/views/error/error.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/login',
component:loginIndex,
children:[
{ path: '/', component: login }
]
},
{
path: '/register',
component: register
},
{
path:'/verify',
component:verify
},
{
path:'/verify/result',
component:verifyResult
},
{
path:'/verify/verifyBack',
component:verifyBack
},
{
path:'/',
redirect: '/volunteer'
},
{
path:'/volunteer',
component:volunteerIndex,
children:volunteer
},
{
path: '/committee',
component: committeeIndex,
children:committee
},
{
path:'*', //错误处理
component: error
}
]
const router = new VueRouter({
routes,
mode:'history'
})
/*Vue-router在3.1之后把$router.push()方法改为了Promise。
所以假如没有回调函数,错误信息就会交给全局的路由错误处理。
*/
const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(location) {
return originalPush.call(this,location).catch(err => err)
}
export default router
volunteer.js
//整体框架组件
import index from '@/components/volunteer/index.vue'
import announce from '@/components/volunteer/announce.vue'
import personal from '@/components/volunteer/personal.vue'
//child组件
import userInfo from '@/components/volunteer/child/personal/userInfo.vue'
import password from '@/components/volunteer/child/personal/password.vue'
import matter from '@/components/volunteer/child/personal/matter.vue'
import application from '@/components/volunteer/child/personal/application.vue'
import attention from '@/components/volunteer/child/personal/attention.vue'
import hometown from '@/components/volunteer/child/index/hometown.vue'
import activityinfo from '@/components/volunteer/child/index/activityinfo.vue'
import announceinfo from '@/components/volunteer/child/announce/notiveInfo.vue'
import participant from '@/components/volunteer/child/index/participant.vue'
export default [
/**首页 通告 我的 */
{
path:'/',
component:index,
children:[
{
path:'hometown',
name:'hometown',
component:hometown,
children:[
{
path:'activityinfo',
name:'activityinfo',
component: activityinfo
}
]
},
{
path: 'activityinfo',
name: 'activityInfo',
component: activityinfo
},
{
path: 'participant',
name: 'participant',
component: participant
}
]
},
{
path:'announce',
component: announce,
children:[
{
path: 'announceinfo',
name: 'announceInfo',
component: announceinfo
}
]
},
{
path:'personal',
component: personal,
children:[
{
path:'userInfo',
name:'userInfo',
component:userInfo
},
{
path:'password',
name:'password',
component:password
},
{
path:'application',
name:'application',
component:application
},
{
path:'matter',
name:'matter',
component:matter
},
{
path:'attention',
name:'attention',
component: attention
}
]
}
]
核心技术 路由守卫permission.js
进入路由前的操作:
- 1.判断Token是否存在
- 2.防止刷新时Vuex数据丢失
- 3.不同身份进入不同页面
禁止访问其他身份页面
import { getLocalStorage } from '@/utils/localStorage'
import router from './router'
import store from '@/store/index.js'
import api from '@/api/apiList.js'
import { Toast } from '_vant@2.5.8@vant'
//判断用户登录
router.beforeEach((to, from, next) => {
if (to.path === '/login' || to.path === '/register' || to.path === '/verify' || to.path === '/verify/result' || to.path ==='/verify/verifyBack') { //登录注册页面不判断
next()
} else {
if (getLocalStorage('Token')) { //当token存在
//保证页面刷新时,始终可以拿到token,vuex保存的信息不丢失
api.user.userinfo().then(msg => {
if (msg.role == '村委') { //为村委时
//设置vuex个人村委信息
store.dispatch('ha_getCommittee', msg)
//更改底部菜单栏的uri
store.commit('setIndexPage', '/committee')
store.commit('setAnnouncePage', '/committee/announce')
store.commit('setPersonalPage', '/committee/personal')
if (to.path.includes('/volunteer')) { //禁止访问volunteer页面
Toast('禁止访问')
next('/committee')
} else {
next()
}
} else if (msg.role == '志愿者') {
store.commit('setVolunteerMessage', msg)
//更换底部菜单栏的uri
store.commit('setIndexPage', '/volunteer')
store.commit('setAnnouncePage', '/volunteer/announce')
store.commit('setPersonalPage', '/volunteer/personal')
if (to.path.includes('/committee')) { //禁止访问
Toast('禁止访问')
next('/volunteer')
} else {
next()
}
}
})
} else {//token不存在,重新登录
Toast('登录过期,请重新登录')
next('/login')
}
}
})
核心技术 Axios (二次封装)
axios.js 初始化和配置Axios,设置拦截器等
import axios from 'axios'
import {getLocalStorage} from '@/utils/localStorage'
import { Toast } from 'mint-ui'
//创建axios实例
let service = axios.create({
timeout:60000
})
//设置post、put默认Content-Type
service.defaults.headers.post['Content-Type'] = 'application/json'
service.defaults.headers.put['Content-Type'] = 'application/json'
//添加请求拦截器
service.interceptors.request.use(
(config) => {
//LocalStorage中获取token后设置请求头
config.headers.common['Authorization'] = getLocalStorage('Token')
return config
},
(error) => {
//请求错误处理
Toast('请求超时!')
return Promise.reject(error)
}
)
//添加响应拦截器
//response拦截器
service.interceptors.response.use(data=>{
if(data.status && data.status == 200 && data.data.status == 'error') {
Toast({message:data.data.msg});
return;
}
return data;
}, err=> {
//错误处理
if(err && err.response) {
switch (err.response.status) {
case 400: Toast('请求错误(400)');break;
case 401: Toast('未授权,请重新登录(401)');router.push('/login');break;
case 403: Toast('拒绝访问(403)');break;
case 404: Toast('请求出错(404)');break;
case 408: Toast('请求超时(408)');break;
case 500: Toast('服务器错误(500)');break;
case 501: Toast('服务未实现(501)');break;
case 502: Toast('网络错误(502)');break;
case 503: Toast('服务不可用(503)');break;
case 504: Toast('网络超时(504)');break;
case 505: Toast('HTTP版本不受支持(505)');break;
default: Toast(`连接出错(${err.response.status})!`);
}
}else {
Toast('连接服务器失败')
}
Toast({message:err.message})
return Promise.reject(err);
})
/**
* 创建统一封装过的axios实例
* @return {AxiosInstance}
*/
export default function() {
return service
}
index.js 封装get/post/put/delete方法
import axios from './axios'
let instance = axios()
export default {
get(url, params) {
return new Promise((resolve, reject) => {
instance.get(url, {
params: params
}).then(res => {
resolve(res.data);
}).catch(err => {
reject(err.data)
})
})
},
post(url, params) {
return new Promise((resolve, reject) => {
instance.post(url, params)
.then(res => {
resolve(res.data);
})
.catch(err => {
reject(err.data)
})
});
},
put(url,params) {
return new Promise((resolve,reject) => {
instance.put(url,params)
.then(res => {
resolve(res.data);
})
.catch(err => {
reject(err.data)
})
});
},
delete(url, params) {
return new Promise((resolve, reject) => {
instance.delete(url, params)
.then(res => {
resolve(res.data);
})
.catch(err => {
reject(err.data)
})
});
}
}
将axios注册到Vue实例
import apiList from './apiList'
const install = function (Vue) {
if (install.installed) return
install.installed = true
Object.defineProperties(Vue.prototype, {
$api: {
get() {
return apiList
},
enumerable: false, // 不可枚举
configurable: false // 不可重写
}
})
}
export default {
install
}
apiList.js 暴露user中的api
import user from './user'
export default {
user
}
/user/index.js Api方法
import api from '../index'
import urls from './urls'
export default {
//获取用户信息vuex保存
userinfo() {
return api.get(urls.userinfo)
},
//登录
postlogin(params) {
params={
account:params.account,
password:params.password
}
return api.post(urls.postlogin,params)
},
//注册
postregister(params) {
params = {
account: params.account,
password: params.password,
user_name: params.user_name,
user_phone: params.user_phone,
user_sex: params.user_sex,
user_village: params.user_village,
role_id: params.role_id,
user_photo: params.user_photo
}
return api.post(urls.postregister,params)
},
//查询审核结果
getresult(params){
params = {
account:params
}
return api.get(urls.getresult,params)
},
//修改密码
putpassword(params){
params = {
account:params.account,
oldpassword:params.oldpassword,
newpassword:params.newpassword,
repeatpassword:params.repeatpassword
}
return api.put(urls.putpassword,params)
},
//获取用户所有信息--编辑用户资料
getuserinfo(params){
params={
account:params
}
return api.get(urls.getuserinfo,params)
},
//更新用户信息
putuserinfo(params,account){
params = {
user_name:params.user_name,
user_phone:params.user_phone,
user_sex:params.user_sex,
user_village:params.user_village,
user_photo:params.user_photo,
account:account
}
return api.put(urls.putuserinfo,params)
},
//获取用户活动表信息
getuseractivity(params){
params = {
params
}
return api.get(urls.getuseractivity,params)
},
//获取所有活动--下拉刷新
getactivitylist(params){
params = {
pageSize:params.pageSize,
page:params.page
}
return api.get(urls.getactivitylist,params)
},
//获取当前村所有活动
getonehometownactivity(params){
params = {
activity_address:params
}
return api.get(urls.getonehometownactivity,params)
},
//获取当前活动
getoneactivity(params){
params = {
id: params
}
return api.get(urls.getoneactivity,params)
},
//获取所有村活动--志愿者模块
getallactivity(){
return api.get(urls.getallactivity)
},
//报名活动
postenrollactivity(params){
params = {
account:params.account,
activity_id:params.id
}
return api.post(urls.postenrollactivity,params)
},
//获取所有通告--降序
getannounces(){
return api.get(urls.getannounces);
},
//获取所有通告--升序
getannouncescol(){
return api.get(urls.getannouncescol);
},
//获取当前通告--通告详情
getcurrentannounce(params){
params = {
id: params
}
return api.get(urls.getcurrentannounce,params);
},
//报名参与活动--参与者
postparticipantactivity(params){
params = {
account:params.account,
activity_id: params.activity_id,
currenttime:params.nowtime
}
return api.post(urls.postparticipantactivity,params)
},
//取消报名/参与活动
deleteapplicationactivity(params){
params = {
account:params.account,
activity_id:params.activity_id
}
return api.delete(urls.deleteapplicationactivity,{params})
},
//获取活动公告
getactivitynotive(){
return api.get(urls.getactivitynotive)
},
//获取志愿者注意事项
getattention(){
return api.get(urls.getattention)
},
//点赞
postapproval(params){
params = {
account:params.account,
announce_id:params.announce_id
}
return api.post(urls.postapproval,params)
},
//获取当前点赞记录
getoneapproval(params){
params = {
account: params.account,
announce_id: params.announce_id
}
return api.get(urls.getoneapproval,params)
},
//取消点赞
putapproval(params) {
params = {
account: params.account,
announce_id: params.announce_id
}
return api.put(urls.putapproval, params)
},
}
urls.js Api路由、baseurl请求前缀
const baseUrl = '/api'
export default {
userinfo: baseUrl + '/users/userinfo',
postlogin: baseUrl + '/users/postlogin',
postregister: baseUrl + '/users/postregister',
getresult: baseUrl + '/users/getresult',
putpassword:baseUrl + '/users/putpassword',
getuserinfo: baseUrl + '/users/getuserinfo',
putuserinfo: baseUrl + '/users/putuserinfo',
getuseractivity: baseUrl + '/users/getuseractivity',
getactivitylist: baseUrl + '/users/getactivitylist',
getonehometownactivity: baseUrl + '/users/getonehometownactivity',
getoneactivity: baseUrl + '/users/getoneactivity',
postenrollactivity: baseUrl + '/users/postenrollactivity',
getannounces: baseUrl + '/users/getannounces',
getannouncescol: baseUrl + '/users/getannouncescol',
getcurrentannounce: baseUrl + '/users/getcurrentannounce',
getallactivity: baseUrl + '/users/getallactivity',
postparticipantactivity: baseUrl + '/users/postparticipantactivity',
deleteapplicationactivity: baseUrl + '/users/deleteapplicationactivity',
getactivitynotive: baseUrl + '/users/getactivitynotive',
getattention: baseUrl + '/users/getattention',
postapproval: baseUrl + '/users/postapproval',
getoneapproval: baseUrl + '/users/getoneapproval',
putapproval: baseUrl + '/users/putapproval'
}
前端部分就总结到这里啦!如果你喜欢小编的分享,就为小编点个赞呗!
项目源码看这里哦,码云地址:https://gitee.com/lwkgood/hener_client