【前端框架】vue2.5+ts+elementui后台管理系统搭建全流程,保姆级教学!(一)项目搭建,布局

10 篇文章 0 订阅
4 篇文章 0 订阅

本教学文章分三部分:

  • 第一篇:项目搭建、布局
  • 第二篇:用户登录、身份认证、用户权限
  • 第三篇:权限管理、商品管理、广告管理

一、使用vue-cli创建项目

$ vue create project-name

? Please pick a preset:  					// 选择预设
	> Manually select features 				// 手动选择功能特性

?Check the features needed for your project:
	◯ Choose 								// Vue version 默认vue2 选择后可选vue3
	◉ Babel 								// 使用babel es6、ts转换
	◉ TypeScript
	◯ Progressive Web App (PWA) Support
 	◉ Router								// vue-router 管理路由
   	◉ Vuex									// vuex 管理共享数据容器
 	◉ CSS Pre-processors					// css预处理器
 	◉ Linter / Formatter					// 代码格式校验
	◯ Unit Testing							// 测试相关
 	◯ E2E Testing							// 测试相关

? Use class-style component syntax? (Y/n) y // 若使用ts,是否使用class风格的组件语法
? Use Babel alongside TypeScript(Y/n) y 	// 是否让babel和ts结合起来编译,使ts只负责转换类型相关的功能特性
? Use history mode for router?(Y/n) n 		// 是否使用history路由模式 history路由模式兼容不太好所以不选择,默认hash模式

? Pick a CSS pre-processor: 				// 选择对应的css预处理器
  > Sass/SCSS (with dart-sass) 				// 此次选择
  Sass/SCSS (with node-sass)   				//老版scss
  Less 
  Stylus 

? Pick a linter / formatter config: 		 // 选择代码的格式校验
	ESLint with error prevention only 
 	ESLint + Airbnb config 
 	> ESLint + Standard config 				// 此次选择
 	ESLint + Prettier 
 	TSLint (deprecated) 

 ? Pick additional lint features:			 // 代码格式校验触发时机 
	◉ Lint on save							 // 保存文件时
	◉ Lint and fix on commit				 // 执行git commit提交时

											
? Where do you prefer placing config for Babel, ESLint, etc.?
											 // Babel, ESLint等工具生成的配置信息如何存放
	> In dedicated config files				 // 存放到单独的配置文件中 此次选择
	In package.json							 // 全都写在package.json

?Save this as a preset for future projects? (y/N)  n
											 // 是否把刚才的配置选项保存起来

二、目录结构:

在这里插入图片描述

三、ts相关配置

1. typescript相关依赖

dependencies 依赖:
	vue-class-component 				// 提供使用 Class 语法写 Vue 组件
	vue-property-decorator		 		// 在 Class 语法基础之上提供了一些辅助装饰器

devDependencies 依赖:
	@typescript-eslint/eslint-plugin 	// 使用 ESLint 校验 TypeScript 代码
	@typescript-eslint/parser 			// 将 TypeScript 转为 AST 供 ESLint 校验使用
	@vue/cli-plugin-typescript 			// 使用 TypeScript + ts-loader + fork-ts-checker-webpack-plugin进行更快的类型检查.
	@vue/eslint-config-typescript 		// 兼容 ESLint 的 TypeScript 校验规则
	typescript 							// TypeScript编译器,提供类型校验和转换 JavaScript 功能

2. typescript配置文件

tsconfig.json

{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "strict": true,
    "jsx": "preserve",
    "importHelpers": true,
    "moduleResolution": "node",
    "experimentalDecorators": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "sourceMap": true,
    "baseUrl": ".",
    "types": [
      "webpack-env"
    ],
    "paths": {
      "@/*": [
        "src/*"
      ]
    },
    "lib": [
      "esnext",
      "dom",
      "dom.iterable",
      "scripthost"
    ]
  },
  "include": [
    "src/**/*.ts",
    "src/**/*.tsx",
    "src/**/*.vue",
    "tests/**/*.ts",
    "tests/**/*.tsx"
  ],
  "exclude": [
    "node_modules"
  ]
}

