element+springboot实现简单的商品管理

  element是饿了么团队开发的PC端用的基于vue的框架,之前在写app端的时候用的是Mint UI(饿了么团队)、vux(这个比较好用)。

  element官网: https://element.eleme.cn/#/zh-CN

  在这里直接下载git上别人写好的: vue-admin-template

1.下载运行vue-admin-template

git地址: https://github.com/PanJiaChen/vue-admin-template

下载之后进入到项目,执行安装相关依赖:

npm install --registry=https://registry.npm.taobao.org

 

运行之后还缺失一些模块,继续执行下面即可:

cnpm install

然后运行项目:

npm run dev

运行起来,访问即可,默认端口是9528:

补充:将该模板汉化。

默认是英语,参考:src/main.js第7行和第32行。如下我们使用日期控件的时候是英语证明当前的语言是英语:

 

 

切换汉语,注释掉src/main.js的第7行和32行并且放开第34行代码,最终如下:

Vue.use(ElementUI)

2.连接后台项目进行登录,前后端分离实现登录(springboot+SSM)

0.去掉一些不必要的js等文件

  一开始我直接引入axios的时候发送的数据老是到不了后台,我去掉了其中的一些JS,因为模板本身对axios进行了拦截处理,我去掉之后可以正常发送数据。

1.引入axios

(1)到项目的根目录下面安装axios:

cnpm install --save axios

 (2)src目录下新建axios\index.js,内容如下:(所有的请求加一个代理地址,对响应信息过滤处理)

import axios from "axios";
import { MessageBox } from 'element-ui';

// 引入常量模块
const defaultSettings = require('../settings.js')

// 修改axios请求的默认配置(配置会以一个优先顺序进行合并。这个顺序是:在 lib/defaults.js 找到的库的默认值,然后是实例的 defaults 属性,最后是请求的 config 参数。)
//` baseURL` 将自动加在 `url` 前面,除非 `url` 是一个绝对 URL。
axios.defaults.baseURL = defaultSettings.serverBasePath;

// 添加请求拦截器
axios.interceptors.request.use(function(config) {
    // 模拟处理前增加token
    return config;
}, function(error) {
    // 对请求错误做些什么
    return Promise.reject(error);
});

// 添加响应拦截器
axios.interceptors.response.use(function(response) {
    // 对响应数据做点什么
    if(response.data.success) {
        // 如果是成功返回信息之后提取出来返回以供后面的调用链使用(后台返回的JSON数据)
        return response.data;
    } else {
        MessageBox.alert(response.data.msg, "提示信息");

        // 返回一个新的Promise对象就相当于接触链式调用
        return new Promise(function(resolve, reject) {
            //                    resolve('success1');
            //                  reject('error');
        });
    }
}, function(error) {
    // 对响应错误做点什么
    return Promise.reject(error);
});

export default axios;

 

(3)修改settings.js加入后台服务基地址

module.exports = {

    title: '丝绸之路商城',

    /**
     * @type {boolean} true | false
     * @description Whether fix the header
     */
    fixedHeader: false,

    /**
     * @type {boolean} true | false
     * @description Whether show the logo in sidebar
     */
    sidebarLogo: false,

    /**
     * 后台服务基地址,每个axios请求都会加这个,拦截请求进行代理
     */
    serverBasePath: '/api'
}

 

(4)vue.config.js增加代理信息以及引入jquery

'use strict'
const path = require('path')
const defaultSettings = require('./src/settings.js')
const webpack = require("webpack")

function resolve(dir) {
    return path.join(__dirname, dir)
}

const name = defaultSettings.title || 'vue Admin Template' // page title

// If your port is set to 80,
// use administrator privileges to execute the command line.
// For example, Mac: sudo npm run
// You can change the port by the following methods:
// port = 9528 npm run dev OR npm run dev --port = 9528
const port = process.env.port || process.env.npm_config_port || 9528 // dev port

