仿wzry官网学习笔记(完结)

269 阅读4分钟

管理页面及后端express相关

添加技能的实现

  • 数组删除用skill.splice(index,1)配合v-for(skill,index) in skills中的index使用
  • @click中直接写方法
  • 增加数组数据 model.skills.push({}) 添加一个空对象
  • v-for循环创建skills模板
  • 使用el-rowel-rol建立布局 相当于div

mongoDB模型与其他模型联系

  • mongoDB建立模型时,如果一条数据是多个用数组
  • 与其他模型有关联时,type应该是mogoose.Schematypes.ObjectID,再加一个ref:'Category'
    • categories: [{ type: mongoose.SchemaTypes.ObjectId, ref: 'Category' }],

富文本编辑

  • 富文本编辑器vue2-editor,
        <vue-editor
          id="editor"
          useCustomImageHandler
          @image-added="handleImageAdded"
          v-model="model.body"
        ></vue-editor>
    async handleImageAdded(file, Editor, cursorLocation, resetUploader) {
      const formData = new FormData();
      formData.append("file", file);
      const res = await this.$http.post("uploads", formData);
      Editor.insertEmbed(cursorLocation, 'image', res.data.src);
      resetUploader();
    },

http-assert抛错

  • 使用http-assert抛出错误,在最后用错误处理函数接受错误
    assert(user, 401, '用户不存在')
    
    app.use(async (err, req, res, next) => {
        res.status(err.statusCode || 500).send({
            message: err.message
        })
    })

上传头像以及静态文件托管

  • 使用multer处理上传文件,先指定一个目标文件夹,使用single()函数接受formData数据,需配合express的静态文件托管使用
   const multer = require('multer')({
        dest: __dirname + '../../../uploads'
    })
    app.post('/admin/api/uploads',authMiddleWare(), multer.single('file'),async (req, res) => {
        const file = req.file
        file.src = `http://localhost:3000/uploads/${file.filename}`
        res.send(file)
    })

密码相关

  • 使用bcrypt做密码散列和验证,在数据库模型中使用以及服务端验证使用
    password: {
        type: String,
        set(val) {
            return require('bcrypt').hashSync(val, 10)
        },
    }
    app.post('/admin/api/login', async (req, res) => {
        const { username, password } = req.body
        const user = await User.findOne({ username }).select('+password')
        assert(user, 401, '用户不存在')
        //compareSync验证密码
        const isPasswordCorrect = require('bcrypt').compareSync(password, user.password)
        assert(isPasswordCorrect, 401, '密码错误')
        //jwt将用户id封起来做成token
        const token = jwt.sign({
            id: user._id
        }, app.get('secret'))
        res.send(token)
    })

token相关

  • 使用jwt生成token以及校验token
//生成token 需要secret密码
    const token = jwt.sign({
            id: user._id
        }, app.get('secret'))
    localStorage.token = res.data//把token放入localStorage
    //在请求拦截器中从localStorage拿出token加到headers的authorization中
    http.interceptors.request.use(config => {
    if (localStorage.token) {
        config.headers.Authorization = 'Bearer ' + String(localStorage.token)
    }
    return config
}, err => {
    return Promise.reject(err)
})
//校验verify 从请求的headers中拿token校验 需要secret密码
async (req, res, next) => {
        const token = (req.headers.authorization || '').split(' ').pop()
        assert(token, 401, '请先登录')
        const { id } = jwt.verify(token, req.app.get('secret'))
        assert(id, 401, '请先登录')
        req.user = User.findById(id)
        assert(req.user, 401, '请先登录')
        await next()
    }

如何在Vue中添加全局方法

  • 使用vuemixin全局添加方法
Vue.mixin({
  computed: {
    uploadURL() {
      return this.$http.defaults.baseURL + '/uploads'
    }
  },
  methods: {
    getAuthHeader() {
      return {
        Authorization: `Bearer ${localStorage.token || ''}`
      }
    }
  }
})

web前端页面

样式重置

* {
  box-sizing: border-box;
  //如果不加,当宽度是100%时,加padding会将元素撑大,而不是内缩
  outline: none;
  //去掉tab时候的外框
}
html {
  font-size: 13px;
  //设置基础字体大小
}
body {
  margin: 0;
  font-family: Arial, Helvetica, sans-serif;
  line-height: 1.2em;
  //根据每行字体大小设置行高
  background: #f1f1f1;
  //设置最外层背景
}
a {
  text-decoration: none;
  color: #999;
  //初始化a标签
}

工具样式的定义

//style.scss
@import './variable'
//text
//靠左 居中 靠右
@each $var in (left, center, right) {
  .text-#{$var} {
    text-align: $var;
  }
}
//文字颜色
@each $colorKey, $color in $colors {
  .text-#{$colorKey} {
    color: $color;
  }
}
//背景颜色
@each $colorKey, $color in $colors {
  .bg-#{$colorKey} {
    background-color: $color;
  }
}
//文字尺寸大小
@each $sizeKey, $size in $sizes {
  .fs-#{$sizeKey} {
    font-size: $size * $base-font-size;
  }
}
//flex布局
.d-flex {
  display: flex;
}
.flex-column {
  flex-direction: column;
}
.flex-1 {
  flex: 1;
}
.flex-grow-1 {
  flex-grow: 1;
}
//justify-content属性
@each $k, $v in $jc {
  .jc-#{$k} {
    justify-content: $v;
  }
}
//justify-content属性
@each $k, $v in $ai {
  .ai-#{$k} {
    align-items: $v;
  }
}

