├── .browserslistrc ├── noteImg ├── git.png ├── 总结.png ├── 服务器.jpg ├── 组件.png ├── token.png ├── vuex.jpg ├── 业务流程图.jpg ├── 测试用例.jpg ├── 生命周期.jpg ├── 生命周期函数.png ├── 程序流程图.jpg ├── 项目开发流程.jpg ├── 项目管理.png ├── vue-cil.png ├── vue文件规则.png ├── 拦截器3个步骤.png ├── axios调用类型.png ├── vuex_module.jpg └── createProject.png ├── public ├── favicon.ico └── index.html ├── babel.config.js ├── src ├── assets │ └── images │ │ ├── face.png │ │ └── logo.png ├── App.vue ├── views │ ├── 404.vue │ ├── Console │ │ └── index.vue │ ├── Layout │ │ ├── components │ │ │ ├── layoutMain.vue │ │ │ ├── layoutNav.vue │ │ │ └── layoutHeader.vue │ │ └── index.vue │ ├── Info │ │ ├── detailed.vue │ │ ├── category.vue │ │ └── index.vue │ ├── User │ │ └── index.vue │ └── Login │ │ └── index.vue ├── store │ ├── modules │ │ ├── common.js │ │ ├── app.js │ │ ├── info.js │ │ ├── login.js │ │ └── permission.js │ └── index.js ├── styles │ ├── config.scss │ ├── main.scss │ ├── elementUI.scss │ └── normalize.scss ├── components │ ├── svgIcon │ │ ├── index.js │ │ ├── svg │ │ │ ├── zhankai.svg │ │ │ ├── jianhaoshouqi.svg │ │ │ ├── jiahaozhankai.svg │ │ │ ├── panel.svg │ │ │ ├── guanbi.svg │ │ │ ├── yonghu.svg │ │ │ ├── xinxi.svg │ │ │ └── logo.svg │ │ └── svgIcon.vue │ ├── table │ │ ├── tableLoadData.js │ │ └── index.vue │ ├── select │ │ └── index.vue │ ├── uploadImg │ │ └── index.vue │ ├── dialog │ │ ├── infoAdd.vue │ │ ├── infoEdit.vue │ │ └── userAdd.vue │ └── cityPicker │ │ └── index.vue ├── utils │ ├── formatData.js │ ├── btnPermission.js │ ├── cookies.js │ ├── validate.js │ ├── global.js │ └── interceptor.js ├── main.js ├── api │ ├── common.js │ ├── login.js │ ├── user.js │ └── info.js └── router │ ├── guard.js │ └── index.js ├── .gitignore ├── .eslintrc.js ├── package.json ├── vue.config.js └── README.md /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /noteImg/git.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sherwinshen/vue-admin/HEAD/noteImg/git.png -------------------------------------------------------------------------------- /noteImg/总结.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sherwinshen/vue-admin/HEAD/noteImg/总结.png -------------------------------------------------------------------------------- /noteImg/服务器.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sherwinshen/vue-admin/HEAD/noteImg/服务器.jpg -------------------------------------------------------------------------------- /noteImg/组件.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sherwinshen/vue-admin/HEAD/noteImg/组件.png -------------------------------------------------------------------------------- /noteImg/token.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sherwinshen/vue-admin/HEAD/noteImg/token.png -------------------------------------------------------------------------------- /noteImg/vuex.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sherwinshen/vue-admin/HEAD/noteImg/vuex.jpg -------------------------------------------------------------------------------- /noteImg/业务流程图.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sherwinshen/vue-admin/HEAD/noteImg/业务流程图.jpg -------------------------------------------------------------------------------- /noteImg/测试用例.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sherwinshen/vue-admin/HEAD/noteImg/测试用例.jpg -------------------------------------------------------------------------------- /noteImg/生命周期.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sherwinshen/vue-admin/HEAD/noteImg/生命周期.jpg -------------------------------------------------------------------------------- /noteImg/生命周期函数.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sherwinshen/vue-admin/HEAD/noteImg/生命周期函数.png -------------------------------------------------------------------------------- /noteImg/程序流程图.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sherwinshen/vue-admin/HEAD/noteImg/程序流程图.jpg -------------------------------------------------------------------------------- /noteImg/项目开发流程.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sherwinshen/vue-admin/HEAD/noteImg/项目开发流程.jpg -------------------------------------------------------------------------------- /noteImg/项目管理.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sherwinshen/vue-admin/HEAD/noteImg/项目管理.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sherwinshen/vue-admin/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /noteImg/vue-cil.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sherwinshen/vue-admin/HEAD/noteImg/vue-cil.png -------------------------------------------------------------------------------- /noteImg/vue文件规则.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sherwinshen/vue-admin/HEAD/noteImg/vue文件规则.png -------------------------------------------------------------------------------- /noteImg/拦截器3个步骤.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sherwinshen/vue-admin/HEAD/noteImg/拦截器3个步骤.png -------------------------------------------------------------------------------- /noteImg/axios调用类型.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sherwinshen/vue-admin/HEAD/noteImg/axios调用类型.png -------------------------------------------------------------------------------- /noteImg/vuex_module.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sherwinshen/vue-admin/HEAD/noteImg/vuex_module.jpg -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /noteImg/createProject.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sherwinshen/vue-admin/HEAD/noteImg/createProject.png -------------------------------------------------------------------------------- /src/assets/images/face.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sherwinshen/vue-admin/HEAD/src/assets/images/face.png -------------------------------------------------------------------------------- /src/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sherwinshen/vue-admin/HEAD/src/assets/images/logo.png -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 11 | -------------------------------------------------------------------------------- /src/views/404.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /src/store/modules/common.js: -------------------------------------------------------------------------------- 1 | const state = { 2 | qiniuUrl: "http://www-web-jshtml-cn-idva7mx.web-jshtml.cn/" 3 | } 4 | const getters = { 5 | qiniuUrl: state => state.qiniuUrl 6 | } 7 | 8 | export default { 9 | namespaced: true, 10 | state, 11 | getters 12 | } -------------------------------------------------------------------------------- /src/styles/config.scss: -------------------------------------------------------------------------------- 1 | $color-main: #344a5f; 2 | $color-second: #f56c6c; 3 | $color-third: #f5f5f5; 4 | 5 | @mixin webkit($type, $value) { 6 | -webkit-#{$type}: $value; 7 | -moz-#{$type}: $value; 8 | -o-#{$type}: $value; 9 | -ms-#{$type}: $value; 10 | #{$type}: $value; 11 | } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw? 22 | -------------------------------------------------------------------------------- /src/components/svgIcon/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import SvgIcon from './svgIcon' 3 | 4 | Vue.component('svg-icon', SvgIcon) 5 | 6 | // require.context 读取指定目录的所有文件,参数说明: 7 | // 第一个:目录 8 | // 第二个:是否遍历子级目录 9 | // 第三个:定义遍历文件规则 10 | const req = require.context('./svg', false, /\.svg$/) 11 | const requireAll = requireContext => { 12 | return requireContext.keys().map(requireContext) 13 | } 14 | requireAll(req) -------------------------------------------------------------------------------- /src/store/modules/app.js: -------------------------------------------------------------------------------- 1 | const state = { 2 | isCollapse: JSON.parse(sessionStorage.getItem('isCollapse')) || false 3 | } 4 | 5 | const mutations = { 6 | SET_COLLAPSE(state) { 7 | state.isCollapse = !state.isCollapse 8 | sessionStorage.setItem('isCollapse', JSON.stringify(state.isCollapse)) 9 | } 10 | } 11 | 12 | export default { 13 | namespaced: true, 14 | state, 15 | mutations 16 | }; -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | 'extends': [ 7 | 'plugin:vue/essential', 8 | 'eslint:recommended' 9 | ], 10 | parserOptions: { 11 | parser: 'babel-eslint' 12 | }, 13 | rules: { 14 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 15 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off' 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/utils/formatData.js: -------------------------------------------------------------------------------- 1 | // 格式化数据 2 | 3 | // 时间戳->时间 4 | export function TimestampToDate(timestamp) { 5 | let now = new Date(timestamp * 1000); 6 | let year = now.getFullYear(); 7 | let month = now.getMonth() + 1; 8 | let date = now.getDate(); 9 | let hour = now.getHours(); 10 | let minute = now.getMinutes(); 11 | let second = now.getSeconds(); 12 | return year + "-" + month + "-" + date + " " + hour + ":" + minute + ":" + second; 13 | } -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | Vue.use(Vuex) 5 | 6 | import app from './modules/app' 7 | import login from './modules/login' 8 | import info from './modules/info' 9 | import common from "./modules/common"; 10 | import permission from './modules/permission' 11 | 12 | export default new Vuex.Store({ 13 | modules: { 14 | app, 15 | login, 16 | info, 17 | common, 18 | permission 19 | } 20 | }) 21 | -------------------------------------------------------------------------------- /src/views/Console/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /src/components/svgIcon/svg/zhankai.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/styles/main.scss: -------------------------------------------------------------------------------- 1 | @import "normalize"; 2 | @import "config"; 3 | @import "elementUI"; 4 | 5 | 6 | .pull-left { 7 | float: left; 8 | } 9 | 10 | .pull-right { 11 | float: right; 12 | } 13 | 14 | .display-none { 15 | display: none !important; 16 | } 17 | 18 | // 富文本编辑器样式 19 | .detailContent { 20 | .ql-toolbar.ql-snow .ql-formats { 21 | margin: 0 !important; 22 | } 23 | 24 | .el-form-item__content { 25 | line-height: 1.15; 26 | } 27 | } 28 | 29 | // 按钮组 30 | .btn-group { 31 | text-align: center; 32 | 33 | .el-button { 34 | width: 180px; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | import store from './store' 5 | 6 | import ElementUI from 'element-ui'; 7 | import 'element-ui/lib/theme-chalk/index.css'; 8 | import VueCompositionApi from '@vue/composition-api'; 9 | import 'components/svgIcon' // 引入自定义全局组件 - svg-icons 10 | import './router/guard' // 引入路由守卫 11 | import "./utils/btnPermission"; // 引入自定义指令按钮权限 12 | 13 | 14 | Vue.use(ElementUI); 15 | Vue.use(VueCompositionApi); 16 | 17 | Vue.config.productionTip = false 18 | 19 | new Vue({ 20 | router, 21 | store, 22 | render: h => h(App) 23 | }).$mount('#app') 24 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/utils/btnPermission.js: -------------------------------------------------------------------------------- 1 | // 自定义指令 2 | 3 | import Vue from "vue"; 4 | import store from "../store/index.js"; 5 | 6 | Vue.directive("btnPerm", { 7 | // 父级未渲染 8 | bind:function(el, binding){ 9 | // 操作DOM 10 | if(binding.def.hasBtnPerm(binding.value)) { 11 | el.className = el.className + " show-button"; 12 | } 13 | }, 14 | 15 | // 检测是否有权限的方法 16 | hasBtnPerm:function(permission){ 17 | const button = store.getters["login/buttonPermission"]; // 请求到的数据权限 18 | const roles = store.getters["login/roles"]; // 获取角色 19 | if(roles.includes("admin")) { return true } 20 | return button.indexOf(permission) !== -1; // 21 | } 22 | }) 23 | 24 | -------------------------------------------------------------------------------- /src/views/Layout/components/layoutMain.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 18 | 19 | -------------------------------------------------------------------------------- /src/utils/cookies.js: -------------------------------------------------------------------------------- 1 | // ------------ cookies处理 ------------ 2 | 3 | import cookie from "cookie_js"; 4 | 5 | const adminToKen = "admin_toKen"; 6 | const usernameKey = "username"; 7 | 8 | export function getToKen() { 9 | return cookie.get(adminToKen); 10 | } 11 | 12 | export function setToKen(toKen) { 13 | return cookie.set(adminToKen, toKen); 14 | } 15 | 16 | export function removeToKen() { 17 | return cookie.remove(adminToKen); 18 | } 19 | 20 | export function setUserName(value) { 21 | return cookie.set(usernameKey, value); 22 | } 23 | 24 | export function getUserName() { 25 | return cookie.get(usernameKey); 26 | } 27 | 28 | export function removeUserName() { 29 | return cookie.remove(usernameKey); 30 | } -------------------------------------------------------------------------------- /src/components/svgIcon/svg/jianhaoshouqi.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/api/common.js: -------------------------------------------------------------------------------- 1 | import service from "../utils/interceptor" 2 | 3 | /** 4 | * 获取七牛云token 5 | * AK: 七牛云的密钥AK;type: string 6 | * SK: 七牛云的密钥AK;type: string 7 | * buckety: 七牛云储存空间名称;type: string 8 | */ 9 | export function QiniuToKen(data) { 10 | return service.request({ 11 | method: "post", 12 | url: "/uploadImgToken/", 13 | data 14 | }) 15 | } 16 | 17 | // 请求表格数据 18 | export function loadTableData(params) { 19 | return service.request({ 20 | method: params.method || "post", 21 | url: params.url, 22 | data: params.data || {} 23 | }) 24 | } 25 | 26 | // 获取省市区街关联 27 | export function GetCityPicker(data) { 28 | return service.request({ 29 | method: "post", 30 | url: "/cityPicker/", 31 | data 32 | }) 33 | } -------------------------------------------------------------------------------- /src/utils/validate.js: -------------------------------------------------------------------------------- 1 | // ------------ 表单验证相关内容 ------------ 2 | 3 | // 过滤字符串 4 | export function strScript(s) { 5 | const pattern = new RegExp("[`~!@#$^&*()=|{}':;,.<>/?!¥…()—【】‘;:”“。,、?]"); 6 | let rs = ""; 7 | for (let i = 0; i < s.length; i++) { 8 | rs = rs + s.substr(i, 1).replace(pattern, ''); 9 | } 10 | return rs; 11 | } 12 | 13 | // 验证邮箱 14 | export function emailRule(value) { 15 | let reg = /^([a-zA-Z]|[0-9])(\w|-)+@[a-zA-Z0-9]+\.([a-zA-Z]{2,4})$/; 16 | return !reg.test(value) 17 | } 18 | 19 | // 验证密码 20 | export function passwordRule( value) { 21 | let reg = /^(?!\D+$)(?![^a-zA-Z]+$)\S{6,20}$/ 22 | return !reg.test(value) 23 | } 24 | 25 | // 验证验证码 26 | export function codeRule(value) { 27 | let reg = /^[a-z0-9]{6}$/ 28 | return !reg.test(value) 29 | } 30 | -------------------------------------------------------------------------------- /src/components/table/tableLoadData.js: -------------------------------------------------------------------------------- 1 | import { reactive } from '@vue/composition-api'; 2 | import {loadTableData} from "../../api/common"; 3 | 4 | export function loadData(){ 5 | const tableData = reactive({ 6 | data: [], 7 | total: 0 8 | }) 9 | 10 | const initLoadData = ((params) => { 11 | let requestData = { 12 | url: params.url, 13 | method: params.method, 14 | data: params.data 15 | } 16 | loadTableData(requestData).then(response => { 17 | let responseData = response.data.data.data; 18 | tableData.data = responseData; 19 | tableData.total = responseData.length === 0 ? 0 : response.data.data.total 20 | }) 21 | }) 22 | 23 | return { 24 | tableData, initLoadData 25 | } 26 | } -------------------------------------------------------------------------------- /src/api/login.js: -------------------------------------------------------------------------------- 1 | // ------------ 登录模块接口api ------------ 2 | import service from "../utils/interceptor" 3 | 4 | // 获取验证码 5 | export function GetSms(data) { 6 | return service.request({ 7 | method: "post", 8 | url: "/getSms/", 9 | data 10 | }) 11 | } 12 | 13 | // 登录 14 | export function Login(data) { 15 | return service.request({ 16 | method: 'post', 17 | url: '/login/', 18 | data 19 | }) 20 | } 21 | 22 | // 注册 23 | export function Register(data) { 24 | return service.request({ 25 | method: "post", 26 | url: "/register/", 27 | data 28 | }) 29 | } 30 | 31 | // 获取用户角色 32 | export function GetUserRole(data = {}) { 33 | return service.request({ 34 | method: "post", 35 | url: "/userRole/", 36 | data 37 | }) 38 | } 39 | -------------------------------------------------------------------------------- /src/components/svgIcon/svg/jiahaozhankai.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/utils/global.js: -------------------------------------------------------------------------------- 1 | // ------------ 全局方法 ------------ 2 | import {MessageBox} from 'element-ui'; 3 | 4 | export default function global() { 5 | // 确认弹窗 6 | const confirm = (params) => { 7 | MessageBox.confirm(params.content, params.tip || "提示", { 8 | confirmButtonText: "确定", 9 | cancelButtonText: "取消", 10 | type: params.type || "warning", 11 | center: true 12 | }).then(() => { 13 | MessageBox({ 14 | type: 'success', 15 | message: '删除成功!' 16 | }).then(); 17 | params.fn && params.fn() 18 | }).catch(() => { 19 | MessageBox({ 20 | type: 'info', 21 | message: '已取消删除' 22 | }).then(); 23 | params.catchFn && params.catchFn() 24 | }); 25 | } 26 | return {confirm} 27 | } 28 | 29 | -------------------------------------------------------------------------------- /src/components/svgIcon/svg/panel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-admin", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "@vue/composition-api": "^0.5.0", 12 | "axios": "^0.21.1", 13 | "cookie_js": "^1.4.0", 14 | "core-js": "^3.6.4", 15 | "element-ui": "^2.13.2", 16 | "js-sha1": "^0.6.0", 17 | "svg-sprite-loader": "^5.0.0", 18 | "vue": "^2.6.11", 19 | "vue-quill-editor": "^3.0.6", 20 | "vue-router": "^3.1.6", 21 | "vuex": "^3.1.3" 22 | }, 23 | "devDependencies": { 24 | "@vue/cli-plugin-babel": "~4.3.0", 25 | "@vue/cli-plugin-eslint": "~4.3.0", 26 | "@vue/cli-plugin-router": "~4.3.0", 27 | "@vue/cli-plugin-vuex": "~4.3.0", 28 | "@vue/cli-service": "~4.3.0", 29 | "babel-eslint": "^10.1.0", 30 | "eslint": "^6.7.2", 31 | "eslint-plugin-vue": "^6.2.2", 32 | "node-sass": "^4.12.0", 33 | "sass-loader": "^8.0.2", 34 | "vue-template-compiler": "^2.6.11" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/store/modules/info.js: -------------------------------------------------------------------------------- 1 | import {GetCategory} from '../../api/info' 2 | 3 | const state = { 4 | /** 5 | * 已经储存了值,不刷新页面的时候,值一直存在vuex 6 | * 刷新页面,就会去取session的值,赋值给变量 7 | */ 8 | id: '' || sessionStorage.getItem('infoId'), 9 | title: '' || sessionStorage.getItem('infoTitle') 10 | } 11 | 12 | const actions = { 13 | // 获取目录 14 | getInfoCategory() { 15 | return new Promise((resolve, reject) => { 16 | GetCategory().then(response => { 17 | resolve(response.data.data.data) 18 | }).catch(error => { 19 | reject(error) 20 | }) 21 | }) 22 | } 23 | } 24 | 25 | const mutations = { 26 | // 编辑的初始信息暂存 27 | UPDATE_STATE_VALUE(state, params) { 28 | for (let key in params) { 29 | state[key] = params[key].value 30 | // session存储 - 防止刷新就不显示 31 | if (params.key.session) { 32 | sessionStorage.setItem(params.key.sessionKey, params.key.value) 33 | } 34 | } 35 | } 36 | } 37 | 38 | export default { 39 | namespaced: true, 40 | state, 41 | mutations, 42 | actions 43 | }; -------------------------------------------------------------------------------- /src/components/svgIcon/svg/guanbi.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/utils/interceptor.js: -------------------------------------------------------------------------------- 1 | // ------------ 请求拦截器 ------------ 2 | 3 | import axios from 'axios' 4 | import {Message} from 'element-ui' // 前面element-ui是在vue中全局引入,我们js文件这边需要重新引入才能使用 5 | import {getToKen, getUserName} from './cookies' 6 | 7 | // 创建axios,赋给变量service - 基本配置 8 | const BASEURL = process.env.NODE_ENV === 'production' ? '' : '/api'; 9 | const service = axios.create({ 10 | baseURL: BASEURL, 11 | // baseURL: '/api', 12 | timeout: 8000, // 超时 13 | }); 14 | 15 | // 添加请求拦截器 16 | service.interceptors.request.use(function (config) { 17 | // 在发送请求之前添加token等头信息 18 | config.headers['Tokey'] = getToKen() 19 | config.headers['UserName'] = getUserName() 20 | return config; 21 | }, function (error) { 22 | // 对请求错误做些什么 23 | return Promise.reject(error); 24 | }); 25 | 26 | // 添加响应拦截器 27 | service.interceptors.response.use(function (response) { 28 | // 对响应数据做点什么 29 | let data = response.data 30 | if (data["resCode"] !== 0) { 31 | Message.error(data.message) 32 | return Promise.reject(data); 33 | } else { 34 | return response; 35 | } 36 | }, function (error) { 37 | // 对响应错误做点什么 38 | return Promise.reject(error); 39 | }); 40 | 41 | export default service; -------------------------------------------------------------------------------- /src/components/svgIcon/svgIcon.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 37 | 38 | -------------------------------------------------------------------------------- /src/api/user.js: -------------------------------------------------------------------------------- 1 | import service from "../utils/interceptor"; 2 | 3 | // 用户列表 4 | export function UserList(data = {}) { 5 | return service.request({ 6 | method: "post", 7 | url: "/user/getList", 8 | data 9 | }) 10 | } 11 | 12 | // 用户添加 13 | export function UserAdd(data = {}) { 14 | return service.request({ 15 | method: "post", 16 | url: "/user/add/", 17 | data 18 | }) 19 | } 20 | 21 | // 用户编辑 22 | export function UserEdit(data) { 23 | return service.request({ 24 | method: "post", 25 | url: "/user/edit/", 26 | data 27 | }) 28 | } 29 | 30 | // 用户删除 31 | export function UserDel(data) { 32 | return service.request({ 33 | method: "post", 34 | url: "/user/delete/", 35 | data 36 | }) 37 | } 38 | 39 | // 用户禁启用 40 | export function UserActives(data) { 41 | return service.request({ 42 | method: "post", 43 | url: "/user/actives/", 44 | data 45 | }) 46 | } 47 | 48 | // 获取系统 49 | export function GetSystem(data = {}) { 50 | return service.request({ 51 | method: "post", 52 | url: "/system/", 53 | data 54 | }) 55 | } 56 | 57 | // 获取角色 58 | export function GetRole(data = {}) { 59 | return service.request({ 60 | method: "post", 61 | url: "/role/", 62 | data 63 | }) 64 | } 65 | 66 | // 获取按钮权限 67 | export function GetPermButton(data = {}) { 68 | return service.request({ 69 | method: "post", 70 | url: "/permButton/", 71 | data 72 | }) 73 | } -------------------------------------------------------------------------------- /src/components/select/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 54 | 55 | -------------------------------------------------------------------------------- /src/store/modules/login.js: -------------------------------------------------------------------------------- 1 | import {Login} from '../../api/login.js' 2 | import {setToKen, setUserName, getUserName, removeToKen, removeUserName} from "../../utils/cookies"; 3 | 4 | 5 | const state = { 6 | toKen: '', 7 | userName: getUserName() || '', 8 | roles: [], // 用户角色 9 | buttonPermission: [] 10 | } 11 | const mutations = { 12 | SET_TOKEN(state, value) { 13 | state.toKen = value 14 | }, 15 | SET_USERNAME(state, value) { 16 | state.userName = value 17 | }, 18 | SET_ROLES(state, value) { 19 | state.roles = value 20 | }, 21 | SET_BUTTON(state, value) { 22 | state.buttonPermission = value; 23 | } 24 | } 25 | const getters = { 26 | roles: state => state.roles, 27 | buttonPermission: state => state.buttonPermission 28 | } 29 | const actions = { 30 | login(content, requestData) { 31 | return new Promise((resolve, reject) => { 32 | Login(requestData).then((response) => { 33 | content.commit('SET_TOKEN', response.data.data.token) 34 | content.commit('SET_USERNAME', response.data.data.username) 35 | setToKen(response.data.data.token) 36 | setUserName(response.data.data.username) 37 | resolve(response) 38 | }).catch((error) => { 39 | reject(error) 40 | }) 41 | }) 42 | }, 43 | exit(content) { 44 | return new Promise((resolve) => { 45 | removeToKen(); 46 | removeUserName(); 47 | content.commit('SET_TOKEN', ''); 48 | content.commit('SET_USERNAME', ''); 49 | content.commit('SET_ROLES', []); 50 | resolve(); 51 | }) 52 | } 53 | } 54 | 55 | export default { 56 | namespaced: true, 57 | state, 58 | getters, 59 | mutations, 60 | actions 61 | }; -------------------------------------------------------------------------------- /src/views/Layout/components/layoutNav.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 34 | 35 | -------------------------------------------------------------------------------- /src/views/Layout/index.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 34 | 35 | -------------------------------------------------------------------------------- /src/store/modules/permission.js: -------------------------------------------------------------------------------- 1 | // 权限相关处理 2 | import {GetUserRole} from "../../api/login"; 3 | import {asyncRouterMap, defaultRouterMap} from "../../router"; 4 | 5 | function filterRouter(roles, router) { 6 | return router.filter(item => { 7 | if (hasPermission(roles, item.meta.role)) { 8 | if (item.children && item.children.length > 0) { 9 | item.children = filterRouter(roles, item.children) 10 | } 11 | return item 12 | } 13 | }) 14 | } 15 | 16 | function hasPermission(roles, itemRoles) { 17 | // some()方法用于检测数组中的元素是否满足指定条件,如果有一个元素满足条件,则表达式返回true,剩余的元素不会再执行检测 18 | return roles.some(item => itemRoles.indexOf(item) >= 0) 19 | } 20 | 21 | const state = { 22 | allRouters: defaultRouterMap, 23 | addRouters: [] 24 | } 25 | const getters = { 26 | allRouters: state => state.allRouters, // 所有的路由 27 | addRouters: state => state.addRouters, // 匹配(动态添加)的路由 28 | } 29 | const mutations = { 30 | SET_ROUTER(state, addRouters) { 31 | state.addRouters = addRouters 32 | state.allRouters = defaultRouterMap.concat(addRouters, asyncRouterMap.slice(-1)) 33 | } 34 | } 35 | const actions = { 36 | // 获取用户角色 37 | getRoles() { 38 | return new Promise(resolve => { 39 | GetUserRole().then(response => { 40 | resolve(response.data.data) 41 | }) 42 | }) 43 | }, 44 | // 按角色分配路由权限 45 | createRouter(content, roles) { 46 | return new Promise(resolve => { 47 | let addRouters; 48 | if (roles.includes('admin')) { 49 | // 超级管理员 50 | addRouters = asyncRouterMap 51 | } else { 52 | // 普通管理员 53 | addRouters = filterRouter(roles, asyncRouterMap) 54 | } 55 | // 更新路由 56 | content.commit('SET_ROUTER', addRouters) 57 | resolve() 58 | }) 59 | } 60 | } 61 | 62 | export default { 63 | namespaced: true, 64 | state, 65 | getters, 66 | actions, 67 | mutations 68 | }; -------------------------------------------------------------------------------- /src/api/info.js: -------------------------------------------------------------------------------- 1 | // ------------ 信息管理接口api ------------ 2 | import service from "../utils/interceptor" 3 | 4 | // 获取信息分类(包含子级) 5 | export function GetCategoryAll(data) { 6 | return service.request({ 7 | method: "post", 8 | url: "/news/getCategoryAll/", 9 | data 10 | }) 11 | } 12 | 13 | // 获取信息分类 14 | export function GetCategory(data) { 15 | return service.request({ 16 | method: "post", 17 | url: "/news/getCategory/", 18 | data 19 | }) 20 | } 21 | 22 | 23 | // 信息分类添加一级 24 | export function AddFirstCategory(data) { 25 | return service.request({ 26 | method: "post", 27 | url: "/news/addFirstCategory/", 28 | data 29 | }) 30 | } 31 | 32 | // 信息分类添加子级 33 | export function AddChildrenCategory(data) { 34 | return service.request({ 35 | method: "post", 36 | url: "/news/addChildrenCategory/", 37 | data 38 | }) 39 | } 40 | 41 | // 删除信息分类 42 | export function DeleteCategory(data) { 43 | return service.request({ 44 | method: "post", 45 | url: "/news/deleteCategory/", 46 | data 47 | }) 48 | } 49 | 50 | // 修改信息分类 51 | export function EditCategory(data) { 52 | return service.request({ 53 | method: "post", 54 | url: "/news/editCategory/", 55 | data 56 | }) 57 | } 58 | 59 | // 获取信息列表 60 | export function GetList(data) { 61 | return service.request({ 62 | method: "post", 63 | url: "/news/getList/", 64 | data 65 | }) 66 | } 67 | 68 | // 添加信息 69 | export function AddInfo(data) { 70 | return service.request({ 71 | method: "post", 72 | url: "/news/add/", 73 | data 74 | }) 75 | } 76 | 77 | // 修改信息 78 | export function EditInfo(data) { 79 | return service.request({ 80 | method: "post", 81 | url: "/news/editInfo/", 82 | data 83 | }) 84 | } 85 | 86 | // 删除信息 87 | export function DeleteInfo(data) { 88 | return service.request({ 89 | method: "post", 90 | url: "/news/deleteInfo/", 91 | data 92 | }) 93 | } 94 | -------------------------------------------------------------------------------- /src/router/guard.js: -------------------------------------------------------------------------------- 1 | // 路由守卫 2 | import router from "./index" 3 | import store from "../store/index" 4 | import {getToKen, removeToKen, removeUserName} from "../utils/cookies"; 5 | 6 | // 设置白名单 7 | const whiteRouter = ['/login']; 8 | 9 | /** 10 | * 1、直接进入index的时候,参数to被改变成了 "/index",触发路由指向,就会跑beforeEach 11 | * 2、再一次 next 指向了login,再次发生路由指向,再跑beforeEach,参数的to被改变成了"/login" 12 | * 3、白名单判断存在,则直接执行next(),因为没有参数,所以不会再次beforeEach。 13 | */ 14 | 15 | router.beforeEach((to, from, next) => { 16 | if (getToKen()) { 17 | // token 存在 18 | if (to.path === '/login') { 19 | removeToKen() 20 | removeUserName() 21 | store.commit('login/SET_TOKEN', '') 22 | store.commit('login/SET_USERNAME', '') 23 | } else { 24 | // 获取用户角色然后动态分配路由权限 25 | if (store.getters['login/roles'].length === 0) { 26 | // 用户角色为空就需要获取 27 | store.dispatch('permission/getRoles').then(response => { 28 | let roles = response.role; 29 | // let button = response.button; // 按钮权限 30 | let btnPerm = response.btnPerm; // 允许的按钮 31 | store.commit('login/SET_ROLES', roles) 32 | store.commit("login/SET_BUTTON", btnPerm); 33 | // 动态分配路由 34 | store.dispatch('permission/createRouter', roles).then(() => { 35 | let allRouters = store.getters['permission/allRouters']; 36 | let addRouters = store.getters['permission/addRouters']; 37 | // 路由更新 38 | router.options.routes = allRouters; 39 | // 添加动态路由 40 | router.addRoutes(addRouters) 41 | next({...to, replace: true}); 42 | }) 43 | }) 44 | } else { 45 | // 用户角色已获取 46 | next(); 47 | } 48 | } 49 | } else { 50 | // token不存在 - 跳转登录页面 51 | if (whiteRouter.indexOf(to.path) !== -1) { 52 | next() 53 | } else { 54 | next('/login') 55 | } 56 | } 57 | }) -------------------------------------------------------------------------------- /src/styles/elementUI.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 导航菜单 - layout-nav 3 | */ 4 | // 展开样式 5 | .el-menu { 6 | border-right: none; 7 | 8 | .el-submenu__title { 9 | color: white; 10 | 11 | i { 12 | color: white 13 | } 14 | 15 | &:hover { 16 | background-color: $color-second !important; 17 | } 18 | } 19 | 20 | .el-menu-item { 21 | color: white; 22 | 23 | &.is-active { 24 | background-color: rgba(245, 100, 108, .2) !important; 25 | //font-size: 16px; 26 | @include webkit(transition, all .2s ease 0s); 27 | } 28 | } 29 | 30 | .el-menu--inline { 31 | background-color: #2f4357 !important; 32 | 33 | .el-menu-item:hover { 34 | background-color: rgba(245, 100, 108, .2) !important; 35 | } 36 | } 37 | } 38 | 39 | // 折叠样式 40 | .el-menu--vertical { 41 | .el-menu { 42 | min-width: 120px; 43 | background-color: #2f4357 !important; 44 | } 45 | 46 | .el-menu--popup { 47 | padding: 0; 48 | } 49 | 50 | .el-menu--popup-right-start { 51 | margin-left: 0; 52 | } 53 | 54 | .el-menu-item { 55 | text-align: center; 56 | padding: 0 !important; 57 | 58 | &.is-active { 59 | background-color: rgba(245, 100, 108, .2) !important; 60 | } 61 | 62 | &:hover { 63 | background-color: rgba(245, 100, 108, .2) !important; 64 | } 65 | } 66 | } 67 | 68 | /** 69 | * 表单样式 - infoIndex 70 | */ 71 | .el-form-item__label { 72 | font-size: 14px; 73 | } 74 | 75 | .el-date-editor--daterange.el-input__inner { 76 | width: 100%; 77 | } 78 | 79 | .el-form-item { 80 | margin-bottom: 22px; 81 | } 82 | 83 | /** 84 | * 表格样式 - infoIndex 85 | */ 86 | div.el-table { 87 | td, th { 88 | text-align: center; 89 | } 90 | 91 | th { 92 | font-weight: bold; 93 | color: #344a5f; 94 | } 95 | 96 | td { 97 | font-size: 14px; 98 | } 99 | } 100 | 101 | .el-table td, .el-table th { 102 | padding: 10px 0; 103 | } 104 | 105 | .info-table .el-table__body .el-table__row { 106 | td:nth-child(3) { 107 | .cell { 108 | overflow: hidden; 109 | text-overflow: ellipsis; 110 | white-space: nowrap; 111 | } 112 | } 113 | } 114 | 115 | -------------------------------------------------------------------------------- /src/components/svgIcon/svg/yonghu.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/views/Layout/components/layoutHeader.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 56 | 57 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | // 基本路径 5 | publicPath: process.env.NODE_ENV === 'production' ? '' : '/', 6 | // 输出文件目录 7 | outputDir: process.env.NODE_ENV === 'production' ? 'dist' : 'devdist', 8 | // eslint-loader 是否在保存的时候检查 9 | lintOnSave: true, 10 | // webpack配置,see https://github.com/vuejs/vue-cli/blob/dev/docs/webpack.md 11 | chainWebpack: (config) => { 12 | const svgRule = config.module.rule("svg"); 13 | svgRule.uses.clear(); 14 | svgRule 15 | .use("svg-sprite-loader") 16 | .loader("svg-sprite-loader") 17 | .options({ 18 | symbolId: "icon-[name]", 19 | include: ["./src/components/icons"] 20 | }); 21 | }, 22 | configureWebpack: (config) => { 23 | config.resolve = { // 配置解析别名 24 | extensions: ['.js', '.json', '.vue'], 25 | alias: { 26 | '@': path.resolve(__dirname, './src'), 27 | 'public': path.resolve(__dirname, './public'), 28 | 'components': path.resolve(__dirname, './src/components'), 29 | 'common': path.resolve(__dirname, './src/common'), 30 | 'api': path.resolve(__dirname, './src/api'), 31 | 'views': path.resolve(__dirname, './src/views'), 32 | 'data': path.resolve(__dirname, './src/data'), 33 | 'vue': 'vue/dist/vue.esm.js', 34 | } 35 | } 36 | }, 37 | // 生产环境是否生成 sourceMap 文件 38 | productionSourceMap: false, 39 | // css相关配置 40 | css: { 41 | extract: true, // 是否使用css分离插件 ExtractTextPlugin 42 | sourceMap: false, // 开启 CSS source maps 43 | loaderOptions: { 44 | scss: { 45 | prependData: `@import "./src/styles/main.scss";` 46 | } 47 | }, 48 | requireModuleExtension: true // 启用 CSS modules for all css / pre-processor files. 49 | }, 50 | // use thread-loader for babel & TS in production build enabled by default if the machine has more than 1 cores 51 | parallel: require('os').cpus().length > 1, 52 | // webpack-dev-server 相关配置 53 | devServer: { 54 | open: false, // 编译完成是否打开网页 55 | host: '0.0.0.0', // 指定使用地址,默认localhost,0.0.0.0代表可以被外界访问 56 | port: 8080, // 访问端口 57 | https: false, // 编译失败时刷新页面 58 | hot: true, // 开启热加载 59 | hotOnly: false, 60 | proxy: { // 设置代理 61 | '/api': { 62 | target: "http://www.web-jshtml.cn/productapi/token", // API服务器的地址 63 | changeOrigin: true, 64 | pathRewrite: { 65 | '^/api': '' 66 | } 67 | } 68 | }, 69 | overlay: { // 全屏模式下是否显示脚本错误 70 | warnings: true, 71 | errors: true 72 | }, 73 | before: app => { 74 | } 75 | }, 76 | // 第三方插件配置 77 | pluginOptions: {} 78 | } -------------------------------------------------------------------------------- /src/components/svgIcon/svg/xinxi.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/uploadImg/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 91 | 92 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from 'vue-router' 3 | 4 | Vue.use(VueRouter) 5 | 6 | const layout = () => import('../views/Layout/index'); 7 | 8 | // 默认路由 - 所有权限都能访问 9 | export const defaultRouterMap = [ 10 | { 11 | path: '/', 12 | name: 'Index', 13 | redirect: 'login', 14 | hidden: true 15 | }, 16 | { 17 | path: '/login', 18 | name: 'Login', 19 | hidden: true, 20 | component: () => import('../views/Login/index') 21 | }, 22 | { 23 | path: '/console', 24 | name: 'Console', 25 | redirect: '/homeIndex', 26 | component: layout, 27 | meta: { 28 | name: '控制台', 29 | icon: 'panel' 30 | }, 31 | children: [ 32 | { 33 | path: '/homeIndex', 34 | meta: { 35 | keepAlive: true, 36 | name: '首页' 37 | }, 38 | component: () => import('../views/Console/index') 39 | } 40 | ] 41 | }, 42 | { 43 | path: '/404', 44 | name: 'page404', 45 | hidden: true, 46 | component: layout, 47 | children: [ 48 | { 49 | path: '/404', 50 | component: () => import('../views/404') 51 | } 52 | ] 53 | }, 54 | ] 55 | 56 | // 动态路由 - 根据权限设定访问 57 | export const asyncRouterMap = [ 58 | { 59 | path: '/info', 60 | name: 'info', 61 | component: layout, 62 | meta: { 63 | role: ['sale', 'manager'], 64 | name: '信息管理', 65 | icon: 'xinxi' 66 | }, 67 | children: [ 68 | { 69 | path: '/infoIndex', 70 | component: () => import('../views/Info/index'), 71 | meta: { 72 | keepAlive: true, 73 | role: ['sale', 'manager'], 74 | name: '信息列表' 75 | } 76 | }, 77 | { 78 | path: '/infoCategory', 79 | component: () => import('../views/Info/category'), 80 | meta: { 81 | keepAlive: true, 82 | role: ['sale'], 83 | name: '信息分类' 84 | } 85 | }, 86 | { 87 | path: '/infoDetailed', 88 | name: 'infoDetailed', 89 | hidden: true, 90 | component: () => import('../views/Info/detailed'), 91 | meta: { 92 | keepAlive: true, 93 | role: ['sale'], 94 | name: '信息详情' 95 | } 96 | } 97 | ] 98 | }, 99 | { 100 | path: '/user', 101 | name: 'User', 102 | component: layout, 103 | meta: { 104 | role: ['manager'], 105 | name: '用户管理', 106 | icon: 'yonghu' 107 | }, 108 | children: [ 109 | { 110 | path: "/userIndex", 111 | component: () => import('../views/User/index'), 112 | meta: { 113 | keepAlive: true, 114 | role: ['manager'], 115 | name: '用户列表' 116 | } 117 | } 118 | ] 119 | }, 120 | { 121 | path: '*', 122 | redirect: '404', 123 | hidden: true 124 | }, 125 | ] 126 | 127 | const router = new VueRouter({ 128 | mode: 'hash', 129 | routes: defaultRouterMap 130 | }) 131 | 132 | export default router 133 | -------------------------------------------------------------------------------- /src/components/dialog/infoAdd.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 103 | 104 | -------------------------------------------------------------------------------- /src/components/dialog/infoEdit.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 123 | 124 | -------------------------------------------------------------------------------- /src/components/cityPicker/index.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 129 | 130 | -------------------------------------------------------------------------------- /src/components/svgIcon/svg/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/table/index.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 140 | 141 | -------------------------------------------------------------------------------- /src/views/Info/detailed.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 147 | 148 | -------------------------------------------------------------------------------- /src/views/User/index.vue: -------------------------------------------------------------------------------- 1 | 56 | 57 | 231 | 232 | -------------------------------------------------------------------------------- /src/styles/normalize.scss: -------------------------------------------------------------------------------- 1 | /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ 2 | 3 | /* Document 4 | ========================================================================== */ 5 | 6 | /** 7 | * 1. Correct the line height in all browsers. 8 | * 2. Prevent adjustments of font size after orientation changes in iOS. 9 | */ 10 | html, body, div, span, applet, object, iframe, 11 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 12 | a, abbr, acronym, address, big, cite, code, 13 | del, dfn, em, img, ins, kbd, q, s, samp, 14 | small, strike, strong, sub, sup, tt, var, 15 | b, u, i, center, 16 | dl, dt, dd, ol, ul, 17 | fieldset, form, legend, 18 | table, caption, tbody, tfoot, thead, tr, th, td, 19 | article, aside, canvas, details, embed, 20 | figure, figcaption, footer, header, hgroup, 21 | menu, nav, output, ruby, section, summary, 22 | time, mark, audio, video { 23 | margin: 0; 24 | padding: 0; 25 | //border: 0; 26 | vertical-align: baseline; 27 | } 28 | /* HTML5 display-role reset for older browsers */ 29 | article, aside, details, figcaption, figure, 30 | footer, header, hgroup, menu, nav, section { 31 | //display: block; 32 | } 33 | html { 34 | line-height: 1.15; /* 1 */ 35 | -webkit-text-size-adjust: 100%; /* 2 */ 36 | } 37 | 38 | /* Sections 39 | ========================================================================== */ 40 | 41 | /** 42 | * Remove the margin in all browsers. 43 | */ 44 | 45 | body { 46 | margin: 0; 47 | } 48 | 49 | /** 50 | * Render the `main` element consistently in IE. 51 | */ 52 | 53 | main { 54 | display: block; 55 | } 56 | 57 | /** 58 | * Correct the font size and margin on `h1` elements within `section` and 59 | * `article` contexts in Chrome, Firefox, and Safari. 60 | */ 61 | 62 | 63 | /* Grouping content 64 | ========================================================================== */ 65 | 66 | /** 67 | * 1. Add the correct box sizing in Firefox. 68 | * 2. Show the overflow in Edge and IE. 69 | */ 70 | 71 | hr { 72 | box-sizing: content-box; /* 1 */ 73 | height: 0; /* 1 */ 74 | overflow: visible; /* 2 */ 75 | } 76 | 77 | /** 78 | * 1. Correct the inheritance and scaling of font size in all browsers. 79 | * 2. Correct the odd `em` font sizing in all browsers. 80 | */ 81 | 82 | pre { 83 | font-family: monospace, monospace; /* 1 */ 84 | font-size: 1em; /* 2 */ 85 | } 86 | 87 | /* Text-level semantics 88 | ========================================================================== */ 89 | 90 | /** 91 | * Remove the gray background on active links in IE 10. 92 | */ 93 | 94 | a { 95 | background-color: transparent; 96 | } 97 | 98 | /** 99 | * 1. Remove the bottom border in Chrome 57- 100 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. 101 | */ 102 | 103 | abbr[title] { 104 | border-bottom: none; /* 1 */ 105 | text-decoration: underline; /* 2 */ 106 | text-decoration: underline dotted; /* 2 */ 107 | } 108 | 109 | /** 110 | * Add the correct font weight in Chrome, Edge, and Safari. 111 | */ 112 | 113 | b, 114 | strong { 115 | font-weight: bolder; 116 | } 117 | 118 | /** 119 | * 1. Correct the inheritance and scaling of font size in all browsers. 120 | * 2. Correct the odd `em` font sizing in all browsers. 121 | */ 122 | 123 | code, 124 | kbd, 125 | samp { 126 | font-family: monospace, monospace; /* 1 */ 127 | font-size: 1em; /* 2 */ 128 | } 129 | 130 | /** 131 | * Add the correct font size in all browsers. 132 | */ 133 | 134 | small { 135 | font-size: 80%; 136 | } 137 | 138 | /** 139 | * Prevent `sub` and `sup` elements from affecting the line height in 140 | * all browsers. 141 | */ 142 | 143 | sub, 144 | sup { 145 | font-size: 75%; 146 | line-height: 0; 147 | position: relative; 148 | vertical-align: baseline; 149 | } 150 | 151 | sub { 152 | bottom: -0.25em; 153 | } 154 | 155 | sup { 156 | top: -0.5em; 157 | } 158 | 159 | /* Embedded content 160 | ========================================================================== */ 161 | 162 | /** 163 | * Remove the border on images inside links in IE 10. 164 | */ 165 | 166 | img { 167 | border-style: none; 168 | } 169 | 170 | /* Forms 171 | ========================================================================== */ 172 | 173 | /** 174 | * 1. Change the font styles in all browsers. 175 | * 2. Remove the margin in Firefox and Safari. 176 | */ 177 | 178 | button, 179 | input, 180 | optgroup, 181 | select, 182 | textarea { 183 | font-family: inherit; /* 1 */ 184 | //font-size: 100%; /* 1 */ 185 | //line-height: 1.15; /* 1 */ 186 | margin: 0; /* 2 */ 187 | } 188 | 189 | /** 190 | * Show the overflow in IE. 191 | * 1. Show the overflow in Edge. 192 | */ 193 | 194 | button, 195 | input { /* 1 */ 196 | overflow: visible; 197 | } 198 | 199 | /** 200 | * Remove the inheritance of text transform in Edge, Firefox, and IE. 201 | * 1. Remove the inheritance of text transform in Firefox. 202 | */ 203 | 204 | button, 205 | select { /* 1 */ 206 | text-transform: none; 207 | } 208 | 209 | /** 210 | * Correct the inability to style clickable types in iOS and Safari. 211 | */ 212 | 213 | button, 214 | [type="button"], 215 | [type="reset"], 216 | [type="submit"] { 217 | -webkit-appearance: button; 218 | } 219 | 220 | /** 221 | * Remove the inner border and padding in Firefox. 222 | */ 223 | 224 | button::-moz-focus-inner, 225 | [type="button"]::-moz-focus-inner, 226 | [type="reset"]::-moz-focus-inner, 227 | [type="submit"]::-moz-focus-inner { 228 | border-style: none; 229 | padding: 0; 230 | } 231 | 232 | /** 233 | * Restore the focus styles unset by the previous rule. 234 | */ 235 | 236 | button:-moz-focusring, 237 | [type="button"]:-moz-focusring, 238 | [type="reset"]:-moz-focusring, 239 | [type="submit"]:-moz-focusring { 240 | outline: 1px dotted ButtonText; 241 | } 242 | 243 | /** 244 | * Correct the padding in Firefox. 245 | */ 246 | 247 | fieldset { 248 | padding: 0.35em 0.75em 0.625em; 249 | } 250 | 251 | /** 252 | * 1. Correct the text wrapping in Edge and IE. 253 | * 2. Correct the color inheritance from `fieldset` elements in IE. 254 | * 3. Remove the padding so developers are not caught out when they zero out 255 | * `fieldset` elements in all browsers. 256 | */ 257 | 258 | legend { 259 | box-sizing: border-box; /* 1 */ 260 | color: inherit; /* 2 */ 261 | display: table; /* 1 */ 262 | max-width: 100%; /* 1 */ 263 | padding: 0; /* 3 */ 264 | white-space: normal; /* 1 */ 265 | } 266 | 267 | /** 268 | * Add the correct vertical alignment in Chrome, Firefox, and Opera. 269 | */ 270 | 271 | progress { 272 | vertical-align: baseline; 273 | } 274 | 275 | /** 276 | * Remove the default vertical scrollbar in IE 10+. 277 | */ 278 | 279 | textarea { 280 | overflow: auto; 281 | } 282 | 283 | /** 284 | * 1. Add the correct box sizing in IE 10. 285 | * 2. Remove the padding in IE 10. 286 | */ 287 | 288 | [type="checkbox"], 289 | [type="radio"] { 290 | box-sizing: border-box; /* 1 */ 291 | padding: 0; /* 2 */ 292 | } 293 | 294 | /** 295 | * Correct the cursor style of increment and decrement buttons in Chrome. 296 | */ 297 | 298 | [type="number"]::-webkit-inner-spin-button, 299 | [type="number"]::-webkit-outer-spin-button { 300 | height: auto; 301 | } 302 | 303 | /** 304 | * 1. Correct the odd appearance in Chrome and Safari. 305 | * 2. Correct the outline style in Safari. 306 | */ 307 | 308 | [type="search"] { 309 | -webkit-appearance: textfield; /* 1 */ 310 | outline-offset: -2px; /* 2 */ 311 | } 312 | 313 | /** 314 | * Remove the inner padding in Chrome and Safari on macOS. 315 | */ 316 | 317 | [type="search"]::-webkit-search-decoration { 318 | -webkit-appearance: none; 319 | } 320 | 321 | /** 322 | * 1. Correct the inability to style clickable types in iOS and Safari. 323 | * 2. Change font properties to `inherit` in Safari. 324 | */ 325 | 326 | ::-webkit-file-upload-button { 327 | -webkit-appearance: button; /* 1 */ 328 | font: inherit; /* 2 */ 329 | } 330 | 331 | /* Interactive 332 | ========================================================================== */ 333 | 334 | /* 335 | * Add the correct display in Edge, IE 10+, and Firefox. 336 | */ 337 | 338 | details { 339 | display: block; 340 | } 341 | 342 | /* 343 | * Add the correct display in all browsers. 344 | */ 345 | 346 | summary { 347 | display: list-item; 348 | } 349 | 350 | /* Misc 351 | ========================================================================== */ 352 | 353 | /** 354 | * Add the correct display in IE 10+. 355 | */ 356 | 357 | template { 358 | display: none; 359 | } 360 | 361 | /** 362 | * Add the correct display in IE 10. 363 | */ 364 | 365 | [hidden] { 366 | display: none; 367 | } 368 | 369 | ul, li { list-style: none; } -------------------------------------------------------------------------------- /src/components/dialog/userAdd.vue: -------------------------------------------------------------------------------- 1 | 52 | 53 | 262 | 263 | -------------------------------------------------------------------------------- /src/views/Login/index.vue: -------------------------------------------------------------------------------- 1 | 45 | 46 | 277 | 278 | -------------------------------------------------------------------------------- /src/views/Info/category.vue: -------------------------------------------------------------------------------- 1 | 51 | 52 | 266 | 267 | -------------------------------------------------------------------------------- /src/views/Info/index.vue: -------------------------------------------------------------------------------- 1 | 105 | 106 | 311 | 312 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vue3.0项目-简易后台管理系统 2 | 3 | 教程官网:http://www.web-jshtml.cn/#/ 视频地址:https://www.bilibili.com/video/BV1zJ411g7Fx 官方源码:[vue-admin](https://github.com/bigbigtime/vue-admin) 4 | 5 | **学习内容**:Vue+Vuex+Router+ElementUi+Webpack全家桶,VUE3.0体验版API,组件化开发,生命周期,路由权限,Sass,Axios拦截器,缓存,项目部署,Nginx,域名,服务器,GIT,原型,接口联调,性能,缓存等; 6 | 7 | **Vue学习**:VUECLI脚手架创建项目,ElementUI组件,VUE各项指令,Vuex、State、Getters、Mutations,component、props、propsData、computed,watch,cookie存储,sessionStorage存储、localStorage存储,路由守卫,检测Token 等等; 8 | 9 | **学习成效**:完全自主搭建后台管理系统,理清系统整体流程,掌握公司级项目开发流程(由原型、UI、项目开发、后台联调、测试工程师、BUG修复、项目部署、项目迭代),VUE组件化思想开发(核心之一),真实业务接口联调,路由权限(角色分配、系统分配、按钮级权限),Nginx项目部署转发,业务逻辑抽离,API接口模块化,GIT代码管理(基本的git命令),模块系统增、删、改、查(信息功能、用户功能),七牛云第三方存储,HTML5本地储存; 10 | 11 | ------ 12 | 13 | 14 | 15 | ## 第1课时 16 | 17 | ### 1.1 项目开发流程 18 | 19 | 20 | 21 | ### 1.2 业务流程图 22 | 23 | 24 | 25 | ### 1.3 程序流程图 26 | 27 | 28 | 29 | ### 1.4 测试用例 30 | 31 | 32 | 33 | ## 第2课时 34 | 35 | ### 2.1 环境安装 36 | 37 | 1. 安装**node.js** 38 | 39 | https://nodejs.org/en/download/ 40 | 41 | 查看版本号 node –v、npm –v 出现版本号即安装成功。(如未出现,请重启电脑后再试)node8.9或以上版本 42 | 43 | 2. 管理nodejs版本(非必装) 44 | 45 | 执行命令安装:npm install -g n 46 | 47 | n latest (升级node.js到最新版) 48 | 49 | n stable(升级node.js到最新稳定版) 50 | 51 | n 后面也可以跟随版本号,例如:$ n v0.10.26 或者 $ n 0.10.26 52 | 53 | 3. 全局安装**vue-cli3.0脚手架** 54 | 55 | 卸载:如果已经全局安装了旧版本的vue-cli(1.x 或 2.x),需要先卸载:npm uninstall vue-cli -g 56 | 57 | 安装:npm install -g @vue/cli 58 | 59 | 查看版本号:vue -V,出现版本号即安装成功。 60 | 61 | 3.0对2.0版本的桥接:npm install –g @vue/cli-init 62 | 63 | ### 2.2 建立项目仓库 64 | 65 | 66 | 67 | 注意,可以使用git可视化管理工具[GitKraken](https://www.gitkraken.com) - 使用教程[1](https://www.cnblogs.com/brifuture/p/8823253.html) [2](https://www.cnblogs.com/brifuture/p/8830797.html) [3](https://www.cnblogs.com/brifuture/p/8869952.html) [4](https://www.cnblogs.com/brifuture/p/8877048.html) [5](https://www.cnblogs.com/brifuture/p/8947031.html) [6](https://www.cnblogs.com/brifuture/p/8969526.html) [7](https://www.cnblogs.com/brifuture/p/9009747.html) [8](https://www.cnblogs.com/brifuture/p/9021362.html) [9](https://www.cnblogs.com/brifuture/p/9052512.html) 68 | 69 | ### 2.3 构建项目 70 | 71 | ```shell 72 | $ vue create vue-admin 73 | ``` 74 | 75 | 76 | 77 | ## 第3课时 78 | 79 | ### 3.1 Vue-cli 80 | 81 | 82 | 83 | ### 3.2 vue.config.js 84 | 85 | > `vue.config.js` 是一个可选的配置文件,如果项目的 (和 `package.json` 同级的) 根目录中存在这个文件,那么它会被 `@vue/cli-service` 自动加载。你也可以使用 `package.json` 中的 `vue` 字段,但是注意这种写法需要你严格遵照 JSON 的格式来写。区别于vue2.0-cli中在webpack.config.js中进行配置。 86 | 87 | ```js 88 | // vue.config.js 89 | module.exports = { 90 | // 配置选项... 91 | } 92 | ``` 93 | 94 | ### 3.3 路由懒加载 95 | 96 | > 路由懒加载指的是当路由被访问的时候才加载对应组件。 97 | 98 | 传统路由写法: 99 | 100 | ```js 101 | import Home from "../views/Home.vue"; 102 | Vue.use([ 103 | { 104 | path:'/', 105 | name:'Home', 106 | component: Home 107 | } 108 | ]); 109 | ``` 110 | 111 | 路由懒加载写法: 112 | 113 | ```js 114 | Vue.use([ 115 | { 116 | path:'/', 117 | name:'Home', 118 | component: ()=> import('../views/Home.vue') 119 | } 120 | ]); 121 | ``` 122 | 123 | ### 3.4 路由重定向redirect 124 | 125 | ```js 126 | // 从 /a 重定向到 /b 127 | const router = new VueRouter({ 128 | routes: [ 129 | { path: '/a', redirect: '/b' } 130 | ] 131 | }) 132 | // 重定向到名字为foo的组件 133 | const router = new VueRouter({ 134 | routes: [ 135 | { path: '/a', redirect: { name: 'foo' }} 136 | ] 137 | }) 138 | ``` 139 | 140 | ### 3.5 Element-UI 141 | 142 | 官网:[Element-UI](https://element.eleme.cn/#/zh-CN) 143 | 144 | 1. **安装依赖** - `npm i element-ui --S` 145 | 2. **全局引入** 146 | 147 | ```js 148 | import ElementUI from 'element-ui'; 149 | import 'element-ui/lib/theme-chalk/index.css'; 150 | Vue.use(ElementUI); 151 | ``` 152 | 153 | ## 第4课时 154 | 155 | ### 4.1 Vue文件规则 156 | 157 | 158 | 159 | ### 4.2 Vue基本指令 160 | 161 | - **v-model**:在表单控件或者组件上创建双向绑定。 162 | - **v-bind** **绑定属性**:绑定方式v-bind:attributeName 或 :attributeName 163 | - **v-for**:基于源数据多次渲染元素或模板块,添加在循环出现的模版上而不是父元素 164 | 165 | ```vue 166 |
167 | {{ item.text }} 168 |
169 | ``` 170 | 171 | - **v-show** / **v-if** 172 | - v-show:在元素中添加 display,隐藏DOM元素 173 | - v-if:直接删除DOM元素。DOM元素中有接口时,当v-if值为true时,会请求接口。 174 | 175 | ### 4.3 :class的绑定方式 176 | 177 | 详见 [官方文档-Class 与 Style 绑定](https://cn.vuejs.org/v2/guide/class-and-style.html) 178 | 179 | 1. 基本方法 180 | 181 | ```vue 182 |
183 | 192 | ``` 193 | 194 | 2. 表达式 195 | 196 | ```vue 197 |
198 | 207 | ``` 208 | 209 | 3. 多个绑定 210 | 211 | ```vue 212 | // 方法1 213 |
214 | 224 | 225 | // 方法2 226 |
227 | 236 | ``` 237 | 238 | 4. 数组绑定 - **数组为具体的类名,而上面的对象通过true/false判断** 239 | 240 | ```vue 241 |
242 | 252 | ``` 253 | 254 | 5. 多种方式结合 255 | 256 | ```vue 257 |
258 | 268 | ``` 269 | 270 | ## 第5课时 271 | 272 | ### 5.1 表单验证规则 273 | 274 | - 验证邮箱:let reg = /^([a-zA-Z]|[0-9])(\w|\-)+@[a-zA-Z0-9]+\.([a-zA-Z]{2,4})$/ 275 | - 字母+数字:let reg = /^(?!\D+$)(?![^a-zA-Z]+$)\S{6,20}$/ 276 | - 字母或数字:left reg = /^[a-z0-9]{6}$/ 277 | - 特殊字符过滤 278 | 279 | ```js 280 | function strFilter(s){ 281 | const pattern = new RegExp("[`~!@#$^&*()=|{}':;,.<>/?!¥…()—【】‘;:”“。,、?]"); 282 | let re = ''; 283 | for (let i = 0; i < s.length; i++) { 284 |  rs = rs + s.substr(i, 1).replace(pattern, ''); 285 | } 286 | return re; 287 | } 288 | ``` 289 | 290 | ## 第6课时 291 | 292 | 暂无。 293 | 294 | ## 第7课时 295 | 296 | ### 7.1 Composition API 297 | 298 | Composition API详解:[https://vue-composition-api-rfc.netlify.com](https://vue-composition-api-rfc.netlify.com/)[/](https://vue-composition-api-rfc.netlify.com/) 299 | 300 | ```shell 301 | $npm install @vue/composition-api --save 302 | ``` 303 | 304 | ```js 305 | // Main.js 306 | import VueCompositionApi from '@vue/composition-api'; 307 | Vue.use(VueCompositionApi); 308 | ``` 309 | 310 | **Vue3.0新特性语法** 311 | 312 | 首先需要引入相关的 313 | 314 | **1. setup函数** 315 | 316 | > 按照官方给出的说法,setup函数是一个新的Vue组件选项,是用于在组件中使用Composition API的入口。 317 | 318 | ```js 319 | export default { 320 | setup(props, context) { 321 | /* attrs: (...) == this.$attrs 322 | emit: (...) == this.$emit 323 | listeners: (...) == this.$listeners 324 | parent: (...) == this.$parent 325 | refs: (...) == this.$refs 326 | root: (...) == this */ 327 | // ...... 328 | } 329 | } 330 | ``` 331 | 332 | 特别要注意的在3.0语法中,ElementUI等已经不能直接用`this.???`之类的了,this要使用root来替换. 333 | 334 | 2. 声明对象** 335 | 336 | > 在Vue2.0中为data(){ return {} },而Vue3.0中使用recative()和ref() 337 | 338 | - **Reactive**(声明单一对象时使用) 339 | 340 | ```js 341 | const obj = reactive({count: 0}) 342 | // 获取值 343 | console.log(obj.count) // 0 344 | ``` 345 | 346 | - **ref**(声明基础数据类型变量时使用) 347 | 348 | ```js 349 | const number = ref(0); 350 | // 获取值 .value 351 | console.log(number.value) // 0 352 | ``` 353 | 354 | - **isRef**(检查一个对象是否是ref对象) 355 | 356 | ```js 357 | const unwrapped = isRef(number) ? number.value : number; 358 | ``` 359 | 360 | - **toRefs**(toRefs将reactive对象转换为普通对象) 361 | 362 | > 保证对象解构或拓展运算符不会丢失原有响应式对象的响应 363 | 364 | ```js 365 | // 普通对象 366 | const obj = {a: 1, b: 2} 367 | let {a, b} = obj 368 | console.log(a, b) // 1 2 369 | // reactve对象 370 | const reactiveObj = reactive({a: 1, b: 2}) 371 | let {a, b} = newObj // 1 2 372 | const newObj = toRefs(reactiveObj) 373 | let {a, b} = newObj 374 | console.log(a, b) // RefImpl{} RefImpl{} 375 | newObj.a // 无法获得 376 | newObj.a.value // 1 - 需要添加value 377 | ``` 378 | 379 | **Composition API中生命周期函数** 380 | 381 | ```js 382 | import { onMounted, onUpdated, onUnmounted } from 'vue' 383 | const MyComponent = { 384 |   setup() { 385 |     onMounted(() => { 386 |       console.log('mounted!') 387 |     }) 388 |     onUpdated(() => { 389 |       console.log('updated!') 390 |     }) 391 |     onUnmounted(() => { 392 |       console.log('unmounted!') 393 |     }) 394 |   } 395 | } 396 | ``` 397 | 398 | 399 | 400 | ## 第8课时 401 | 402 | ### 8.1 axios拦截器 403 | 404 | 官网:[axios](http://www.axios-js.com) 安装 `npm install axios` 405 | 406 | > 首先为了避免组件之间重复写接口,需要将接口脱离出来,形成单独的api文件引入即可。 407 | 408 | 409 | 410 | > 然后定义拦截器,在对应的api中引入拦截器进行处理。 411 | 412 | 413 | 414 | > 拦截器应用举例: 415 | 416 | ```js 417 | // 添加请求拦截器 418 | axios.interceptors.request.use(function (config) { 419 | // 在发送请求之前做些什么 420 | return config; 421 | }, function (error) { 422 | // 对请求错误做些什么 423 | return Promise.reject(error); 424 | }); 425 | 426 | // 添加响应拦截器 427 | axios.interceptors.response.use(function (response) { 428 | // 对响应数据做点什么 429 | return response; 430 | }, function (error) { 431 | // 对响应错误做点什么 432 | return Promise.reject(error); 433 | }); 434 | ``` 435 | 436 | 一般会在请求拦截器中对请求添加一些参数:Tokey,userID...... 437 | 438 | ### 8.2 模块引用 439 | 440 | 通过export default导出就不需要{}来import,但是这样只能导出一个函数; 441 | 442 | ```js 443 | // file1.js 444 | function service(){} 445 | export default service; 446 | // file2.js 447 | import service from 'file1.js' 448 | ``` 449 | 450 | 通过export导出时import需要{},且可以导出多个函数; 451 | 452 | ```js 453 | // file1.js 454 | function service1(){} 455 | function service2(){} 456 | export {service1, service2}; 457 | // file2.js 458 | import {service1, service2} from 'file1.js' 459 | ``` 460 | 461 | ### 8.3 接口文档 462 | 463 | 详见:http://www.web-jshtml.cn/file/api.html 464 | 465 | ## 第9课时 466 | 467 | ### 9.1 环境变量与模式 468 | 469 | 默认情况下,一个 Vue CLI 项目有三个模式: 470 | 471 | - `development` 模式用于开发环境 472 | - `production` 模式用于生产环境(项目上线) 473 | - `test` 模式用于测试 474 | 475 | 通过`process.env.NODE_ENV`的值development/production来知道此时处于何种环境下,并且根据当前的环境我们可以进行不同配置,举例如下: 476 | 477 | ```js 478 | publicPath: process.env.NODE_ENV === 'production' ? '' : '/', // 基本路径 479 | outputDir: process.env.NODE_ENV === 'production' ? 'dist' : 'devdist', // 输出文件目录 480 | ``` 481 | 482 | 我们也可以在项目根目录创建一个名为 `.env.development` 的文件,那么在这个文件里声明过的变量就只会在 development 模式下被载入,production同理。 483 | 484 | **环境变量**: 485 | 486 | - 以 `VUE_APP_` 开头 - 例如设置`VUE_APP_SECRET=secret`后,在构建过程中,`process.env.VUE_APP_SECRET` 将会被`secret`所取代。 487 | - `NODE_ENV` - 会是 `"development"`、`"production"` 或 `"test"` 中的一个。 488 | - `BASE_URL` - 会和 `vue.config.js` 中的 `publicPath` 选项相符,即你的应用会部署到的基础路径。 489 | 490 | ### 9.2 跨域设置 491 | 492 | 1. 本地`http://192.168.1.106:8080`调用`/getSms`接口则对应的URL为`http://192.168.1.106:8080/getSms/` 493 | 2. baseURL设置 - 相当于在访问的借口前加了一个前缀 494 | - 如果axios的baseURL设置为`http://192.168.1.1`,则此时对应的URL为`http://192.168.1.1/getSms/` 495 | 496 | ``` 497 | const service = axios.create({ 498 | baseURL: 'http://192.168.1.1', 499 | timeout: 1000, // 超时 500 | }) 501 | ``` 502 | 503 | - 如果axios的baseURL设置为`/api`,则此时对应的URL为`http://192.168.1.106:8080/api/getSms/` 504 | 505 | ``` 506 | const service = axios.create({ 507 | baseURL: '/api', 508 | timeout: 1000, // 超时 509 | }) 510 | ``` 511 | 512 | 3. 在vue.config.js中进行跨域配置 - 当访问域名时遇到/api,则讲前面的域名转换为target,即原本`http://192.168.1.106:8080/api/getSms/`变成了`http://www.web-jshtml.cn/productapi/api/getSms/`,此时我们不需要`/api`则借助pathRewrite将其转为空,最终访问urk为`http://www.web-jshtml.cn/productapi/getSms/` 513 | 514 | ```js 515 | proxy: { 516 | '/api': { 517 | target: 'http://www.web-jshtml.cn/productapi', // 服务地址 518 | changeOrigin: true, 519 | pathRewrite: { 520 | '^/api': '' 521 | } 522 | } 523 | } 524 | ``` 525 | 526 | ## 第10课时 527 | 528 | 暂无 529 | 530 | ## 第11课时 531 | 532 | ### 11.1 setTimeout/setInterval 533 | 534 | > 需求:验证码按钮在请求过程中禁用并显示“发送中”,在请求成功后,倒计时60s结束后显示“重新发送”。 535 | 536 | 通过setTimeout/setInterval来实现,但是要注意,setTimeout/setInterval在使用过程中要注意清除!!! 537 | 538 | - setTimeout:clearTimeout(变量) 只执行一次 539 | - setInterval:clearInterval(变量) 不断的执行,需要条件才会停止 540 | 541 | ## 第12课时 542 | 543 | ### 12.1 promise对象 544 | 545 | > 主要了解四个内容,resolve,reject,all,race。 546 | 547 | - 普通语法 548 | 549 | ```js 550 | let promise = new Promise((resolve, reject) => { 551 | // ... 552 | }) 553 | promise.then(response => { 554 | console.log('成功') 555 | console.log(response) 556 | }).catch(error => { 557 | console.log('失败') 558 | console.log(error) 559 | }) 560 | ``` 561 | 562 | - all方法 - 数组内promise全部成功(resolve),才执行then,只要有一个返回(reject)不成功,就执行catch。 563 | 564 | ```js 565 | Promise.all([promise1(true), promise2(true), promise3(true)]).then(response => { 566 | console.log('全部调用成功'); 567 | }).catch(error => { 568 | console.log('有些可能失败了'); 569 | }) 570 | ``` 571 | 572 | - race方法 - 竞速,只有有一个率先改变状态,最终的状态就跟着改变。 573 | 574 | ```js 575 | Promise.race([promise1(false), promise2(true), promise3(true)]).then(response => { 576 | console.log('成功'); 577 | }).catch(error => { 578 | console.log('失败了'); 579 | }) 580 | ``` 581 | 582 | ## 第13课时 583 | 584 | ### 13.1 密码sha1加密 585 | 586 | 安装完js-sha1后,`import sha1 from 'js-sha1'`导入,然后直接使用`sha1()`即可。 587 | 588 | ```shell 589 | $npm install js-sha1 590 | ``` 591 | 592 | 密码加密的流程: 593 | 594 | 1. 在前端预先加密一次 595 | 1. 登录的密码:123456(普通字符串) 596 | 2. 经过加密后:sha1('123456') == '541216ad5s4f5ds1f5asd4f65asd4' (加密后的字符串) 597 | 2. 后台加密 598 | 1. 接收到字符串:'541216ad5s4f5ds1f5asd4f65asd4' 599 | 2. 后台再次加密:md5('541216ad5s4f5ds1f5asd4f65asd4') == '8f9qwersd3g165y4d1sf3s1f6aew4'(最终的加密后的密码) 600 | 3. 登录 - 用户名与加密后的密码进行匹配,成功则登录,失败则提示 601 | 602 | ## 第14课时 603 | 604 | 暂无。 605 | 606 | ## 第15课时 607 | 608 | ### 15.1 Router路由 609 | 610 | 1. 在HTML中以a标签形式跳转 - 在template中设置``即可 611 | 612 | ```vue 613 | 614 | 跳转链接1 615 | 跳转链接2 616 | 617 | 跳转链接3 618 | 跳转链接3 619 | ``` 620 | 621 | 2. 在JS中设置进行跳转 - 在函数中调用`this.$router.push()`即可 622 | 623 | ```js 624 | // 不带参数跳转 625 | this.$router.push('/home'); 626 | this.$router.push({name:'home'}); 627 | this.$router.push({path:'/home'}); 628 | // 带参数跳转 629 | this.$router.push({name:'home',query: {id:'1'}}) 630 | this.$router.push({path:'/home',query: {id:'1'}}) // 参数接收:this.$route.query.xxxxxxx 631 | this.$router.push({name:'home',params: {id:'1'}}) // 参数接收:this.$route.params.xxxxxxx 632 | ``` 633 | 634 | **⚠️传参区别** 635 | 636 | - query跳转配合路由 path 属性,传参为明文,url上参数可见,刷新后参数不会消失 637 | - Params路转配合路由 name 属性,传参为密文,url上参数不可见,刷新后参数会消失 638 | 639 | 注意,通过Params传参的话,路由配置 path: "/home/:id" 或者 path: "/home:id" 刷新页面id会保留,不配置path ,刷新页面参数会消失。 640 | 641 | 其他请参看[22.1 路由传参和接收方式](#221 路由传参和接收方式) 642 | 643 | ### 15.2 二级路由 644 | 645 | 路由配置如下: 646 | 647 | ```js 648 | path: '/home', 649 | name: 'Home', 650 | component: () => import('../views/Layout/index'), 651 | children: [ 652 | { 653 | path: '/home', 654 | name:'home', 655 | component:() => import('../views/home/index') 656 | }, 657 | { 658 | path: '/home/user', 659 | name:'user', 660 | component:() => import('../views/user/index') 661 | } 662 | ] 663 | ``` 664 | 665 | 解释说明: 666 | 667 | 当访问地址`/home`时,加载`../views/Layout/index`组件进行显示,此时该组件中仍旧嵌套了路由``,则该部分就加载`'../views/home/index'`组件进行显示;当访问地址`/home/user`时加载`../views/Layout/index`组件进行显示,但是该组件中的路由部分则加载`'../views/user/index'`来显示了。 668 | 669 | 总结: 670 | 671 | - 访问`/home` - `../views/Layout/index`组件 + 路由部分`'../views/home/index'`组件 672 | - 访问`/home/user` - `../views/Layout/index`组件 + 路由部分`'../views/user/index'`组件 673 | 674 | ## 第16课时 675 | 676 | ### 16.1局部样式与全局样式 677 | 678 | ```vue 679 | 680 | ``` 681 | 682 | 在vue文件的样式中,如果设置了scoped则表示当前的样式仅在该vue文件中生效;如果没有设置scoped则表示为全局样式。 683 | 684 | ### 16.2 路由菜单 685 | 686 | 路由菜单可通过router.js文件来设置,`this.$router.options.routes`获取路由信息,通过遍历即可获取对应的信息,举例如下: 687 | 688 | ```js 689 | const routes = [ 690 | { 691 | path: '/login', 692 | name: 'Login', 693 | hidden: true, 694 | component: () => import('../views/Login/index'), 695 | meta: { 696 | name: '登录' 697 | } 698 | }, 699 | { 700 | path: '/user', 701 | name: 'User', 702 | component: () => import('../views/Layout/index'), 703 | meta: { 704 | name: '用户管理' 705 | }, 706 | children: [ 707 | { 708 | path: "/userIndex", 709 | component: () => import('../views/User/index'), 710 | meta: { 711 | name: '用户列表' 712 | } 713 | } 714 | ] 715 | } 716 | ] 717 | ``` 718 | 719 | 借用elementUI来实现导航菜单: 720 | 721 | ```vue 722 | 723 | 734 | 735 | ``` 736 | 737 | 说明1:通过`this.$router.options.routes`获取routes,如果该路由不需要生成菜单可自定义属性进行判断,例如上述采用hidden属性来判断,另外可通过childen来获取下一层的路由信息,生成二级导航菜单。 738 | 739 | 说明2:一般我们避免v-for和v-if同时使用,上述采用的解决方法是v-for里嵌套v-if,只需要把v-for写在template上即可;除此之外,还可以v-if里嵌套v-for。 740 | 741 | ## 第17课时 742 | 743 | ### 17.1 SVG文件 744 | 745 | 1. 自定义全局组件SvgIcon在`main.js`注册,见[17.2 全局组件 Vue.component](#172 全局组件 Vuecomponent) 746 | 2. 组件模版添加 747 | 748 | ```vue 749 | 752 | ``` 753 | 754 | 3. 解析svg文件 755 | 756 | ``` 757 | const req = require.context('./svg', false, /\.svg$/) 758 | const requireAll = requireContext => { 759 |   return requireContext.keys().map(requireContext) 760 | } 761 | requireAll(req) 762 | ``` 763 | 764 | 4. Vue.config.json中配置 765 | 766 | ```js 767 | module.exports = { 768 | chainWebpack: (config) => { 769 | const svgRule = config.module.rule("svg"); 770 | svgRule.uses.clear(); 771 | svgRule 772 | .use("svg-sprite-loader") 773 | .loader("svg-sprite-loader") 774 | .options({ 775 | symbolId: "icon-[name]", 776 | include: ["./src/components/icons"] 777 | }); 778 | } 779 | } 780 | ``` 781 | 782 | 注意,需要安装依赖: 783 | 784 | ```shell 785 | $npm install svg-sprite-loader -S 786 | ``` 787 | 788 | svg文件更改颜色,不能通过color属性来设置,而是使用fill属性: 789 | 790 | ```css 791 | .svg-icon{ 792 | fill: #fff; 793 | } 794 | 795 | /* 或者也可指定使用当前的颜色 */ 796 | .svg-icon{ 797 | fill: currentColor; 798 | } 799 | ``` 800 | 801 | ### 17.2 全局组件 Vue.component 802 | 803 | 规则:`Vue.component(‘组件名称’, ‘组件代码’)` —— 组件代码即我们使用vue文件写的那些代码 804 | 805 | 在vue3.0中有两种模式: 806 | 807 | - compiler(模板)模式 808 | - runtime模式 —— vue模块的默认为runtime模式, 指向了"dist/vue.runtime.common.js"位置 809 | 810 | ```js 811 | // compiler(模式)模板 812 | new Vue({ 813 |   el: '#app', 814 |   router: router, 815 |   store: store, 816 |   template: '', 817 |   components: { App } 818 | }) 819 | // runtime模式(运行时) 820 | new Vue({ 821 |   router, 822 |   store, 823 |   render: h => h(App) 824 | }).$mount("#app") 825 | ``` 826 | 827 | 我们在vue3.0中要使用全局组件的话就需要在config.js里修改vue的默认指向,也就是改为compiler模式 - [参考链接](https://www.cnblogs.com/maizilili/p/12624964.html): 828 | 829 | ```js 830 | module.exports = { 831 | configureWebpack: (config) => { 832 | config.resolve = { // 配置解析别名 833 | alias: { 834 | 'vue': 'vue/dist/vue.esm.js', 835 | } 836 | } 837 | } 838 | } 839 | ``` 840 | 841 | ### 17.3 prop/propsData 842 | 843 | props 可以是基础数据或对象,用于接收来自父组件的数据。父组件中使用子组件直接以`参数名=参数值`的形式传递即可,在子组件中使用props获取。 844 | 845 | ```vue 846 | // 父组件传递参数 847 | 852 | 853 | // 子组件接受参数 854 | 857 | 866 | ``` 867 | 868 | 接收Props参数分为两种写法: 869 | 870 | ```js 871 | // 普通写法就直接参数名字的数组 872 | props: [‘iconClass’, ‘className’] //不限制数据类型 873 | 874 | // 规定数据类型写法 875 | props: { 876 | iconClass: { 877 | type: String, 878 | required: true 879 | }, 880 | className: { 881 | type: Array, 882 | default: () => [], 883 | validator: (value) => { 884 | return value >= 0; 885 | } 886 | } 887 | } 888 | ``` 889 | 890 | 规定数据类型的参数说明: 891 | 892 | - type(规定数据类型 ):String:字符串;Number:数字;Boolean:布尔;Array:数组;Object:对象;Date:日期;Function:函数;Symbol:示独一无二的值(ES6)。 893 | - required(必填项):默认为false,如果设为true,则父级必须传入数据,否则会报错。 894 | - default(默认值):基础数据类型:直接赋值;对象数据类型:用函数赋值 () => [] 895 | - validator(验证传入的值是否符合规则):校验 896 | 897 | ### 17.4 watct/computed 898 | 899 | **computed (计算属性)** 900 | 901 | ```js 902 | // 2.0 - 普通写法 903 | computed:{ 904 | iconName(){ return this.name + 'icon' } 905 | } 906 | // 2.0 - get/set写法 907 | computed:{ 908 | iconName:{ 909 | get(){ return this.name + 'icon' } 910 | set(){ this.name = 'newIcon' } 911 | } 912 | } 913 | 914 | const count = ref(1) 915 | // 3.0 - 普通写法 916 | const plusOne = computed(() => count.value + 1) 917 | // 3.0 - get/set写法 918 | const plusOne = computed({ 919 |   get: () => count.value + 1, 920 |   set: val => { count.value = val - 1 } 921 | }) 922 | // 举例 923 | plusOne.value = 2 924 | console.log(count.value) // 1,上面set设置 count.value 为 1 925 | console.log(plusOne.value) // 2,此时count.value 为 1,+1即为2 926 | ``` 927 | 928 | **watch (观察值变化)** 929 | 930 | 两种监听方式: 931 | 932 | - deep:深度监听,无论数据被嵌套多深 933 | - immediate:初始监听(简单理解,组件被加载时就监听) 934 | 935 | ```js 936 | // 2.0写法 937 | wacth: { 938 |   myData: { 939 |     handler(newValue, oldValue){ 940 |       console.log(newValue) 941 |     }, 942 |     deep: true, 943 |     immediate: true 944 |   } 945 | } 946 | // 3.0写法 947 | // 基础数据 948 | watch(myData, (val)=>{ 949 |     console.log(val) 950 | }) 951 | // 对象数据 952 | watch(() => chooseItems.value, (val) => { console.log(val) }) 953 | // 多个监听 954 | watch([ 955 |   () => cons.page,  956 |   () => cons.pageSize,  957 |   () => query.value 958 | ], ([val1, val2, val3]) => { 959 |   console.log(val1, val2, val3) 960 | }) 961 | ``` 962 | 963 | ### 17.5 头像 - 图片居中裁剪 964 | 965 | ```html 966 | 967 | 975 | ``` 976 | 977 | 关键在于object-fit 属性指定元素的内容应该如何去适应指定容器的高度与宽度: 978 | 979 | - fill - 默认,不保证保持原有的比例,内容拉伸填满整个内容容器 980 | - contain - 保持原有尺寸比例,内容被缩放(显示全部,多余部分会留白) 981 | - cover - 保持原有尺寸比例,但部分内容可能被剪切(显示部分,多余部分会裁剪) 982 | 983 | ## 第18课时 984 | 985 | ### 18.1 Vuex状态管理 986 | 987 | 988 | 989 | **5个部分** 990 | 991 | **1. state** —— 储存初始化数据 this.$store.state.xxxxx 992 | 993 | **2. getters** —— 对State里面的数据二次处理(对数据进行过滤类似filter的作用)this.$store.getters.xxx 994 | 995 | **3. mutations** —— 对数据进行计算的方法全部写在里面,在页面中触发时使用 this.$store.commit('mutationName') 996 | 997 | **4. modules** —— 模块化Vuex 998 | 999 | **5. actions** (异步)—— action的功能和mutation是类似的,都是去变更store里的state,不过action和mutation有两点不同: 1000 | 1001 | - action主要处理的是异步的操作,mutation必须同步执行,而action就不受这样的限制,也就是说action中我们既可以处理同步(视图触发Action,Action再触发Mutation),也可以处理异步的操作 1002 | 1003 | - action改变状态,最后是通过提交mutation,this.$store.dispatch(actionName) 1004 | 1005 | - 角色定位基于流程顺序,二者扮演不同的角色。Mutation:专注于修改State,理论上是修改State的唯一途径。Action:业务代码、异步请求。 1006 | 1007 | **模块化** - 参考资料:[Vuex 模块化使用](https://segmentfault.com/a/1190000019924674) 1008 | 1009 | 1010 | 1011 | ```js 1012 | export default new Vuex.Store({ 1013 | modules:{ 1014 | login, 1015 | info, 1016 | user 1017 | } 1018 | }) 1019 | 1020 | // 模块化后使用可以添加模块名 1021 | this.$store.state.moduleA.name; 1022 | // getter,mutation,action 他们默认都是注册在全局命名空间的,我们可以模块导出的时候加个 namespaced: true 使其成为带命名空间的模块,然后就可以通过模块名访问了,具体详见参考资料 1023 | this.$store.getters['moduleA/fullName']; 1024 | this.$store.dispatch('moduleA/ASYNC_SET_NAME', { name: "JJ" }); 1025 | ``` 1026 | 1027 | ### 18.2 浏览器存储 1028 | 1029 | **1. cookie_js** 1030 | 1031 | ```shell 1032 | $npm install cookie_js --save 1033 | ``` 1034 | 1035 | ```js 1036 | // 储存 1037 | cookie.set('key', 'value'); 1038 | cookie.set({ key1: 'value1', key2: 'value2' }); 1039 | 1040 | // 获取 1041 | cookie.get('key'); 1042 | cookie.get(['key1', 'key2']); 1043 | 1044 | // 清除 1045 | cookie.remove('key'); 1046 | cookie.remove('key1', 'key2'); 1047 | cookie.remove(['key1', 'key2']); 1048 | ``` 1049 | 1050 | **2. HTML5本储存** 1051 | 1052 | - sessionStorage(关闭浏览器时即清除) 临时性 1053 | 1054 | >存储大小:5M(数据量大小);存储于客户端;只能存储字符串类型;主要存储一些比较简单的东西,或是小的交互; 1055 | 1056 | ```js 1057 | // 存储 1058 | window.sessionStorage.setItem("key","value"); 1059 | // 获取 1060 | window.sessionStorage.getItem("key"); 1061 | // 删除 1062 | window.sessionStorage.removeItem("key"); 1063 | // 清空所有 1064 | sessionStorage.clear(); 1065 | ``` 1066 | 1067 | 注意:在存取的时候需要对象和字符串进行转换,JOSN.parse() 字符串转为对象 + JSON.stringify() 对象转为字符串。 1068 | 1069 | - localStorage(手动清除) 长期性; 1070 | 1071 | ```js 1072 | // 存储 1073 | window.localStorage.setItem("key","value"); 1074 | // 获取 1075 | window.localStorage.getItem("key"); 1076 | // 删除 1077 | window.localStorage.removeItem("key"); 1078 | // 清空所有 1079 | localStorage.clear(); 1080 | ``` 1081 | 1082 | ## 第19课时 1083 | 1084 | ### 19.1 守卫路由 1085 | 1086 | > 常见用途:用户进入首页,如果未登录则自动跳转至登录页面。 1087 | 1088 | ```js 1089 | router.beforeEach((to, from, next) => { 1090 | /** 1091 | * to: 进入的页面 1092 | * from: 上一个页面 1093 | * next: 跳转页面 1094 | */ 1095 | }) 1096 | ``` 1097 | 1098 | 注意:一定确保要调用 next 方法(也就是写了next方法),否则钩子就不会被 resolved,可能会陷入死循环。 1099 | 1100 | - next():参数为空,即执行了to里面的路由对象,且跳转不会再执行beforeEach,带参数的next跳转会再次触发beforeEach 1101 | 1102 | - next('/')或者next({ path: '/' }):跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向 next 传递任意位置对象,且允许设置诸如 replace: true、name: 'home' 之类的选项以及任何用在 router-link 的 toprop 或 router.push 中的选项。 1103 | - next(error):如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。 1104 | 1105 | **Token登录检测举例** 1106 | 1107 | ```js 1108 | /** 1109 | * 1、直接进入非白名单路径如index的时候,参数to被改变成了 "/index",触发路由指向,就会跑beforeEach 1110 | * 2、若token不存在,且不在白名单中,next 指向了login,再次发生路由指向,再跑beforeEach,参数的to被改变成了"/login" 1111 | * 3、白名单判断存在,则直接执行next(),因为没有参数,所以不会再次beforeEach。 1112 | */ 1113 | 1114 | const whiteRouter = ['/login']; // 设置白名单 1115 | router.beforeEach((to, from, next) => { 1116 | if (getToken()) { 1117 | // token 存在 1118 | next() 1119 | } else { 1120 | // token不存在 - 跳转登录页面 1121 | if (whiteRouter.indexOf(to.path) !== -1) { 1122 | next() 1123 | } else { 1124 | next('/login') 1125 | } 1126 | } 1127 | }) 1128 | ``` 1129 | 1130 | ### 19.2 Token登录检测 1131 | 1132 | 1133 | 1134 | ## 第20课时 1135 | 1136 | ### 20.1 回调$emit与修饰符sync 1137 | 1138 | 子组件通过`vm.$emit(eventName,arg)`传递信息给父组件,而父组件上通过``监听事件来接受。修饰符sync为语法糖:子组件传递`this.$emit('update:foo', newValue)`,父组件原本写法``,现在可以通过sync语法糖简写``即可。vue 修饰符sync的功能是:当一个子组件改变了一个 prop 的值时,这个变化也会同步到父组件中所绑定。 1139 | 1140 | ```vue 1141 | // 子组件 1142 | 1148 | 1157 | ``` 1158 | 1159 | ```vue 1160 | // 父组件 1161 | 1169 | 1190 | ``` 1191 | 1192 | 一步步简写过程: 1193 | 1194 | - `` 1195 | - `` 1196 | - `` 1197 | 1198 | ### 20.1 全局方法 1199 | 1200 | > 全局方法有助于节约代码资源,不用重复写很多一致的代码或仅部分不同的代码。 1201 | 1202 | **1. 全局注册** 1203 | 1204 | ```js 1205 | // 1.自定义文件toTop.js中创建方法 1206 | export default { 1207 | install (Vue, options) { 1208 | Vue.prototype.$toTop = function () { 1209 | console.log('Plugin Test') 1210 | } 1211 | } 1212 | } 1213 | // 2.在main.js中添加到全局方法 1214 | import ToTop from './toTop' 1215 | Vue.use(ToTop) 1216 | // 3. 全局均可使用 1217 | this.$toTop() 1218 | ``` 1219 | 1220 | **2. 按需引入** 1221 | 1222 | ```js 1223 | // 1.自定义文件toTop.js中创建方法 1224 | export function toTop() { 1225 | console.log('Plugin Test') 1226 | } 1227 | // 2.使用的文件中按需引入 1228 | import {toTop} from './toTop' 1229 | // 3.引入后才可使用 1230 | toTop() 1231 | ``` 1232 | 1233 | **3. VUE3.0方法** 1234 | 1235 | ```js 1236 | // 1.自定义文件中创建方法 1237 | export function myGlobal(){ 1238 | const func1 = ()=>{} 1239 | const func2 = ()=>{} 1240 | return {func1, func2} 1241 | } 1242 | // 2.使用的文件中引入 1243 | import myGlobal from './global.js' 1244 | setup(){ 1245 | const {func1,func2} = myGlobal() 1246 | // 下面即可使用func1和func2了 1247 | func1(); 1248 | } 1249 | ``` 1250 | 1251 | ## 第21课时 1252 | 1253 | 略。 1254 | 1255 | ## 第22课时 1256 | 1257 | ### 22.1 路由传参和接收方式 1258 | 1259 | **第一种:明文传参** 1260 | 1261 | > URL路径会显示传递的参数。优势:页面刷新参数不会丢失,劣势:参数公开。 1262 | 1263 | ```js 1264 | // Html跳转 1265 | 1266 | 1267 | // JS跳转 1268 | this.$router.push({ 1269 | name: `xxx`, 1270 | query: { 1271 | } 1272 | }) 1273 | 1274 | // 跳转页面接收参数 1275 | this.$route.query.xxxxxx 1276 | ``` 1277 | 1278 | **第二种:密文传参** 1279 | 1280 | >URL路径不会显示传递的参数。优势:参数不显示,劣势:页面刷新参数消失。 1281 | 1282 | ```js 1283 | // Html跳转 1284 | 1285 | 1286 | // JS跳转 1287 | this.$router.push({ 1288 |   name: `xxx`, 1289 |   params: { 1290 |   } 1291 | }) 1292 | 1293 | // 跳转页面接收参数 1294 | this.$route.params.xxxxxx 1295 | ``` 1296 | 1297 | **第三种:冒号形式传参** 1298 | 1299 | > 优势:页面刷新参数不会丢失,劣势:需要一一配置。 1300 | 1301 | ```js 1302 | // 路由配置 1303 | { 1304 |   path: "/infoDetailed/:newsId/:newsTitle", 1305 |   name: "InfoDetailed” 1306 |   meta: { 1307 |     name: "信息详情" 1308 |   }, 1309 |   component: () => import("../views/Info/category.vue") 1310 | } 1311 | 1312 | // JS跳转 1313 | root.$router.push({ 1314 |     path: `/InfoDetailed/${data.id}/${data.title}` 1315 | }) 1316 | 1317 | // 跳转页面接收参数 1318 | $route.params.newsId 1319 | $route.params.newsTitle 1320 | ``` 1321 | 1322 | **第四种:vuex 结合 HTML5本地储存** 1323 | 1324 | > 优势:参数不显示,劣势:微稍有点大材小用(解决第二种传参参数丢失) 1325 | 1326 | **第五种:新窗口打开** 1327 | 1328 | > 很少见。优势:参数不显示,劣势:微稍有点大材小用(解决第二种传参参数丢失) 1329 | 1330 | ```js 1331 | // Html跳转 - vue1不支持,query/params均可 1332 | 热门好货 1333 | 1334 | 1335 | // JS跳转 - 注意函数是resolve,不是push 1336 | let routeData = this.$router.resolve({ 1337 |    name: "searchGoods", 1338 |    query: params, 1339 |    // params:{} 1340 | }); 1341 | window.open(routeData.href, '_blank') 1342 | ``` 1343 | 1344 | ### 22.2 富文本编辑器 1345 | 1346 | > 选择第三方富文本编辑器安装即可,以下为一种例子 - https://www.npmjs.com/package/vue-quill-editor 1347 | 1348 | 安装: 1349 | 1350 | ```shell 1351 | $npm install vue-quill-editor --save 1352 | ``` 1353 | 1354 | 引入 1355 | 1356 | ```js 1357 | import { quillEditor } from "vue-quill-editor"; 1358 | import 'quill/dist/quill.core.css'; 1359 | import 'quill/dist/quill.snow.css'; 1360 | import 'quill/dist/quill.bubble.css'; 1361 | ``` 1362 | 1363 | 使用 1364 | 1365 | ```vue 1366 | 1367 | ``` 1368 | 1369 | ## 第23课时 1370 | 1371 | ### 23.1 组件化 1372 | 1373 | >页面开发中的**组件**其实就是页面组成的一部分,就像电脑中的每一个元件(如硬盘、键盘、鼠标),它是一个具有独立的逻辑和功能或界面,同时又能根据规定的接口规则进行相互融合,变成一个完整的应用。更简单的理解就是,将页面的业务逻辑拆分各个小块,再重新的组合起来,形成一个完整的体系。当不需要某个组件,或者想要替换某个组件时,可以随时进行替换和删除,而不影响整个应用的运行。 1374 | 1375 | **1. 为什么要组件化开发、优势是什么?** 1376 | 1377 | **高内聚性:**组件功能必须是完整的,如我要实现下拉菜单功能,那么在下拉菜单这个组件中,就只做下拉菜单所需要的所有功能。 1378 | 1379 | **低耦合度:**代码独立不会和项目中的其他代码发生冲突。 1380 | 1381 | 说的简单点就是,在实际工程中,常会涉及到团队协作(多人共同开发)。以传统的方式开发业务线去编写代码的方式,就很容易相互冲突,因为大家都可能在同一个文件中做事。所以用组件化方式开发就可避免这种冲突,因为每个人负责着不同的组件开发,所以都不会有改到别人代码的事情发生,并且每一个组件都有清晰的职责,完整的功能,各管各的功能,便于单元测试和重复利用。 1382 | 1383 | **优势:** 1384 | 1385 | 1. 提高开发效率:当开发好一个组件后,如果其他地方功能是一样的,直接载入组件即可,方便又快; 1386 | 2. 方便重复使用:一次开发,多处使用; 1387 | 3. 简化调试步骤:只调试当前组件,与其他业务逻辑无关。很多页面用了相同组件后,如果组件有BUG,那么所以引用的都有BUG,一个修复,即全部修复; 1388 | 4. 提升整个项目的可维护性:组件独立维护,管理性强;维护时对其他业务逻辑没有影响; 1389 | 5. 便于多人协同开发:多人开发,各人负责自己的所开发的组件,不会修改到其他人的代码;避免代码误改问题; 1390 | 1391 | **2. 组件类型** 1392 | 1393 | 1. 页面级别的组件:页面级别的组件,通常是views目录下的.vue组件,是组成整个项目的一个大的页面。一般不会有对外的接口。 1394 | 2. 业务上可复用的基础组件:在业务中被各个页面复用的组件,这一类组件通常都写到components目录下,然后通过import在各个页面中使用。 1395 | 3. 与业务无关的独立组件:与业务功能无关的独立组件。这类组件通常是作为基础组件,在各个业务组件或者页面组件中被使用。目前市面上比较流行的ElementUI和iview等中包含的组件都是独立组件。如果是自己定义的独立组件,比如富文本编辑器等,通常写在utils目录中。 1396 | 1397 | **3. 组件三要素:** 1398 | 1399 | 1. Prop:用于定义组件的属性(组件属性参数)。 1400 | 2. Event:自定义事件用于触发组件的事件(经常会回调父组件方法)。 1401 | 3. Slot:用于组件功能的扩展。(插槽),父组件的内容传入子组件,在子组件中显示 1402 | 1403 | **4. 定义组件** 1404 | 1405 | ```js 1406 | // 全局组件 1407 | // 注册的全局组件,在vue项目中.vue页面都会挂载此组件,有点资源浪费 1408 | Vue.component('child-component', { 1409 |     template: ` 1410 |     
1411 |          1412 |             