// All configuration item explanations can be find in https://cli.vuejs.org/config/
module.exports = {
    /**
     * You will need to set publicPath if you plan to deploy your site under a sub path,
     * for example GitHub Pages. If you plan to deploy your site to https://foo.github.io/bar/,
     * then publicPath should be set to "/bar/".
     * In most cases please use '/' !!!
     * Detail: https://cli.vuejs.org/config/#publicpath
     */
    publicPath: '/',
    outputDir: 'dist',
    assetsDir: 'static',
    lintOnSave: process.env.NODE_ENV === 'development',
    productionSourceMap: false,
    devServer: {
        port: port,
        open: true,
        overlay: {
            warnings: false,
            errors: true
        },
        proxy: {
            '/api': {
                target: 'http://localhost:8088',
                ws: true,
                changeOrigin: true,
                pathRewrite: {
                    '^/api': ''
                }
            }
        }
    },
    configureWebpack: {
        // provide the app's title in webpack's name field, so that
        // it can be accessed in index.html to inject the correct title.
        name: name,
        resolve: {
            alias: {
                '@': resolve('src')
            }
        },
        plugins: [
            new webpack.ProvidePlugin({
              jQuery: "jquery",
              $: "jquery"
            })
          ]
    },
    chainWebpack(config) {
        config.plugins.delete('preload') // TODO: need test
        config.plugins.delete('prefetch') // TODO: need test

        // set svg-sprite-loader
        config.module
            .rule('svg')
            .exclude.add(resolve('src/icons'))
            .end()
        config.module
            .rule('icons')
            .test(/\.svg$/)
            .include.add(resolve('src/icons'))
            .end()
            .use('svg-sprite-loader')
            .loader('svg-sprite-loader')
            .options({
                symbolId: 'icon-[name]'
            })
            .end()

        // set preserveWhitespace
        config.module
            .rule('vue')
            .use('vue-loader')
            .loader('vue-loader')
            .tap(options => {
                options.compilerOptions.preserveWhitespace = true
                return options
            })
            .end()

        config
            // https://webpack.js.org/configuration/devtool/#development
            .when(process.env.NODE_ENV === 'development',
                config => config.devtool('cheap-source-map')
            )

        config
            .when(process.env.NODE_ENV !== 'development',
                config => {
                    config
                        .plugin('ScriptExtHtmlWebpackPlugin')
                        .after('html')
                        .use('script-ext-html-webpack-plugin', [{
                            // `runtime` must same as runtimeChunk name. default is `runtime`
                            inline: /runtime\..*\.js$/
                        }])
                        .end()
                    config
                        .optimization.splitChunks({
                            chunks: 'all',
                            cacheGroups: {
                                libs: {
                                    name: 'chunk-libs',
                                    test: /[\\/]node_modules[\\/]/,
                                    priority: 10,
                                    chunks: 'initial' // only package third parties that are initially dependent
                                },
                                elementUI: {
                                    name: 'chunk-elementUI', // split elementUI into a single package
                                    priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app
                                    test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // in order to adapt to cnpm
                                },
                                commons: {
                                    name: 'chunk-commons',
                                    test: resolve('src/components'), // can customize your rules
                                    minChunks: 3, //  minimum common number
                                    priority: 5,
                                    reuseExistingChunk: true
                                }
                            }
                        })
                    config.optimization.runtimeChunk('single')
                }
            )
    }
}

 

2.修改登录逻辑

(1)src\views\login\index.vue修改登录处理(登录成功之后设置token,并且跳转路由到桌面。后台返回的是user信息,包括基本信息以及roles角色)

    async handleLogin() {
            // 异步登录
                var response = await axios.post('/doLoginJSON.html', {
                    username: this.loginForm.username,
                    password: this.loginForm.password
                });
                
                // 登录成功之后的处理
                if(response.success) {
                    // 显示文字
                    Message({message: '登录成功', type: 'success'});

                    // 将用户信息作为token存入sessionStorage
                    setToken(response.data);

                    // 跳转路由
                    this.$router.replace("/dashboard");
                }
    },

 

(2)修改src\utils\auth.js中setToken和getToken的方法(原来是存到cookie中,现在我存到sessionStorage中。roles也是后台返回的roles数组信息)。

const TokenKey = 'vue_admin_template_token'

export function getToken() {
    const token = sessionStorage.getItem(TokenKey);
    if (token) {
        return JSON.parse(token);
    }
    
  return "";
}

