电商后台-学习笔记

电商后台-学习笔记

项目演示

gitee地址

1.初始化项目

打开vue项目管理页面

vue ui 

在这里插入图片描述

配置项目

预备安装 Babel Router Linter
在这里插入图片描述

安装依赖库

安装依赖: axios

初始化git远程仓库、代码托管到码云

Git配置多个SSH-Key

参考Gitee文章
利用本地工具生成ssh公钥和私钥

ssh-keygen -t rsa -C 'xxxxx@company.com' -f ~/.ssh/gitee_id_rsa
ssh-keygen -t rsa -C 'xxxxx@qq.com' -f ~/.ssh/github_id_rsa

在这里插入图片描述

在.ssh目录下添加config文件,内容如下:

# gitee
Host gitee.com
HostName gitee.com
PreferredAuthentications publickey
IdentityFile ./gitee_id_rsa
# github
Host github.com
HostName github.com
PreferredAuthentications publickey
IdentityFile ./github_id_rsa

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

将公钥复制到码云:个人主页 -> 个人设置 -> 安全设置 -> ssh公钥
在这里插入图片描述

测试:

ssh -T git@gitee.com
ssh -T git@github.com

在这里插入图片描述

创建仓库、Git初始化本地项目

关于Git,参考我的文章:未发布。

2.配置vue.config.js

const path = require("path");
module.exports = {
  // 访问路径(部署后访问资源的基本路径)
  publicPath: process.env.NODE_ENV === 'production' ? '/admin/' : '/',
  // 打包时输出文件目录
  outputDir:  'dist',
  // eslint-loader 是否在保存的时候检查
  lintOnSave: false,
  // 生产环境是否生成 sourceMap 文件
  productionSourceMap: false,
  /**
   * webpack配置,see https://github.com/vuejs/vue-cli/blob/dev/docs/webpack.md
   **/
  chainWebpack: config => { },
  configureWebpack: config => {
    config.resolve = {
      alias: {
        // 配置别名
        "@": path.resolve(__dirname, "./src"),
      }
    };
      
    if (process.env.NODE_ENV === 'production') {
      //生产环境下 移除console
      config.optimization.minimizer[0].options.terserOptions.compress.drop_console = true
    }
  },

  // webpack-dev-server 相关配置
  devServer: {
    open: true, // 编译完成是否打开网页
    host: "localhost", // 主机ip
    port: 8080, // 访问端口
    https: false, // 编译失败时刷新页面
    inline: true, //实时刷新
    hot: true, // 开启热加载
    hotOnly: false,

    proxy: {
      "/api": {
        target: "http://xxx.com/my-app", // 你请求的第三方接口
        changeOrigin: true, // 在本地会创建一个虚拟服务端,然后发送请求的数据,并同时接收请求的数据,这样服务端和服务端进行数据的交互就不会有跨域问题
        pathRewrite: {
          // 路径重写,
          "^/api": "" // 替换成target中的请求地址,也就是说需要用axios请求http://xxx.com/my-app/login这个地址的时候直接写成/api/login即可。
        }
      }
    }
  }
};

前端代理:解决跨域问题

首先需要明白的是,之所以存在跨域问题,是因为一般情况下浏览器不允许访问非当前域,例如网站域名为aaa.com,提供API服务端所在域名为bbb.com,这个时候从网站发出的请求结果会被浏览器拦截处理,抛出跨域错误。网站正式上线时,在API服务端中设置 Access-Control-Allow-Origin 来使浏览器允许跨域请求。

开发阶段则需配置本地代理:即Webpack 开启了一个8080的服务端口,可以看做一台本地服务器,这个服务器不仅负责发送html/css/js,还提供代理服务,js网络请求本地服务器,本地服务器对把url中的"/api"替换成"",然后在前面拼接上target字符串,由本地服务器访问目的服务器,最终返回数据。
在这里插入图片描述

3.封装axios

首先创建一个axios示例,配置好拦截器

// 创建axios实例
const service = axios.create({
  baseURL: process.env.VUE_APP_BASE_URL,
  timeout: 10000 // 请求超时时间
})