shims-tsx.d.ts

// 为 jsx 组件模板补充类型声明
import Vue, { VNode } from 'vue'

declare global {
  namespace JSX {
    // tslint:disable no-empty-interface
    interface Element extends VNode {}
    // tslint:disable no-empty-interface
    interface ElementClass extends Vue {}
    interface IntrinsicElements {
      [elem: string]: any
    }
  }
}

shims-vue.d.ts

// 主要用于 Typescrip 识别 .vue 文件模块
// Typescript 默认不支持导入.vue 模块,这个文件告诉 Typescript 导入.vue 文件模块都按 Vueconstructor<vue>类型识别处理
declare module '*.vue' {
  import Vue from 'vue'
  export default Vue
}

3. eslint配置

.eslintrc.js配置文件

module.exports = {
  root: true,
  env: {
    node: true
  },
  extends: [
    'plugin:vue/essential',
    '@vue/standard',
    '@vue/typescript/recommended'
  ],
  parserOptions: {
    ecmaVersion: 2020
  },
  // 自定义校验规则
  rules: {
  	// 查阅eslint文档进行自己项目代码规则配置
    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
  }
}

4. elementUI组件库

安装 element-ui

$ npm i element-ui -S
// 完整引入 
// mian.js文件中加入
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);

5. 样式处理

src/styles
├── index.scss # 全局样式(在入口模块被加载生效)
├── mixin.scss # 公共mixin混入(可以把重复的样式封装为mixin混入到复用的地方)
├── reset.scss # 重置基础样式
└── variables.scss # 公共样式变量

index.scss

@import './variables.scss';