export function setToken(token) {
    if (!token) {
        return;
    }
    
    // 将用户存入sessionStorage
    sessionStorage.setItem("userid", token.id);
    sessionStorage.setItem("username", token.username);
    sessionStorage.setItem("fullname", token.fullname);

    sessionStorage.setItem(TokenKey, JSON.stringify(token));
}

export function removeToken() {
    sessionStorage.removeItem("userid");
    sessionStorage.removeItem("username");
    sessionStorage.removeItem("fullname");
    
    sessionStorage.removeItem(TokenKey);
}

export function getRoles() {
    const rolesArray = [];
    const token = sessionStorage.getItem(TokenKey);
    if (token) {
        rolesArray.push(JSON.parse(token).roles);
    }
    
  return rolesArray;
}

 

(3)后台登录逻辑如下

    /**
     * 处理登陆请求(JSON数据)
     * 
     * @param username
     * @param password
     * @param session
     * @return
     */
    @RequestMapping("doLoginJSON")
    @ResponseBody
    public JSONResultUtil doLoginJSON(@RequestBody User user, HttpSession session, HttpServletRequest request) {
        User loginUser = userService.getUserByUserNameAndPassword(user.getUsername(), user.getPassword());
        logger.debug("loginUser: {}", loginUser);
        if (loginUser == null) {
            return JSONResultUtil.error("账号或者密码错误");
        }

        session.setAttribute("user", loginUser);
        return new JSONResultUtil<User>(true, "登录成功", loginUser);
    }

3.修改登出逻辑

(1)修改src\layout\components\Navbar.vue

          <el-dropdown-item divided @click.native="doLogout">
            <span style="display:block;">退出</span>
          </el-dropdown-item>

 

登出方法如下:向后台发送登录请求,跳转路由之后调用auth.utils的removeToken方法删掉token信息。

    async doLogout() {
        const logoutUrl = "/logoutJSON.html";
        const response = await axios.post(logoutUrl);
        
            // 跳转路由
            this.$router.replace("/login");
            
        // 删除token
        removeToken();
    },
    

 

(2)后台springboot登录逻辑如下:

package cn.qs.controller.system;

import javax.servlet.http.HttpSession;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import cn.qs.utils.JSONResultUtil;

/**
 * 退出登陆
 * 
 * @author Administrator
 *
 */
@Controller
public class LogoutController {
    @RequestMapping("logout")
    public String logout(HttpSession session) {
        session.removeAttribute("user");
        return "redirect:/login.html";
    }

    @RequestMapping("logoutJSON")
    @ResponseBody
    public JSONResultUtil logoutJSON(HttpSession session) {
        session.removeAttribute("user");
        return JSONResultUtil.ok();
    }
}

 

4. 模板中加入自己的菜单,并且实现权限控制

(1)src\router\index.js文件中constantRoutes里面加入用户管理菜单:

  {
    path: '/user',
    component: Layout,
    redirect: '/user/list',
    name: 'user',
    alwaysShow: true,
    meta: { title: '用户管理', icon: 'example', roles: ["系统管理员"] },
    children: [
      {
        path: 'list',
        name: 'List',
        component: () => import('@/views/user/index'),
        meta: { title: '用户列表', icon: 'table' }
      }
    ]
  },

 

这个有好几个属性,在文件头部也说明了。

/**
 * Note: sub-menu only appear when route children.length >= 1
 * Detail see: https://panjiachen.github.io/vue-element-admin-site/guide/essentials/router-and-nav.html
 *
 * hidden: true                   if set true, item will not show in the sidebar(default is false)
 * alwaysShow: true               if set true, will always show the root menu
 *                                if not set alwaysShow, when item has more than one children route,
 *                                it will becomes nested mode, otherwise not show the root menu
 * redirect: noRedirect           if set noRedirect will no redirect in the breadcrumb
 * name:'router-name'             the name is used by <keep-alive> (must set!!!)
 * meta : {
    roles: ['admin','editor']    control the page roles (you can set multiple roles)
    title: 'title'               the name show in sidebar and breadcrumb (recommend set)
    icon: 'svg-name'             the icon show in the sidebar
    breadcrumb: false            if set false, the item will hidden in breadcrumb(default is true)
    activeMenu: '/example/list'  if set path, the sidebar will highlight the path you set
  }
 */

 

