【实战项目Hener--前端分析及部分源码】vue-cli3+Vant UI+koa2+mysql

项目功能演示视频地址:

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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值