html {
  font-family: $font-family;
  -webkit-text-size-adjust: 100%;
  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
  // better Font Rendering
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

body {
  margin: 0;
  background-color: $body-bg;
}

/* custom element theme */
$--color-primary: $primary-color;
$--color-success: $success-color;
$--color-warning: $warning-color;
$--color-danger: $danger-color;
$--color-info: $info-color;
/* change font path, required */
$--font-path: '~element-ui/lib/theme-chalk/fonts';
/* import element default theme */
@import '~element-ui/packages/theme-chalk/src/index';

.el-pagination {
  color: #868e96;
}

.status {
  display: inline-block;
  cursor: pointer;
  width: .875rem;
  height: .875rem;
  vertical-align: middle;
  border-radius: 50%;

  &-primary {
    background: $--color-primary;
  }

  &-success {
    background: $--color-success;
  }

  &-warning {
    background: $--color-warning;
  }

  &-danger {
    background: $--color-danger;
  }

  &-info {
    background: $--color-info;
  }
}

variables.scss

$primary-color: #40586F;
$success-color: #51cf66;
$warning-color: #fcc419;
$danger-color: #ff6b6b;
$info-color: #868e96; // #22b8cf;

$body-bg: #E9EEF3; // #f5f5f9;

$sidebar-bg: #F8F9FB;
$navbar-bg: #F8F9FB;

$font-family: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;

// 在vue.config.js中配置共享全局样式变量文件
scss: {
	additionalData: `@import "~@/variables.scss"`
}

6. 配置后端代理

常用跨域方法:CORS(需服务端配合)、proxy代理(此次使用)

// vue.config.js
module.exports = {
	devServer: {
		proxy: {
			'/api': {
				target: '', //转发地址
				changeOrigin: true // 把请求头中的host配置为targte
			}
		}
	}
}

6. 初始化路由组件

在views中创建项目相关页面
在这里插入图片描述

页面基本内容,首页为例

<template>
  <div class="home">
    首页
  </div>
</template>
<script lang="ts">
import Vue from 'vue'

export default Vue.extend({
  name: 'HomeIndex',
  data () {
    return {
      username: ''
    }
  },
  methods: {
  }
})
</script>
<style lang="scss" scoped>
</style>

src/router/index.ts

import Vue from 'vue'
import VueRouter, { RouteConfig } from 'vue-router'
import Layout from '@/layout/index.vue'
import store from '@/store'

Vue.use(VueRouter)

// 路由配置规则
const routes: Array<RouteConfig> = [
  {
    path: '/login',
    name: 'login',
    component: () => import(/* webpackChunkName: 'login' */ '@/views/login/index.vue')
  },
  {
    path: '/register',
    name: 'register',
    component: () => import(/* webpackChunkName: 'register' */ '@/views/login/register.vue')
  },
 {
    path: '', // 默认子路由
    name: 'home',
    component: () => import(/* webpackChunkName: 'home' */ '@/views/home/index.vue')
  },
  {
    path: '/role',
    name: 'role',
    component: () => import(/* webpackChunkName: 'login' */ '@/views/role/index.vue')
  },
  {
    path: '/menu',
    name: 'menu',
    component: () => import(/* webpackChunkName: 'menu' */ '@/views/menu/index.vue')
  },
  {
    path: '/resource',
    name: 'resource',
    component: () => import(/* webpackChunkName: 'resource' */ '@/views/resource/index.vue')
  },
  {
    path: '/product',
    name: 'product',
    component: () => import(/* webpackChunkName: 'course' */ '@/views/product/index.vue')
  },
  {
    path: '/user',
    name: 'user',
    component: () => import(/* webpackChunkName: 'user' */ '@/views/user/index.vue')
  },
  {
    path: '/advert',
    name: 'advert',
    component: () => import(/* webpackChunkName: 'advert' */ '@/views/advert/index.vue')
  },
  {
    path: '/advert-space',
    name: 'advert-space',
    component: () => import(/* webpackChunkName: 'advert-space' */ '@/views/advert-space/index.vue')
  },
  {
    path: '/menu/create',
    name: 'menu-create',
    component: () => import(/* webpackChunkName: 'menu-create' */ '@/views//menu/create.vue')
  }
  {
    path: '*',
    name: '404',
    component: () => import(/* webpackChunkName: '404' */ '@/views/error-page/404.vue')
  }
]

const router = new VueRouter({
  routes
})

export default router

7. layout,嵌套路由

后台管理系统一般大多页面都有很多公共部分,例如头部、侧边栏…,因此我们需要创建一个layout模版组件,使用嵌套路由,只切换页面内容部分

src/layout/compontens/app-header.vue

<template>
  <div class="header">
    <el-breadcrumb separator-class="el-icon-arrow-right">
      <el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
      <el-breadcrumb-item>活动管理</el-breadcrumb-item>
      <el-breadcrumb-item>活动列表</el-breadcrumb-item>
      <el-breadcrumb-item>活动详情</el-breadcrumb-item>
    </el-breadcrumb>
    <el-dropdown>
      <span class="el-dropdown-link">
        <el-avatar shape="square" :size="30" :src="@/assets/portrait.png"></el-avatar>
        <i class="el-icon-arrow-down el-icon--right"></i>
      </span>
      <el-dropdown-menu slot="dropdown">
        <el-dropdown-item>xxxx</el-dropdown-item>
        <el-dropdown-item divided>退出</el-dropdown-item>
      </el-dropdown-menu>
    </el-dropdown>
  </div>
</template>
<script lang="ts">
import Vue from 'vue'

export default Vue.extend({
  name: 'AppHeader',
})
</script>
<style lang="scss" scoped>
.header {
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  .el-dropdown-link {
    display: flex;
    align-items: center
  }
}
</style>

src/layout/compontens/app-aside.vue

<template>
  <div class="aside">
    <div>
      <el-menu
      default-active="2"
      class="el-menu-vertical-demo"
      @open="handleOpen"
      @close="handleClose"
      background-color="rgb(48, 65, 86)"
      text-color="#fff"
      active-text-color="#ffd04b"
      router
    >
      <el-submenu index="1">
        <template slot="title">
          <i class="el-icon-location"></i>
          <span>权限管理</span>
        </template>
        <el-menu-item-group>
          <el-menu-item index="/role">
             <i class="el-icon-menu"></i>
             <span slot="title">角色管理</span>
          </el-menu-item>
           <el-menu-item index="/menu">
             <i class="el-icon-menu"></i>
             <span slot="title">菜单管理</span>
          </el-menu-item>
           <el-menu-item index="/resource">
             <i class="el-icon-menu"></i>
             <span slot="title">资源管理</span>
          </el-menu-item>
        </el-menu-item-group>
      </el-submenu>
      <el-menu-item index="/product">
        <i class="el-icon-menu"></i>
        <span slot="title">商品管理</span>
      </el-menu-item>
      <el-menu-item index="/user">
        <i class="el-icon-document"></i>
        <span slot="title">用户管理</span>
      </el-menu-item>
      <el-submenu index="4">
        <template slot="title">
          <i class="el-icon-location"></i>
          <span>广告管理</span>
        </template>
        <el-menu-item-group>
          <el-menu-item index="/advert">
             <i class="el-icon-menu"></i>
             <span slot="title">广告列表</span>
          </el-menu-item>
           <el-menu-item index="/advert-space">
             <i class="el-icon-menu"></i>
             <span slot="title">广告位列表</span>
          </el-menu-item>
        </el-menu-item-group>
      </el-submenu>
    </el-menu>
    </div>
  </div>
</template>
<script lang="ts">
import Vue from 'vue'

export default Vue.extend({
  name: 'AppAside',
  methods: {
    handleOpen (key: string, keyPath: string): void {
      console.log(key, keyPath)
    },
    handleClose (key: string, keyPath: string): void {
      console.log(key, keyPath)
    }
  }
})
</script>
<style lang="scss">
.aside {
  width: 201px;
}
.el-submenu__title:hover{
  background-color: #263445 !important;
}
.el-menu-item:hover {
  background: #001528!important;
}
</style>


src/layout/index.vue

<template>
  <el-container>
    <div class="page-left">
      <h1 class="logo">
        boat管理系统
      </h1>
      <el-aside>
        <app-aside/>
      </el-aside>
    </div>
    <el-container>
      <el-header>
        <app-header />
      </el-header>
      <el-main>
        <!-- 子路由出口 -->
        <router-view />
      </el-main>
    </el-container>
  </el-container>
</template>

<script lang="ts">
import Vue from 'vue'
import appAside from './components/app-aside.vue'
import appHeader from './components/app-header.vue'

export default Vue.extend({
  name: 'layoutIndex',
  components: {
    appAside,
    appHeader
  }
})
</script>

<style lang="scss" scoped>
.el-container {
  max-height: 100vh;
  min-width: 980px;
}
.logo {
  padding: 20px 0;
  text-align: center;
  margin: 0;
  color: #fff;
  background: rgb(48, 65, 86);
  box-sizing: border-box;
}
.el-aside {
  flex: 1;
  background: rgb(48, 65, 86);
}
.el-header {
  background: #fff;
}
.el-main {
  background: #e9eef3;
}
.page-left {
  width: 200px;
  min-height: 100vh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
</style>

修改src/router/index.ts

import Vue from 'vue'
import VueRouter, { RouteConfig } from 'vue-router'
import Layout from '@/layout/index.vue'
import store from '@/store'

Vue.use(VueRouter)

// 路由配置规则
const routes: Array<RouteConfig> = [
  {
    path: '/login',
    name: 'login',
    component: () => import(/* webpackChunkName: 'login' */ '@/views/login/index.vue')
  },
  {
    path: '/register',
    name: 'register',
    component: () => import(/* webpackChunkName: 'register' */ '@/views/login/register.vue')
  },
  {
    path: '/',
    component: Layout,
    meta: {
      requiresAuth: true // 自定义数据
    },
    children: [
      {
        path: '', // 默认子路由
        name: 'home',
        component: () => import(/* webpackChunkName: 'home' */ '@/views/home/index.vue')
      },
      {
        path: '/role',
        name: 'role',
        component: () => import(/* webpackChunkName: 'login' */ '@/views/role/index.vue')
      },
      {
        path: '/menu',
        name: 'menu',
        component: () => import(/* webpackChunkName: 'menu' */ '@/views/menu/index.vue')
      },
      {
        path: '/resource',
        name: 'resource',
        component: () => import(/* webpackChunkName: 'resource' */ '@/views/resource/index.vue')
      },
      {
        path: '/product',
        name: 'product',
        component: () => import(/* webpackChunkName: 'course' */ '@/views/product/index.vue')
      },
      {
        path: '/user',
        name: 'user',
        component: () => import(/* webpackChunkName: 'user' */ '@/views/user/index.vue')
      },
      {
        path: '/advert',
        name: 'advert',
        component: () => import(/* webpackChunkName: 'advert' */ '@/views/advert/index.vue')
      },
      {
        path: '/advert-space',
        name: 'advert-space',
        component: () => import(/* webpackChunkName: 'advert-space' */ '@/views/advert-space/index.vue')
      },
      {
        path: '/menu/create',
        name: 'menu-create',
        component: () => import(/* webpackChunkName: 'menu-create' */ '@/views//menu/create.vue')
      }
    ]
  },
  {
    path: '*',
    name: '404',
    component: () => import(/* webpackChunkName: '404' */ '@/views/error-page/404.vue')
  }
]

const router = new VueRouter({
  routes
})

export default router

8、登录页面

登录页:src/views/login/index.vue

<template>
  <div class="login">
    <div class="login-main">
      <div class="login-left">
        <h1>boat管理系统</h1>
      </div>
      <el-form
        class="login-form"
        label-position="top"
        ref="form"
        :model="formData"
        label-width="80px"
        :rules="formRule"
      >
        <h2>登录</h2>
        <el-form-item label="手机号" prop="userName">
          <el-input v-model="formData.userName"></el-input>
        </el-form-item>
        <el-form-item label="密码" prop="pwd">
          <el-input v-model="formData.pwd" type="password"></el-input>
        </el-form-item>
        <el-form-item>
          <el-button
            class="login-btn"
            :loading="isLoginLoading"
            type="primary"
            @click="onSubmit"
          >
            登录
          </el-button>
        </el-form-item>
        <div class="login-link">
          <el-link class="login-link-item" @click="linkHandle('0')">立即注册</el-link>
          <el-link class="login-link-item"  @click="linkHandle('1')">忘记密码?</el-link>
        </div>
      </el-form>
      </div>
  </div>
</template>
<script lang="ts">
import Vue from 'vue'
import { Form } from 'element-ui'

export default Vue.extend({
  name: 'LoginIndex',
  data () {
    return {
      isLoginLoading: false,
      formData: {
        userName: '',
        pwd: ''
      },
      formRule: {
        userName: [
          { required: true, message: '请输入手机号', trigger: 'blur' },
          { pattern: /^1\d{10}$/, message: '请输入正确的手机号', trigger: 'blur' }
        ],
        pwd: [
          { required: true, message: '请输入密码', trigger: 'blur' },
          { min: 6, max: 18, message: '长度在 6 到 18 个字符', trigger: 'blur' }
        ]
      }
    }
  },
  methods: {
    linkHandle (target: string): void {
      if (target === '0') { // 注册
        this.$router.push({
          name: 'register'
        })
      } else { // 忘记密码

      }
    },
    async onSubmit () {
      try {
        // 1. 表单验证
        await (this.$refs.form as Form).validate()
        // 登录按钮 loading
        this.isLoginLoading = true

        // 2. 验证通过 -> 提交表单
        interface User {
          userName: string,
          pwd: string
        }
        const params:User = this.formData
        const { data } = await login(params)
        console.log(data)
        if (data.status !== 200) {
          this.$message.error(data.msg)
        } else {
        .	// 登录成功
        }
      } catch (err) {
        this.$message.error('登录失败')
        console.log(err)
      }
      this.isLoginLoading = false
    }
  }
})
</script>
<style lang="scss" scoped>
.login {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
  padding: 100px 500px;
  .login-main {
    display: flex;
    justify-content: center;
    align-items: center;
    border-radius: 10px;
    overflow: hidden;
    .login-left {
      align-items: center;
      padding: 170px 20px;
      width: 380px;
      height: 600px;
      background: url(../../assets/login.png);
      background-size: 100% 100%;
      color: #fff;
      box-sizing: border-box;
    }
    .login-form {
      flex:1;
      height: 600px;
      background: #fff;
      padding: 100px;
      border-radius: 5px;
      box-sizing: border-box;
    }
    .login-btn {
      width: 100%;
    }
    .login-link {
      display: flex;
      justify-content: flex-end;
      .login-link-item:nth-child(1) {
        margin-right: 10px;
      }
    }
  }
}
</style>

注册页:src/views/login/register.vue

<template>
  <div class="register">
     <h1>boat 管理系统</h1>
    <el-form
      class="register-form"
      label-position="top"
      ref="form"
      :model="formData"
      label-width="80px"
      :rules="formRule"
    >
      <h2>注册</h2>
      <el-form-item label="手机号" prop="userName">
        <el-input v-model="formData.userName"></el-input>
      </el-form-item>
      <el-form-item label="密码" prop="pwd">
        <el-input v-model="formData.pwd"></el-input>
      </el-form-item>
      <el-form-item>
         <el-button
          class="register-btn"
          :loading="isLoginLoading"
          type="primary"
          @click="onSubmit"
        >
          注册
        </el-button>
      </el-form-item>
      <div>
        <el-link @click="goLogin">&lt; 返回登陆</el-link>
      </div>
    </el-form>
  </div>
</template>
<script lang="ts">
import Vue from 'vue'
import { register } from '@/services/user'
import { Form } from 'element-ui'

export default Vue.extend({
  name: 'Register',
  data () {
    return {
      isLoginLoading: false,
      formData: {
        userName: '',
        pwd: ''
      },
      formRule: {
        userName: [
          { required: true, message: '请输入手机号', trigger: 'blur' },
          { pattern: /^1\d{10}$/, message: '请输入正确的手机号', trigger: 'blur' }
        ],
        pwd: [
          { required: true, message: '请输入密码', trigger: 'blur' },
          { min: 6, max: 18, message: '长度在 6 到 18 个字符', trigger: 'blur' }
        ]
      }
    }
  },
  methods: {
    goLogin () {
      this.$router.go(-1)
    },
    async onSubmit () {
      try {
        // 1. 表单验证
        await (this.$refs.form as Form).validate()
        // 登录按钮 loading
        this.isLoginLoading = true

        // 2. 验证通过 -> 提交表单
        const { data } = await register(this.formData)

        if (data !== 200) {
          this.$message.error(data.msg)
        } else {
          //    成功:跳转回原来页面或首页
          this.$router.push(this.$route.query.redirect as string || '/')
          this.$message.success('登录成功')
        }
      } catch (err) {
        this.$message.error('登录失败')
        console.log(err)
      }
      this.isLoginLoading = false
    }
  }
})
</script>
<style lang="scss" scoped>
.register {
  height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  .register-form {
    width: 300px;
    background: #fff;
    padding: 20px;
    border-radius: 5px;
  }
  .register-btn {
    width: 100%;
  }
}
</style>

最终效果图:
在这里插入图片描述

在这里插入图片描述

结尾

这篇内容主要是详细讲述如何从0搭建、完善vue项目结构,引入ui,完成了公共样式,路由,以及登录页面的简单功能开发。下一篇的内容会有,登录时身份认证、如何根据不同的用户权限,显示不同的菜单、功能。(可以看我之前的文章,试着自己用nodejs和mysql写数据接口,最终所有代码最后一篇会公开让大家参考)

如有问题或建议欢迎留言或私信我一起讨论👏

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值