//边距
@each $typeKey, $type in $spacing-types {
  @each $sizeKey, $size in $spacing-sizes {
    .#{$typeKey}-#{$sizeKey} {
      #{$type}: $size * $base-spacing-size;
    }
    .#{$typeKey}x-#{$sizeKey} {
      #{$type}-left: $size * $base-spacing-size;
      #{$type}-right: $size * $base-spacing-size;
    }
    .#{$typeKey}y-#{$sizeKey} {
      #{$type}-top: $size * $base-spacing-size;
      #{$type}-bottom: $size * $base-spacing-size;
    }
  }
  @each $directionKey, $direction in $spacing-direction {
    @each $sizeKey, $size in $spacing-sizes {
      .#{$typeKey}#{$directionKey}-#{$sizeKey} {
        #{$type}-#{$direction}: $size * $base-spacing-size;
      }
    }
  }
}
//_variables.scss
//colors
$colors: (
  "grey-1": #666,
  "grey-2": #999,
  "white": #fff,
  "yellow": #db9e3f,
  "blue-1": #4b67af,
  "blue-2": #464f73,
  "black-1": #222,
  "black-2": #212222
);

//font 字体尺寸
$base-font-size: 1rem;
$sizes: (
  "xs": 0.9231,
  "sm": 1,
  "md": 1.0769,
  "lg": 1.2308
);

//flex布局
$jc: (
  "start": flex-start,
  "end": flex-end,
  "center": center,
  "between": space-between,
  "around": space-around
);
$ai: (
  "start": flex-start,
  "end": flex-end,
  "center": center,
  "stretch": stretch
);

//边距
$spacing-types: (
  "m": margin,
  "p": padding
);
$spacing-direction: (
  "t": top,
  "l": left,
  "r": right,
  "b": bottom
);
$base-spacing-size: 1rem;
$spacing-sizes: (
  0: 0,
  1: 0.25,
  2: 0.5,
  3: 1,
  4: 1.5,
  5: 3
);

sprite精灵图片

  • 利用http://www.spritecow.com/
//sprite
.sprite {
  background: url("../img/index.png") no-repeat;
  background-size: 28.8462rem;
  display: inline-block;
  &.sprite-disclose {
    background-position: 63.546% 15.517%;
    width: 1.7692rem;
    height: 1.5385rem;
  }
}

swiper轮播插件

  • 使用vue-awesome-swiper
//基本用法
<swiper
        :options="swiperOption"
        ref="mySwiper"
      >
        <!-- slides -->
        <swiper-slide></swiper-slide>

        <!-- Optional controls -->
        <div
          class="swiper-pagination text-right pr-3"
          slot="pagination"
        ></div>
      </swiper>
  • 要下面有标记点需要添加选项option
//swiper的选项
 swiperOption: {
        autoHeight: true,
        loop: true,
        speed: 400,
        autoplay: {
          delay: 2500
        },
        pagination: {
          el: ".swiper-pagination"
        }
      }
  • 几个常用的api
    • 先通过ref找到swiper
    • $refs.mySwiper.swiper.slideTo(i)
    • $refs.mySwiper.swiper.realIndex
    • swiper元素上可以监听改变@slide-change

要实现点击高亮效果

  • @click="active = i"
  • :class="{active : active === i}"
  • 配合v-forindex使用

封装卡片组件

  • icontitle父子传值
  • 使用具名插槽
    • 类似于父子组件传值的方式
    • slotname属性,其他地方通过#name可以关联起来,在通过{xx}可以拿到传的值
    <card
      :categories="categories"
      newHero="https://ossweb-img.qq.com/upload/webplat/info/yxzj/20200108/20796372351730.jpg"
      title="英雄列表"
      icon="hero"
    >
    //具名插槽传值
      <template #swiperslide="{category}">
        <div class="d-flex ai-center jc-around flex-wrap pt-2">
          <div
            class="d-flex flex-column text-black-2 text-center p-2"
            style="width:20%"
            v-for="(hero,hi) in category.heros"
            :key="hi"
          >
            <img
              class="w-100"
              :src="hero.heroAvatar"
              alt=""
            >
            <span class="pt-2">{{hero.heroName}}</span>
          </div>
        </div>
      </template>
    </card>
//具名插槽
        <slot
            name="swiperslide"
            :category="category"
          ></slot>

在chrome的console拿数据的方式

  • ?('.xxx')相当于jQuery一样

后台录入数据

打乱顺序

  • const randomCats = cats.slice(0).sort((a, b) => Math.random() - 0.5)

require-all

  • 使用require-all先引用一遍model

数据库插入

        await Article.deleteMany({})
        await Article.insertMany(newsList)

利用mongoose的API写接口

点击router-link组件不刷新?

  • 试着给router-view绑定key$route.path