// request拦截器
service.interceptors.request.use(config => {
  //如果需要,带上token
  const token = getToken('token')
  if(token){
    config.headers['Authorization'] = token
  }
  return config
}, error => {
  // Do something with request error
  console.log(error) // for debug
  Promise.reject(error)
})

// respone拦截器
service.interceptors.response.use(response => {

  /**
  * 这里可结合项目接口设计进行修改
  */
    const res = response.data
    //失败
    if (res.meta.code !== 200) {
      Message({
        message: res.meta.msg, //这里可以显示后端给的错误信息
        type: 'error',
        duration: 3000
      })

	  if(res.meta.code === 400){
         //请求参数错误
      }else if(res.meta.code === 401) {
        //未授权,进行退出登录操作
      }else if(res.meta.code === 403){
      	//拒绝访问
      }else if(res.meta.code === 404){
        //服务不存在
      }else if(res.meta.code === 500){
        //服务器错误
      }
      return Promise.reject('handled business error in interceptor')
    } else {
      //成功
      return response.data
    }
  },
  error => {
    console.log('err' + error)// for debug
    Message({
      message: error.message,
      type: 'error',
      duration: 3000
    })
    return Promise.reject(error)
  }
)

然后建一个api目录,存放各个模块的接口,例如user.js:

import request from '../utils/request'

/*获取用户列表*/
export function getUsers(params){
  return request({
    method:'get',
    url:'users',
    params
  })
}
/*添加用户*/
export function addUser(data){
  return request({
    method:'post',
    url:'users',
    data
  })
}
/*编辑用户提交*/
export function updateUser(id,data){
  return request({
    method:'put',
    url:'users/'+id,
    data
  })
} 
/*删除用户*/
export function deleteUser(id){
  return request({
    method:'delete',
    url:'users/'+id
  })
} 

4. 路由

路由导航守卫

全局导航守卫,例如:

router.beforeEach((to, from, next) => {
  const token = getToken('token')
  if (to.name !== 'login' && !token) 
    next({ name: 'login' })
  else
    next()
})

一定要注意next()不能重复调用,只能调用一次。

路由的堆栈

vue-router中由push()和back()等API控制的栈路由和原生App开发有区别,在vue中:push:A->B,此时A是被销毁的,即A的数据已不存在,当back:B->A,会重新初始化A(created和mounted方法会被回调)

嵌套路由

const routes = [
  // 登录
  {path:'/login', component:Login},
  // 首页
  {
    path:'',
    component:Layout,
    redirect:'/home', //默认子路由
    children:[{
      path:'home',
      component:()=>import ('../views/home')
    }],
  },
  // 用户模块
  {
    path:'/users',
    name:'users',
    component:Layout,
    redirect:'/users/list',
    children:[{
      path:'list',
      name:'usersList',
      component:()=>import ('../views/users'),
    }]
  },
  // 权限模块
  {
    path:'/rights',
    name:'rights',
    redirect:'/rights/roleList',
    component:Layout,
    children:[{
      path:'roleList',
      name:'roleList',
      component:()=>import ('../views/right/roles')
    },{
      path:'rightList',
      name:'rightList',
      component:()=>import ('../views/right/rights')
    },{
      path:'alloRights/:id',
      name:'alloRights',
      component:()=>import ('../views/right/roles/allocRight')
    }]
  },
  // 商品模块
  
]

5.语法知识

ES6模块化

有一个文件A.js

...
export default B

在另一个文件中导入 import C from ‘…/A.js’ ,这里的C就是对导出的对象命名,不一定要写成import B from ‘…/A.js’。

第三方组件上使用vue原生事件

加上.native,例如:监听el-input的键盘事件

<el-input placeholder="请输入内容" v-model="query"  @keyup.enter.native="getUserList">

input组件键盘enter事件被调用两次

 <el-input v-if="scope.row.inputVisable"  
 @keyup.enter.native="handleAttrValAdd(scope.row)"
 @blur="handleAttrValAdd(scope.row)"
 >
handleAttrValAdd(row){
     row.inputVisable = false
     row.inputValue = ''
}

keyupblur绑定了同一个方法,当keyup触发后第一次调用handleAttrValAdd,然后input组件隐藏就会失去焦点,从而第二次调用handleAttrValAdd

Vue的$event

在元素的事件中可以拿到事件描述$event