简单解释几个有用的:

hidden: 是否隐藏,默认否,隐藏之后不会在左边菜单栏显示。

alwaysShow:为true的时候表示只有一个子菜单也显示父菜单,false会隐藏父菜单,只显示子菜单。

redirect: 重定向的路由

name:路由名称。

meta:[

  title: '菜单显示的名称',

  icon:'显示的图标'

  roles; ['r1', 'r2']  // 需要的权限

]

(2)上面的meta的roles默认没生效,需要修改src\layout\components\Sidebar\SidebarItem.vue文件:

  增加hasRoles(item)方法进行解释判断,获取当前用户存到sessionStorage的角色信息进行匹配。

<template>
  <div v-if="!item.hidden && hasRoles(item)">
    <template v-if="hasOneShowingChild(item.children,item) && (!onlyOneChild.children||onlyOneChild.noShowingChildren)&&!item.alwaysShow">
      <app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
        <el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown':!isNest}">
          <item :icon="onlyOneChild.meta.icon||(item.meta&&item.meta.icon)" :title="onlyOneChild.meta.title" />
        </el-menu-item>
      </app-link>
    </template>

    <el-submenu v-else ref="subMenu" :index="resolvePath(item.path)" popper-append-to-body>
      <template slot="title">
        <item v-if="item.meta" :icon="item.meta && item.meta.icon" :title="item.meta.title" />
      </template>
      <sidebar-item
        v-for="child in item.children"
        :key="child.path"
        :is-nest="true"
        :item="child"
        :base-path="resolvePath(child.path)"
        class="nest-menu"
      />
    </el-submenu>
  </div>
</template>

<script>
import path from 'path'
import { isExternal } from '@/utils/validate'
import Item from './Item'
import AppLink from './Link'
import FixiOSBug from './FixiOSBug'
import { getRoles } from '@/utils/auth'

export default {
  name: 'SidebarItem',
  components: { Item, AppLink },
  mixins: [FixiOSBug],
  props: {
    // route object
    item: {
      type: Object,
      required: true
    },
    isNest: {
      type: Boolean,
      default: false
    },
    basePath: {
      type: String,
      default: ''
    }
  },
  data() {
    // To fix https://github.com/PanJiaChen/vue-admin-template/issues/237
    // TODO: refactor with render function
    this.onlyOneChild = null
    return {}
  },
  methods: {
      // 根据角色过滤按钮
      hasRoles(item) {
          if (item && item.meta && item.meta.roles && item.meta.roles.length >0 ) {
              const userRoles = getRoles();
              if (!userRoles) {
                  return false;
              }
              
              var index = 0;
              for (index in userRoles) {
                  if (item.meta.roles.indexOf(userRoles[index]) > -1) {
                      return true;
                  }
              }
              
              return false;
          }
          
          return true;
      },
      
    hasOneShowingChild(children = [], parent) {
      const showingChildren = children.filter(item => {
        if (item.hidden) {
          return false
        } else {
          // Temp set(will be used if only has one showing child)
          this.onlyOneChild = item
          return true
        }
      })

      // When there is only one child router, the child router is displayed by default
      if (showingChildren.length === 1) {
        return true
      }

      // Show parent if there are no child router to display
      if (showingChildren.length === 0) {
        this.onlyOneChild = { ... parent, path: '', noShowingChildren: true }
        return true
      }

      return false
    },
    resolvePath(routePath) {
      if (isExternal(routePath)) {
        return routePath
      }
      if (isExternal(this.basePath)) {
        return this.basePath
      }
      return path.resolve(this.basePath, routePath)
    }
  }
}
</script>

 

  实际中还实现了整合vue-kindeditor实现文本编辑器(参考:这个)、分页查询商品等操作。

 

git地址: https://github.com/qiao-zhi/vue_springboot_shop

 

posted @ 2020-03-26 11:51  QiaoZhi  阅读(2185)  评论(0编辑  收藏  举报