如果父组件没用插入内容,我将作为默认出现

1413 |         
1414 |     
` 1415 | }); 1416 | 1417 | // 局部组件 1418 | // 按需加载,只在有需要的.vue页面中引用,不浪费资源,就是有点麻烦 1419 | import Users from './components/Users‘ 1420 | ``` 1421 | 1422 | ### 23.2 生命周期 1423 | 1424 | 1425 | 1426 | 注意:watch只有数据发生改变才会执行,而created()阶段在数据实例化之前,此时改变 data() 中的数据是不会触发 change 事件的,无法进入 watch 代码部分,但是mounted()阶段在数据实例化之后,此时改变 data 中的数据是会触发 change 事件的,也就能被 watch 到。 1427 | 1428 | ### 23.3 v-slot插槽 1429 | 1430 | 1、匿名插槽 - 没有指定,全部显示 1431 | 1432 | 以下例子,在渲染中slot就会被替换成父组件中的插槽内容hello,注意子组件中几个slot就显示几个插槽内容: 1433 | 1434 | ```vue 1435 | 1436 | hello 1437 | 1438 | 1444 | ``` 1445 | 1446 | 2、具名插槽 - 指定名称显示 1447 | 1448 | 以下例子,在渲染中slot就会被替换成父组件中的对应name的插槽内容: 1449 | 1450 | ```vue 1451 | 1452 | 1453 | 1456 | 1459 | 1460 | 1461 | 1467 | ``` 1468 | 1469 | 3、作用域插槽 - 数据绑定,父子组件通信(组件化中常用) 1470 | 1471 | 下面例子,父组件通过绑定插槽 prop - slotProps来获取把绑定在slot上的属性,从而实现父组件获取子组件的值的目的,注意如果是具名插槽,下面的v-slot:default需要改成v-slot:对应的插槽名: 1472 | 1473 | 子组件的name属性与父组件中使用的`v-slot:name`的name对应,而子组件中的属性在父组件中通过`v-slot:name`设置的值来访问: 1474 | 1475 | ```vue 1476 | 1477 | 1478 | 1481 | 1482 | 1483 | 1484 | 1485 | {{ myUser.lastName }} 1486 | 1487 | 1488 | ``` 1489 | 1490 | 具体详见[User模块](./src/views/User/index.vue)和[table组件 - index.vue](./src/components/table/index.vue) 1491 | 1492 | ### 23.4 组件业务逻辑拆分 1493 | 1494 | 具体详见[table组件 - index.vue](./src/components/table/index.vue)和[tableLoadData.js](./src/components/table/tableLoadData.js) 1495 | 1496 | 2.0中使用**mixin**: 1497 | 1498 | > 混入 (mixin) 是一种分发 Vue 组件中**可复用功能**的非常灵活的方式。混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被混入该组件本身的选项。mixin可以简单理解成常见函数封装,想像一下封装JS方法。 1499 | 1500 | ```js 1501 | // 定义一个混入对象.js 1502 | var myMixin = { 1503 | created: function () { 1504 | this.hello() 1505 | }, 1506 | methods: { 1507 | hello: function () { 1508 | console.log('hello from mixin!') 1509 | } 1510 | } 1511 | } 1512 | 1513 | // 定义一个使用混入对象的组件 1514 | var Component = Vue.extend({ 1515 | mixins: [myMixin] 1516 | }) 1517 | ``` 1518 | 1519 | **选项合并** 1520 | 1521 | 1、混入的 data 中,键名相同,则读取**组件**中的 键名;反之读取混入键名; 1522 | 1523 | 2、钩子:同名钩子函数将混合为一个数组,因此都将被调用。混入对象的钩子将在组件自身钩子**之前调用**。 1524 | 1525 | 3、值为对象的混入:如methods,components等,选项会被合并,**组件对象**覆盖混入对象。 1526 | 1527 | ## 第24课时 1528 | 1529 | ### 24.1 JSON对象拷贝 1530 | 1531 | **1、深拷贝(白话文理解)** - JSON.stringify(对象); 1532 | 1533 | > 直接复制原始对象所有数据,并脱离原始对象;不会对原始对象造成影响;注:深拷贝出来的对象是 **字符串类型**。 1534 | 1535 | **2、浅拷贝(白话文理解)** - Object.assign({}, 对象); 1536 | 1537 | > 只复制原始对象的第一层数据,第二层及以下数据还是原始数据对象的引用;注:浅拷贝出来的对象是 **对象类型** 1538 | 1539 | **3、递归方式(白话文理解)视频中没讲到** 1540 | 1541 | > 把对象循环遍历一次,无论层级多深,将所以key对象全部赋给新对象,并脱离原始对象;不会对原始对象造成影响;此方法可以有效解决 深拷贝 的function、undefined、Symbol() 丢失问题;注:遍历出来的对象是什么样的就是什么样,保持不变 1542 | 1543 | ### 24.2 组件通讯(重要) 1544 | 1545 | **组件关系:** 1546 | 1547 | - 父子组件:A与B、B与C、B与D、C与E、D与E 1548 | 1549 | - 子孙组件:A与D、B与E 1550 | 1551 | - 兄弟组件:C与D 1552 | 1553 | - 隔代组件:A与E 1554 | 1555 | 1556 | 1557 | 方法1:父、子组件通讯 props、emit、.sync 1558 | 1559 | ```vue 1560 | // 下面的“属性名”和“方法名”要一一对应 1561 | // 父组件.vue 1562 | 1565 | 1575 | 1576 | // 子组件.vue 1577 | 1580 | 1594 | ``` 1595 | 1596 | 方法2:中央事件总线 - 各级组件之间均可通讯 1597 | 1598 | ```js 1599 | // bus.js - 创建实例 1600 | import Vue from 'vue'; 1601 | export default new Vue(); 1602 | 1603 | // 调用事件 1604 | import Bus from 'bus.js' 1605 | Bus.$emit('getTarget', 'hello world!'); 1606 | 1607 | // 注册事件 1608 | import Bus from 'bus.js' 1609 | Bus.$on('getTarget', target => {   1610 |     console.log(target);   1611 | });  1612 | ``` 1613 | 1614 | 方法3:Vuex 1615 | 1616 | > 略,详见vuex部分。 1617 | 1618 | 方法4:$attrs、$listeners 1619 | 1620 | - $attrs从外层组件传到内部组件 1621 | 1622 | > 举例:A组件嵌套B,B组件嵌套C;A传递属性到B,B可以通过this.$attrs获取,并且要继续传递给下一层,就需要在下一层组件上绑定`v-on='$attrs'`,注意如果B中通过props接收相同名称的属性, this.$attrs此时以及后续都不能读取到该名称属性。$listeners传递方法同理。 1623 | 1624 | 方法5:provide、inject - 跨级组件间的通信 1625 | 1626 | ```js 1627 | // 祖先组件中通过provider来提供变量,然后在子孙组件中通过inject来注入变量 1628 | // 父组件 1629 | provide("customVal", "我是父组件向子组件传递的值"); 1630 | // 子组件 1631 | inject("customVal"); // 通过this.customVal获取 1632 | ``` 1633 | 1634 | 方法6:$parent、$children 1635 | 1636 | > 略,感觉较少用。 1637 | 1638 | ## 第25课时 1639 | 1640 | ### 25.1 动态路由 1641 | 1642 | > 路由文件中可以设置默认路由(任何角色都能访问)和动态路由(按角色分配访问权限),在路由守卫中判断用户角色并且动态分配路由权限。具体详见[guard.js](./src/router/guard.js)。 1643 | 1644 | ### 25.2 自定义指令 1645 | 1646 | 实现不同权限显示按钮的需求,除了可以通过[全局方法](#201 全局方法)(也就是通过注册全局方法,在按钮中v-if调用该方法并传入参数来判断是否显示),还可以是使用自定义指令的方法来实现。 1647 | 1648 | **自定义指令5种状态**(操作DOM元素) 1649 | 1650 | - bind:只调用一次,指令第一次绑定到元素时候调用,用这个钩子可以定义一个绑定时执行一次的初始化动作。 1651 | - Inserted:被绑定的元素插入父节点的时候调用(父节点存在即可调用,不必存在document中) 1652 | - Update:被绑定与元素所在模板更新时调用,而且无论绑定值是否有变化,通过比较更新前后的绑定值,忽略不必要的模板更新 1653 | - componentUpdate:被绑定的元素所在模板完成一次更新周期的时候调用 1654 | - Unbind:只调用一次,指令元素解绑的时候调用 1655 | 1656 | ```js 1657 | // 通过v-hello使用即可 1658 | Vue.directive("hello",{ 1659 |   bind:function(el,bingind,vnode){ 1660 |       el.style["color"] = bingind.value; 1661 |       console.log("1-bind"); 1662 |   }, 1663 |   inserted:function(){ 1664 |       console.log("2-insert"); 1665 |   }, 1666 |   update:function(){ 1667 |       console.log("3-update"); 1668 |   }, 1669 |   componentUpdated:function(){ 1670 |       console.log('4 - componentUpdated'); 1671 |   }, 1672 |   unbind:function(){ 1673 |       console.log('5 - unbind'); 1674 |   } 1675 | }) 1676 | ``` 1677 | 1678 | 注意自定义指令不要忘记在main.js中引入。 1679 | 1680 | ## 第26课时 1681 | 1682 | ### 26.1 keep-alive组件缓存 1683 | 1684 | >vue内置组件,能在组件切换过程中保留内存中DOM元素,防止重复渲染DOM,提高性能。 1685 | 1686 | 原本路由: 1687 | 1688 | ```vue 1689 | 1690 | ``` 1691 | 1692 | 增加缓存的写法: 1693 | 1694 | ```vue 1695 | 1696 | 1697 | 1698 | 1699 | 1700 | 1701 | ``` 1702 | 1703 | **属性:** 1704 | 1705 | - include:字符串或正则表达式,只有名称匹配的组件会被缓存 1706 | - exclude:字符串或正则表达式,名称匹配的组件不会被缓存 1707 | - max:数字,最多可以缓存多少组件实例 1708 | 1709 | **keep-alive相关钩子函数activated** 1710 | 1711 | activated在被 keep-alive 缓存的组件激活时调用,而deactivated函数被 keep-alive 缓存的组件停用时调用。生命周期的顺序:created-mouted-activated. 1712 | 1713 | ### 26.2 404页面 1714 | 1715 | 在默认路由中添加如下配置,*指代其他为匹配到的路径,都重定向至404,注意一般404配置放到最后: 1716 | 1717 | ```js 1718 | [{ 1719 | path: '/404', 1720 | name: 'page404', 1721 | hidden: true, 1722 | component: layout, 1723 | children: [ 1724 | { 1725 | path: '/404', 1726 | component: () => import('../views/404') 1727 | } 1728 | ] 1729 | }, 1730 | { 1731 | path: '*', 1732 | redirect: '404', 1733 | hidden: true 1734 | }] 1735 | ``` 1736 | 1737 | 如果使用动态路由的话,需要将*匹配放至动态路由匹配中,然后再动态分配的时候放到最后一项。 1738 | 1739 | ## 第27课时 1740 | 1741 | 暂无。 1742 | 1743 | ## 第28课时 1744 | 1745 | 具体操作详见视频教程:[教程-1](https://www.bilibili.com/video/BV1pT4y157cv) [教程-2](https://www.bilibili.com/video/BV1BK4y1C7a5) [教程-3](https://www.bilibili.com/video/BV11C4y147PF) 1746 | 1747 | ### 28.1 服务器 1748 | 1749 | 1750 | 1751 | ### 28.2 Nginx安装 1752 | 1753 | 1. 连接并进入服务器 1754 | 1755 | ```shell 1756 | $ssh root@服务器地址 1757 | ``` 1758 | 1759 | 2. nginx安装 1760 | 1761 | ```shell 1762 | // 安装: 1763 | $yum install nginx 1764 | // 卸载 1765 | $yum remove nginx 1766 | // 查看是否安装成功 1767 | $nginx –v 1768 | // 查看文件位置 1769 | $nginx –t 1770 | // 指定配置文件 - 后续的配置操作将围绕其展开 1771 | $nginx –c 目录 1772 | // 停止 1773 | $nginx -s stop 1774 | // 退出 1775 | $nginx -s quit 1776 | // 重启加载配置 1777 | $nginx -s reload 1778 | ``` 1779 | 1780 | ### 28.3 CentOS7 防火墙 1781 | 1782 | **基本操作:** 1783 | 1784 | 启动:systemctl start firewalld.service 1785 | 1786 | 停止:systemctl stop firewalld.service 1787 | 1788 | 重启: systemctl restart firewalld.service || firewall-cmd –reload 1789 | 1790 | 开启开机启动: systemctl enable firewalld 1791 | 1792 | 禁止开机启动:systemctl disable firewalld 1793 | 1794 | 查看防火墙状态:firewall-cmd --state 1795 | 1796 | **端口操作:** 1797 | 1798 | 查看已开放的端口: firewall-cmd --list-ports 1799 | 1800 | 开启端口: firewall-cmd --zone=public --add-port=80/tcp --permanent 1801 | 1802 | 关闭端口: firewall-cmd --zone=public --remove-port=3338/tcp --permanent 1803 | 1804 | 查询某端口是否开启: firewall-cmd --query-port=80/tcp 1805 | 1806 | 重启防火墙: firewall-cmd --reload 1807 | 1808 | ### 28.4 iptables配置 1809 | 1810 | 注意:CentOS 7中默认是firewalld防火墙,如果使用iptables需要先关闭firewalld防火墙(1.关闭防火墙,2.取消开机启动)。 1811 | 1812 | > #关闭防火墙 systemctl stop firewalld 1813 | > 1814 | > #取消开机启动 systemctl disable firewalld 1815 | > 1816 | > #查看状态 firewall-cmd --state 1817 | 1818 | **安装iptables:** 1819 | 1820 | 检查是否安装了:service iptables status 1821 | 1822 | 安装iptables:yum install -y iptables 1823 | 1824 | 安装iptables-services:yum -y install iptables-services 1825 | 1826 | 注册iptables服务:systemctl enable iptables.service 1827 | 1828 | 开启服务:systemctl start iptables.service 1829 | 1830 | 查看状态:systemctl status iptables.service 1831 | 1832 | **Iptables配置:** 1833 | 1834 | \#配置filter表 1835 | 1836 | \### 允许 已连接的数据包 进入 1837 | 1838 | \### 允许 新建连接的 22 80 443 端口的 tcp 包 进入 1839 | 1840 | iptables -A INPUT -p tcp -m state --state NEW -m tcp --dport 22 -j ACCEPT 1841 | 1842 | iptables -A INPUT -p tcp -m state --state NEW -m tcp --dport 80 -j ACCEPT 1843 | 1844 | iptables -A INPUT -p tcp -m state --state NEW -m tcp --dport 443 -j ACCEPT 1845 | 1846 | ### 28.5 nginx配置 1847 | 1848 | **proxy_pass** 1849 | 1850 | - proxy_pass配置中url末尾有/时,nginx转发会将原url去除loc带/时ation匹配表达式 后的内容拼接在proxy_pass中url之后。 1851 | - proxy_pass配置中url末尾不带/时,如url中不包含path,则直接将原url拼接在proxy_pass中url之后;如url中包含path,则将原uri去除location匹配表达式后的内容拼接在proxy_pass中的url之后。 1852 | 1853 | ```js 1854 | // 举例 - 测试地址:http://www.web-jshtml.cn/productapi/getSms/ 1855 | 1856 | 1857 | // 1、配置中url末尾有/时 1858 | // 代理后实际访问地址:http://www.web-jshtml.cn/api/getSms/; 1859 | location ^~ /productapi/ { 1860 | proxy_pass http://www.web-jshtml.cn/api/; 1861 | } 1862 | // 代理后实际访问地址:http://www.web-jshtml.cn/getSms/; 1863 | location ^~ /productapi/ { 1864 | proxy_pass http://www.web-jshtml.cn/; 1865 | } 1866 | // 代理后实际访问地址:http://www.web-jshtml.cn//getSms/; 1867 | location ^~ /productapi { 1868 | proxy_pass http://www.web-jshtml.cn/; 1869 | } 1870 | // 代理后实际访问地址:http://www.web-jshtml.cn/api//getSms/; 1871 | location ^~ /productapi { 1872 | proxy_pass http://www.web-jshtml.cn/api/; 1873 | } 1874 | 1875 | // 2、配置中url末尾无/时 1876 | // 代理后实际访问地址:http://www.web-jshtml.cn/productapigetSms/; 1877 | location ^~ /productapi/ { 1878 | proxy_pass http://www.web-jshtml.cn/productapi; 1879 | } 1880 | // 代理后实际访问地址:http://www.web-jshtml.cn/api/getSms/; 1881 | location ^~ /productapi { 1882 | proxy_pass http://www.web-jshtml.cn/api; 1883 | } 1884 | // 代理后实际访问地址:http://www.web-jshtml.cn/productapi/getSms/; 1885 | location ^~ /productapi/ { 1886 | proxy_pass http://www.web-jshtml.cn; 1887 | } 1888 | // 代理后实际访问地址:http://www.web-jshtml.cn/productapi/getSms/; 1889 | location ^~ /productapi { 1890 | proxy_pass http://www.web-jshtml.cn; 1891 | } 1892 | ``` 1893 | 1894 | **location匹配规则** 1895 | 1896 | | **标识符** | **描述** | 1897 | | ---------- | ------------------------------------------------------------ | 1898 | | = | **精确匹配**;用于标准url前,请求字符串和url严格匹配。如果匹配成功,就停止匹配,立即执行该location里面的请求。 | 1899 | | ~ | **正则匹配**;用于正则url前,表示uri里面包含正则,并且区分大小写。 | 1900 | | ~* | **正则匹配**;用于正则url前,表示uri里面包含正则,不区分大小写。 | 1901 | | ^~ | **非正则匹配**;用于标准url前,nginx服务器匹配到前缀最多的uri后就结束,该模式匹配成功后,不会使用正则匹配。 | 1902 | | 无 | **普通匹配(最长字符匹配)**;与location顺序无关,是按照匹配的长短来取匹配结果。若完全匹配,就停止匹配。 | 1903 | 1904 | >**优先级** - 多个location配置的情况匹配顺序为:首先精确匹配 = ;其次前缀匹配 ^~;其次是按照配置文件中的正则匹配;然后匹配不带任何修饰符的前缀匹配;最后交给/通用匹配; 1905 | 1906 | ```js 1907 | // 举例 1908 | location = / { 1909 | //精确匹配/ ,主机名后面不能带任何字符串 1910 | echo "规则A"; 1911 | } 1912 | location = /login { 1913 | //精确匹配 /login 开头的地址,匹配符合以后,不在继续往下搜索 1914 | echo "规则B"; 1915 | } 1916 | location ^~ /blog/ { 1917 | //非正则匹配,匹配/blog/后,停止往下搜索正则,采用这一条 1918 | echo "规则C"; 1919 | } 1920 | location ~ \.(gif|jpg|png|js|css)$ { 1921 | //区分大小写的正则匹配 若匹配成功,停止往下搜索正则,采用这一条 1922 | echo "规则D"; 1923 | } 1924 | location ~* \.png$ { 1925 | //区分大小写的正则匹配 ,停止往下搜索正则,采用这一条 1926 | echo "规则E"; 1927 | } 1928 | location / { 1929 | //因为所有的地址都以 / 开头,所以这条规则将匹配到所有请求 1930 | //如果没任何规则匹配上,就采用这条规则 1931 | echo "规则F"; 1932 | } 1933 | location /blog/detail { 1934 | //最长字符串匹配,若完全匹配成功,就不在继续匹配,否则还会进行正则匹配 1935 | echo "规则G"; 1936 | } 1937 | location /images { 1938 | //最长字符串匹配,同上 1939 | echo "规则Y"; 1940 | } 1941 | location ^~ /static/files { 1942 | //非正则匹配,若匹配成功,就不在继续匹配 1943 | echo "规则X"; 1944 | } 1945 | 1946 | // 当访问根路径/的时候,比如http://www.web-jshtml.cn/ ,会匹配规则A。 1947 | // 当访如http://www.web-jshtml.c/login ,会匹配规则B。 1948 | // 当访如http://www.web-jshtml.c/login.html ,会匹配规则F。 1949 | // 当访如http://www.web-jshtml.c/blog/detail/3.html ,会匹配规则C。 - 按匹配优先级去找 1950 | ``` 1951 | 1952 | **rewrite 重定向** 1953 | 1954 | 指令语法:`rewrite regex replacement [flag];` 1955 | 1956 | | 字符 | 描述 | 1957 | | --------- | ------------------------------------------------------------ | 1958 | | \ | 将后面接着的字符标记为一个特殊字符或者一个原义字符或一个向后引用 | 1959 | | ^ | 匹配输入字符串的起始位置 | 1960 | | $ | 匹配输入字符串的结束位置 | 1961 | | * | 匹配前面的字符零次或者多次 | 1962 | | + | 匹配前面字符串一次或者多次 | 1963 | | ? | 匹配前面字符串的零次或者一次 | 1964 | | . | 匹配除“\n”之外的所有单个字符 | 1965 | | (pattern) | 匹配括号内的pattern | 1966 | 1967 | rewrite最后一项flag参数: 1968 | 1969 | | 标记符号 | 说明 | 1970 | | --------- | -------------------------------------------------- | 1971 | | last | 本条规则匹配完成后继续向下匹配新的location URI规则 | 1972 | | break | 本条规则匹配完成后终止,不在匹配任何规则 | 1973 | | redirect | 返回302临时重定向 | 1974 | | permanent | 返回301永久重定向 | 1975 | 1976 | ```js 1977 | // 举例: 1978 | // 表示匹配成功后跳转,执行永久301跳转 1979 | rewrite ^/(.*) http://www.web-jshtml.cn/ permanent; 1980 | ``` 1981 | 1982 | ## 第29课时 1983 | 1984 | ### 29.1 总结 1985 | 1986 | 1987 | 1988 | ### 29.2 Vue2.0重置 1989 | 1990 | 基础模版(方法使用this.XXX; 数据使用this.XXX): 1991 | 1992 | ```js 1993 | export default { 1994 | name: "vue2.0_example", 1995 | components: {}, 1996 | props: {}, // 其他地方使用props直接通过this.XXX即可 1997 | data() { 1998 | return { 1999 | myData: 0 2000 | } 2001 | }, 2002 | computed: { 2003 | myData(){ 2004 | return XXX; 2005 | } 2006 | }, 2007 | watch:{ 2008 | a(val, oldVal){ // 普通的watch监听 2009 |          console.log("a: "+val, oldVal); 2010 |       }, 2011 |       b:{ // 深度监听,可监听到对象、数组的变化 2012 |          handler(val, oldVal){ 2013 |              console.log("b.c: "+val.c, oldVal.c); 2014 |          }, 2015 |          deep:true // true 深度监听, 2016 | immediate: true // 初始化监听 2017 |       } 2018 | }, 2019 | beforeCreate() {}, 2020 | created() {}, 2021 | beforeMount() {}, 2022 | mounted() {}, 2023 | beforeUpdate() {}, 2024 | updated() {}, 2025 | beforeDestroy() {}, 2026 | destroyed() {}, 2027 | methods: { 2028 | myFunc() {} 2029 | }, 2030 | } 2031 | ``` 2032 | 2033 | ## 其他 2034 | 2035 | ### 1. 日报、周报总结 2036 | 2037 | > 建议养成写日报的习惯,这样写周报就比较容易。 2038 | 2039 | - 周报内容: 2040 | 2041 | 1、........ 2042 | 2、........ 2043 | 3、........ 2044 | 2045 | - 总结周报: 2046 | 2047 | 1、这周做了什么? 2048 | 2、在项目上体现出来的业绩? 2049 | 3、开发中遇到的问题以及解决方法和思路? 2050 | 2051 | - 技术难点:如何攻克这些难点? 2052 | - 业务沟通:业务的想法,建议性东西 2053 | - 项目想法:项目上的自己的见解想法、建设性建议等 2054 | 2055 | ### 2. 项目管理 2056 | 2057 | git仓库不同分支可记录进行管理: 2058 | 2059 | 2060 | 2061 | 2062 | 2063 | ## 课时总结 2064 | 2065 | - 第1学时 了解产品从0到1的开发流程,产品经理、UI设计师、研发部、测试工程师 2066 | - 第2学时 安装node.js、vue3.0脚手架、创建github代码管理仓库、构建vue项目 2067 | - 第3学时 2.0与3.0的差异,vue.config.js、引入全局样式、router重定向、elementui依赖,git命令提交文件 2068 | - 第4学时 vue文件标准结构、v-for遍历、key、v-bind属性、@click事件、绑定class、基础数据类型、引用数据类型区别 2069 | - 第5学时 熟悉element-ui组件、制作表单验证、了解组件的用法 2070 | - 第6学时 封装校验js文件、webpack目录配置指向、export暴露方法、import引用、指令v-show、v-if的区别 2071 | - 第7学时 2.0语法转3.0、setup函数、reactive函数、ref函数、isRef、toRefs方法 2072 | - 第8学时 axios拦截器,模块管理API,export、export default的区别 2073 | - 第9学时 axios跨域配置、环境变量、接口文档 2074 | - 第10学时 登录接口接调试、响应拦截、elementui message、root参数 2075 | - 第10-1学时 登录接口接调试、响应拦截、elementui message、root参数 2076 | - 第11学时 按测试用例流程开发项目、注册接口联调、倒计时setTimeout、setInterval、超时timeout、登录接口调试、定时器知识点 2077 | - 第12学时 了解基础的Promise的方法、resolve、reject、all、race、then、catch 2078 | - 第13学时 请求头拦截、Request Headers添加参数、登录密码sha1加密、前端台加密流程、代码优化封装方法 2079 | - 第14学时 简单了解vue学习目标,具体学习什么东西,了解基础的指令 2080 | - 第15学时 后台首页搭建、router路由跳转、children属性、components组件、局部组件引入 2081 | - 第16学时 elementui的el-menu组件生成路由菜单、定义全局elemenui样式表、修改组个样式 2082 | - 第17学时 svgIcon制作、全局组件Vue.component、父子组件传值props、propsData、计算属性computed 2083 | - 第18学时 Vuex、State、Getters、Mutations、菜单导航收起、展开 2084 | - 第18-1学时 cookie存储,sessionStorage存储、localStorage存储、JSON.parse、JSON.stringify 2085 | - 第18-2学时 Vuex的action异步、同步、modules模块管理状态数据 2086 | - 第19学时 router.beforeEach路由守卫,检测toKen是否非法进入后台,to、from参数、next方法、Vuex命名空间 2087 | - 第19-1学时 登录存储token、token存在基础逻辑进入后台 2088 | - 第19-2学时 退出后台清除token、防止非法进入、GIT代码合并、提交当天开发的代码 2089 | - 第20学时 设计稿UI制作、element-ui组件、el-select、el-row、el-col、el-button 2090 | - 第20-1学时 设计稿UI制作、element-ui组件、el-table、el-pagination 2091 | - 第20-2学时 设计稿UI制作、element-ui组件、el-dialog、父子组件回调emit、修饰器sync、vue2.0、3.0写法、watch 2092 | - 第20-3学时 设计稿UI制作、element-ui组件、el-messageBox、自定义全局方法export install、VUE3.0组件重命名、watch 2093 | - 第20-4学时 设计稿UI制作、信息分类UI制作 2094 | - 第21学时 信息管理模块,一级分类接口、获取分类接口、onMounted、相关优化 2095 | - 第21-1学时 信息管理模块,删除接口、修改接口 2096 | - 第21-2学时 接口封装,vue3.0封装方式,vuex的actions方式,为后期维护方便 2097 | - 第21-3学时 添加信息接口、获取列表接口、分页处理请求数据、获取分类优化,变量优化 2098 | - 第21-4学时 单记录、批量删除接口、table组件数据加载优化、formatter属性返回值、日期组件配置数据格式、筛选条件处理 2099 | - 第21-5学时 信息编辑接口、添加子级分类接口、请求全部分类接口 2100 | - 第21-6学时 原型学习、原型版本查看、GIT命令控制代码版本迭代、合并代码、创建新分支 2101 | 2102 | - 第22学时 router路由跳转、5种传参方式、vuex配合HTML5本地储存 2103 | - 第22-1学时 详细页数据读取、初始化数据、富文本编辑器、vue devTool依赖 2104 | - 第22-2学时 elementui upload组件结合七牛云第三方储存,七牛云建立空间、域名绑定、解析 2105 | - 第22-3学时 elementUI组件二次封装开发,组件封装的一些问题思考,什么时候需要watch,传参动态配置数据 2106 | 2107 | - 第23学时 用户管理功能迭代、git分支创建、日常工作中的日报、周报总结、项目的管理 2108 | 2109 | - 第23-1学时 用户管理UI制作、elementUI el-select组件封装、参数配置、组件命名冲突 2110 | - 第23-2学时 真正理解vue组件化开发、组件概念、优势、全局组件component、局部组件import、从源头解决BUG 2111 | - 第23-3学时 vue生命周期,组件生命周期,3.0改写2.0组件 2112 | - 第23-4学时 vue3.0生命周期,封装el-table组件 2113 | - 第23-5学时 封装el-table组件,v-slot插槽3种方式,数据绑定 2114 | - 第23-6学时 封装el-table组件,数据请求,整合url请求地址,统一api文件夹管理 2115 | - 第23-7学时 封装el-table组件,业务逻辑的拆分、组合 2116 | - 第23-8学时 elementUI 页码组件、业务逻辑拆分页码,配置项 2117 | - 第23-9学时 vue2.0 mixins混入、按需混入、全局混入 2118 | 2119 | - 第24学时 省、市、区、街道组件封装、业务逻辑抽离 2120 | - 第24-1学时 省、市、区、街道组件封装、业务逻辑抽离 2121 | - 第24-2学时 省市区数据返回,el-radio、el-checkbox、获取角色管理API 2122 | - 第24-3学时 用户添加接口、json对象深拷贝、浅拷贝用法及注意事项 2123 | - 第24-4学时 组件通讯开始篇.sync、elemntUI Switch组件、用户列表、删除接口联调 2124 | - 第24-5学时 组件通讯完整版(重点知识) 2125 | - 第24-6学时 用户状态接口、编辑接口、搜索接口联调(上) 2126 | - 第24-7学时 用户状态接口、编辑接口、搜索接口联调(下) 2127 | 2128 | - 第25学时 动态路由开发,以系统分配路由,系统列表接口 2129 | - 第25-1学时 动态路由开发,以角色分配路由 2130 | - 第25-2学时 按钮级权限 2131 | - 第25-3学时 按钮级权限,自定义指令处理 2132 | 2133 | - 第26学时 组件缓存keep-alive、接口优化避免资源浪费 2134 | - 第26-1学时 BUG修复、监听路由变化、环境变量参数配置 2135 | - 第26-2学时 404页面 2136 | - 第26-3学时 404页面问题修复,退出接口联调 2137 | 2138 | - 第27学时 BUG修复过程、优先级排序、项目流程阶段 2139 | 2140 | - 第28学时 ECS云服务器购买、了解服务器的基础结构、nginx安装、端口配置、防火墙 2141 | - 第28-1学时 nginx配置、多项目部署、单项目部署、iptables安装配置 2142 | - 第28-2学时 nginx配置、日志查看,proxy_pass指向配置、调通接口数据、域名解析访问项目 2143 | 2144 | 2145 | 2146 | ------ 2147 | 2148 | 如果发现本项目有错误,欢迎提交 issues 指正。 --------------------------------------------------------------------------------