<button @click="$event.target.name" name="哈哈">单击</button>
<!--打印:哈哈-->

Vue的全局API之$nextTick方法

$nextTick(callback)返回一个promise,他的作用是将回调推迟到DOM更新后执行,官方示例:

import { createApp, nextTick } from 'vue'

const app = createApp({
  setup() {
    const message = ref('Hello!')
    const changeMessage = async newMessage => {
      message.value = newMessage
      await nextTick()
      console.log('Now DOM is updated')
    }
  }
})

空字符串==false

if判断中,空字符串相当于false (js等部分语言中可以将空字符串转换为false,非空字符串转换为true)

let a = "" //空字符串
if(a)
    console.log('a相当于true')
else
    console.log('a相当于false')
//结果:a相当于false

属性赋值

方式一:

let a = {'name':'fk',sex:'man'}
let b = {sex:'woman'}
Object.assign(a,b)
//a的值:{name: "fk", sex: "woman"}
//注意这里的a不是原来的a,而是产生一个新对象复制给了a

方式二:

let a = {'name':'fk',sex:'man'}
let b = {sex:'woman'}
e = {...a, ...b}
//e的值:{name: "fk", sex: "woman"}

值得注意的是,方式一可以在Vue用来实现数据更新,例如:

data(){
	return {
        a:{'name':'fk',sex:'man'}
    }
}
//更新
methods:{
    handler(){
        let b = {sex:'woman'}
		Object.assign(a,b) //a的数据会发生更新,引起页面重新渲染
    }
}

js的split方法

"".split(' ') //0个空格
// [""]
" ".split(' ') //1个空格
// ["", ""] 
"  ".split(' ') //2个空格
// ["", "", ""]

6. element-ui

el-container布局

.el-container{
    display: flex;
    flex-direction: row;
    flex: 1;
    flex-basis: auto;
    box-sizing: border-box;
    min-width: 0;
}

建议添加样式

.el-container{
  height: 100%; /*不设置无法占满高度空间*/
  overflow:auto; /*不设置会部分无法滚动显示出来*/
}

表单的使用

以登录注册为例,看下element-ui的表单组件使用

表单绑定、验证

<el-form class="login-form" :model="loginForm" :rules="loginRules">
    <el-form-item  prop="username">
        <!-- 用户名 -->
        <el-input prefix-icon="el-icon-user"  v-model="loginForm.username">
        </el-input>
    </el-form-item>
    <el-form-item prop="password">
        <!-- 密码 -->
        <el-input prefix-icon="el-icon-key" type='password' v-model="loginForm.password">
        </el-input>
    </el-form-item>
    <el-form-item class="button-group">
        <el-button type="primary" @click="login()">登录</el-button>
        <el-button @click="resetForm()">重置</el-button>
    </el-form-item>
</el-form>
<script>
export default {

  data(){
    return {
      loginForm:{
        username:'admin',
        password:'123'
      },
      loginRules:{
        username:[
          { required: true, message: '请输入账号', trigger: 'blur' },
          { min: 3, max: 16, message: '长度应为3-16个字符', trigger: 'blur' }
        ],
        password:[
          { required: true, message: '请输入密码', trigger: 'blur' },
          { min: 3, max: 16, message: '长度应为3-16个字符', trigger: 'blur' }
        ]
      }

    }
  }
}
</script>
  • model属性不赋值的话,rules验证不会生效
  • model和rules相当于组件传参,把这两个数据交给el-form,由el-form来完成验证的效果
  • model 需搭配 prop属性,若没有prop属性,form.resetFields()是无效的

表单的resetFields失效

失效原因:在表单组件挂载到浏览器之前对数据保存了一个副本,resetFields()可以将数据还原到副本的值。下面的代码resetFields之所以会失效,是因为在表单挂载(显示)之前已经对数据进行了修改,保存的副本不再是data中的数据了。

data(){
    return {
     ...
      user:{
        username:'',
        password:'',
        email:'',
        mobile:'',
      },
    }
}
// 显示弹框
showEditDialog (row){
    this.editDialogVisable = true
    //这里设置的值是表单初始值,resetFields会重置为这些初始值。
    this.user.username = row.username
    this.user.email = row.email
    this.user.mobile = row.mobile
},
// 取消弹框
cancelDialog(type){
    switch(type){
        case 'add':
            this.addDialogVisable = false
            this.$refs.addFormRef.resetFields()
            break
        case 'edit':
            this.editDialogVisable = false
            this.$refs.editFormRef.resetFields() 
            break
    }
},

同一个页面有添加弹框和编辑弹框,最佳做法:准备两份数据和两个弹框,校验规则可以复用。

Dialog关闭时重置字段

监听Dialog组件的close事件,关闭时重置表单

// 处理添加框和编辑框关闭
handleAddDialClose(){
	this.$refs.addFormRef.resetFields()
},
handleEditDialClose(){
	this.$refs.editFormRef.resetFields()
},

级联样式修改

/* 级联器面板 */
.el-cascader-panel{
  max-height: 240px;
  min-height: 50px;
}
/* 取消级联面板水平滚动条 */
.el-scrollbar__wrap { 
  overflow-x: hidden; 
}
/* 取消级联面板的单选框:start */
.el-cascader-panel .el-radio{ 
  width: 100%; 
  height: 100%; 
  z-index: 10; 
  position: absolute; 
  top: 10px; 
  right: 10px; 
} 
.el-cascader-panel .el-radio__input{ 
  visibility: hidden; 
} 
.el-cascader-panel .el-cascader-node__postfix{ 
  top: 10px; 
} 
/* 取消级联面板的单选框:end */

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

修改引用组件的内部样式

深度作用选择器 >>> (注意,只作用于vue文件中的样式)

.test >>> .el-icon-search {
    ...
}

但如果是sass/less的话可能无法识别,这时候需要使用 /deep/ 或 ::v-deep 操作符取而代之——两者都是 >>> 的别名(此次验证,::v-deep未生效,其他均生效)。

<style lang="scss" scoped>
.test{
  /deep/ .el-icon-search {
    font-size: 30px;
  }
}
</style>

7.文件上传

1)封装组件

2)理解http请求

http请求头中 Content-Type 的类型
媒体格式
text/htmlHTML格式
text/plain纯文本格式
text/xmlXML格式
image/jpegjpg图片格式
image/pngpng图片格式
image/gifgif图片格式
application开头的媒体格式
application/xmlXML数据格式
application/jsonJSON数据格式
application/pdfpdf格式
application/mswordWord文档格式
application/octet-stream二进制流数据(如常见的文件下载)
application/x-www-form-urlencoded中默认的encType,form表单数据被编码为key/value格式发送到服务器(表单默认的提交数据的格式)
表单上传文件的媒体格式
multipart/form-data需要在表单中进行文件上传时,就需要使用该格式

表单中文件上传中,需要将file封装成FormData,关于FormData的详解参考:https://www.jianshu.com/p/e984c3619019

简而言之FormData是一种包含键值对的对象,可结合下面例子理解:

<form id="advForm">
    <p>广告名称:<input type="text" name="advName"  value="xixi"></p>
    <p>广告类别:
        <select name="advType">
            <option value="1">轮播图</option>
            <option value="2">轮播图底部广告</option>
            <option value="3">热门回收广告</option>
            <option value="4">优品精选广告</option>
        </select>
	</p>
    <p><input type="button" id="btn" value="添加"></p>
</form>

//js
//通过表单初始FormData
var form=document.querySelector("#advForm");
var formdata=new FormData(form);
    
//通过key获取value
formdata.get("advName");//xixi

//创建key-value
formData.append('fieldName',field); //field是字符串
formData.append('fileName',file); //file是文件对象

3)axios封装和调用

utils/request.js

// 上传表单中的文件
export function postUpload(options) {
  // post(url [, data [, config]])
  return service.post(options.url, options.data, { headers: { 'Content-Type': 'multipart/form-data' } })
}

api/upload.js

import {postUpload} from '../utils/request'

export function uploadImg(formData){
  return postUpload({
    url:'upload',
    data:formData
  })
}

在Vue页面

let formData = new FormData()
formData.append('file',file) 
const res = await uploadImg(formData)

8.第三方依赖

省市三级联动插件

安装 element-china-area-data

页面加载进度条

安装 nprogress

9.打包

参考我的这篇文章 Vue打包优化

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值