vue 使用element-ui基础布局

首先创建项目,在此之前,我们需要全局安装npm

接着执行如下指令:

npm install -g vue-cli
vue init webpack vueDemo
cd vueDemo
npm install
npm run dev

webpack为官方推荐的模板,还可以执行如下代码创建vue项目(vue3.0),但是这种方法初始化后没有this.$emit()等方法,具体原因不清楚。

vue create vueDemo

 

访问http://localhost:8080就可以看到我们新创建的vue项目了

然后安装element-ui

npm i element-ui -S

接着在main.js文件中集成element-ui

// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import App from './App'

Vue.use(ElementUI)
Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  components: { App },
  template: '<App/>'
})

接下来我们在App.vue中增加一个顶级的router-view

<template>
  <div id="app">
    <router-view></router-view>
  </div>
</template>

<script>
import router from '@/js/router'
export default {
  name: 'App',
  router,
  components: {
  },
  created () {
  },
  data () {
    return {
    }
  },
  methods: {

  }
}

</script>

<style>

</style>

写router.js

import Vue from 'vue'
import VueRouter from 'vue-router'
import Layout from '@/layout/Layout'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    component: Layout,
    redirect: '/student',
    children: [
      {
        path: 'student',
        name: 'student',
        component: () => import('@/components/student/student')
      }
    ]
  }
]

// 路由配置
const RouterConfig = {
  mode: 'history', // require service support
  scrollBehavior: () => ({
    y: 0
  }),
  routes
}
// export const router = new Router(RouterConfig)
const createRouter = () => new VueRouter(RouterConfig)

// 创建路由实例
const router = createRouter()
// 添加动态路由
// addAsyncRouter()

export default router

我们可以看到定义的路由组件有子组件,这样我们就可以直接在App.vue中定义一个router-view,然后其它什么也不用写,将路由全部路由到Layout组件中,这样就可以所有页面统一样式了,每一个菜单的特定路由定义在children路由中

当然也可以在App.vue中引入Layout组件,如果这样,所有的菜单路由就不需要统一路由到Layout组件了。

然后我们写一个布局文件Layout.vue

<template>
  <div>
    <el-row>
      <el-col :span="3">
        <side-bar></side-bar></el-col>
      <el-col :span="21">
        <router-view></router-view></el-col>
    </el-row>
  </div>
</template>

<script>
import sideBar from '../components/SideBar/sideBar'
export default {
  name: '',
  components: {
    sideBar
  }
}
</script>

<style scoped>

</style>

其中sideBar是我们自定义的左侧导航

by the way 封装以下http请求

http.js

import axios from 'axios' // 引入axios

// 响应拦截器
// 响应拦截器
axios.interceptors.response.use(
  response => {
    // 如果返回的状态码为200,说明接口请求成功,可以正常拿到数据
    // 否则的话抛出错误
    if (response.status === 200) {
      return Promise.resolve(response)
    } else {
      return Promise.reject(response)
    }
  },
  // 服务器状态码不是2开头的的情况
  // 这里可以跟你们的后台开发人员协商好统一的错误状态码
  // 然后根据返回的状态码进行一些操作,例如登录过期提示,错误提示等等
  // 下面列举几个常见的操作,其他需求可自行扩展
  error => {
    if (error.response.status) {
      return Promise.reject(error.response)
    }
  })

/**
 * get方法,对应get请求
 * @param {String} url [请求的url地址]
 * @param {Object} params [请求时携带的参数]
 */
export function get (url, params) {
  return new Promise((resolve, reject) => {
    axios.get(url, {
      params: params
    }).then(res => {
      resolve(res.data)
    }).catch(err => {
      reject(err.data)
    })
  })
}

/**
 * post方法,对应post请求
 * @param {String} url [请求的url地址]
 * @param {Object} params [请求时携带的参数]
 */
export function post (url, params) {
  return new Promise((resolve, reject) => {
    axios.post(url, params)
      .then(res => {
        resolve(res.data)
      })
      .catch(err => {
        reject(err.data)
      })
  })
}

api.js

/**
 * api接口统一管理
 */
import { get, post } from './http'
const preUrl = 'http://localhost:8081/'
export const apiAddress = p => get(preUrl + '/stuUser/list', p)

export const apiAddressa = p => post(preUrl + '/stuUser/list', p)

然后写一个router.js中定义的student组件

<template>
  <div>
  <el-table

    v-loading="listLoading"
    :data="tableData"
    style="width: 100%">
    <el-table-column
      label="照片"
      width="180">
      <template slot-scope="scope">
        <i class="el-icon-time"></i>
        <span style="margin-left: 10px">
          <el-image
          style="width: 100px; height: 100px"
          :src="scope.row.picture"
          fit="fit"></el-image></span>
      </template>
    </el-table-column>
    <el-table-column
      label="学生姓名"
      width="180">
      <template slot-scope="scope">
        <el-popover trigger="hover" placement="top">
          <p>学生姓名: {{ scope.row.studentName }}</p>
          <p>学生学号: {{ scope.row.studentNumber }}</p>
          <div slot="reference" class="name-wrapper">
            <el-tag size="medium">{{ scope.row.studentName }}</el-tag>
          </div>
        </el-popover>
      </template>
    </el-table-column>
    <el-table-column
      label="手机号"
      width="180">
      <template slot-scope="scope">
        <span style="margin-left: 10px">{{ scope.row.phoneNumber }}</span>
      </template>
    </el-table-column>
    <el-table-column
      label="家庭地址"
      width="180">
      <template slot-scope="scope">
        <span style="margin-left: 10px">{{ scope.row.address }}</span>
      </template>
    </el-table-column>
    <el-table-column label="操作">
      <template slot-scope="scope">
        <el-button
          size="mini"
          @click="handleEdit(scope.$index, scope.row)">编辑</el-button>
        <el-button
          size="mini"
          type="danger"
          @click="handleDelete(scope.$index, scope.row)">删除</el-button>
      </template>
    </el-table-column>
  </el-table>
    <pagination v-show="total>0" :total="total" :page.sync="pageNum" :rows.sync="pageSize" @pagination="getList" />
    <el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible">
      <el-form ref="dataForm" :rules="rules" :model="temp" label-position="left" label-width="80px" style="width: 400px; margin-left:50px;">
       <el-form-item label="照片" prop="studentName">
         <uploader @setImage="setImage" :url.sync="temp.picture"/>

         <el-input v-model="temp.picture" />
        </el-form-item>
       <el-form-item label="学生姓名" prop="studentName">
         <el-input v-model="temp.studentName" />
        </el-form-item>
        <el-form-item label="学生学号" prop="studentNumber">
          <el-input v-model="temp.studentNumber" />
        </el-form-item>
        <el-form-item label="手机号" prop="phoneNumber">
          <el-input v-model="temp.phoneNumber" />
        </el-form-item>
        <el-form-item label="家庭地址" prop="address">
          <el-input v-model="temp.address" />
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button @click="dialogFormVisible = false">
          Cancel
        </el-button>
        <el-button type="primary" @click="dialogStatus==='create'?createData():updateData()">
          Confirm
        </el-button>
      </div>
    </el-dialog>
    </div>
</template>

<script>
import { apiAddress } from '@/js/api'
import Pagination from '@/components/Pagination' // secondary package based on el-pagination
import uploader from '../uploader/uploader'
export default {
  components: {Pagination, uploader},
  data () {
    return {
      picture: '',
      tableData: [],
      listLoading: true,
      total: 0,
      pageNum: 1,
      pageSize: 20,
      temp: {studentNumber: '',
        studentName: '',
        phoneNumber: '',
        address: '',
        id: ''},
      dialogFormVisible: false,
      dialogStatus: '',
      textMap: {
        update: '编辑',
        create: '新增'
      },
      rules: {
        address: [{ required: true, message: 'address is required', trigger: 'change' }],
        phoneNumber: [{ required: true, message: 'phoneNumber is required', trigger: 'change' }],
        studentName: [{ required: true, message: 'studentName is required', trigger: 'blur' }],
        picture: [{ required: true, message: 'picture is required', trigger: 'blur' }],
        studentNumber: [{ required: true, message: 'title is required', trigger: 'blur' }]
      }
    }
  },
  mounted () {
    this.getList()
  },
  methods: {
    setImage (val) {
      console.log(val)
    },
    getList () {
      this.listLoading = true
      apiAddress({
        page: this.pageNum,
        rows: this.pageSize
      }).then(res => {
        if (res.list.length > 0) {
          res.list.forEach(item => {
            item.picture = 'http://localhost:8081/' + item.picture
          })
          this.tableData = res.list
          this.total = res.total
          this.pageNum = res.pageNum
          this.pageSize = res.pageSize
        }
        this.listLoading = false
      })
    },
    handleEdit (index, row) {
      this.temp = Object.assign({}, row) // copy obj
      this.dialogStatus = 'update'
      this.dialogFormVisible = true
      /* this.$nextTick(() => {
        this.$refs['dataForm'].clearValidate()
      }) */
    },
    handleDelete (index, row) {
      this.$notify({
        title: 'Success',
        message: 'Delete Successfully',
        type: 'success',
        duration: 2000
      })
      this.list.splice(index, 1)
    },
    updateData () {
      this.$refs['dataForm'].validate((valid) => {
        if (valid) {
          const tempData = Object.assign({}, this.temp)
          tempData.timestamp = +new Date(tempData.timestamp) // change Thu Nov 30 2017 16:41:05 GMT+0800 (CST) to 1512031311464
        }
      })
    }
  }
}
</script>

其中涉及Pagenation分页组件和uploader图片上传组件

Pagenation index.vue

<template>
  <div :class="{'hidden':hidden}" class="pagination-container">
    <el-pagination
      :background="background"
      :current-page.sync="currentPage"
      :page-size.sync="pageSize"
      :layout="layout"
      :page-sizes="pageSizes"
      :total="total"
      v-bind="$attrs"
      @size-change="handleSizeChange"
      @current-change="handleCurrentChange"
    />
  </div>
</template>

<script>
import { scrollTo } from './scroll-to'

export default {
  name: 'Pagination',
  props: {
    total: {
      required: true,
      type: Number
    },
    page: {
      type: Number,
      default: 1
    },
    rows: {
      type: Number,
      default: 20
    },
    pageSizes: {
      type: Array,
      default () {
        return [10, 20, 30, 50]
      }
    },
    layout: {
      type: String,
      default: 'total, sizes, prev, pager, next, jumper'
    },
    background: {
      type: Boolean,
      default: true
    },
    autoScroll: {
      type: Boolean,
      default: true
    },
    hidden: {
      type: Boolean,
      default: false
    }
  },
  computed: {
    currentPage: {
      get () {
        return this.page
      },
      set (val) {
        this.$emit('update:page', val)
      }
    },
    pageSize: {
      get () {
        return this.rows
      },
      set (val) {
        this.$emit('update:rows', val)
      }
    }
  },
  methods: {
    handleSizeChange (val) {
      this.$emit('pagination', { page: this.currentPage, rows: val })
      if (this.autoScroll) {
        scrollTo(0, 800)
      }
    },
    handleCurrentChange (val) {
      this.$emit('pagination', { page: val, rows: this.pageSize })
      if (this.autoScroll) {
        scrollTo(0, 800)
      }
    }
  }
}
</script>

<style scoped>
.pagination-container {
  background: #fff;
  padding: 32px 16px;
}
.pagination-container.hidden {
  display: none;
}
</style>

scroll-to.js

Math.easeInOutQuad = function (t, b, c, d) {
  t /= d / 2
  if (t < 1) {
    return c / 2 * t * t + b
  }
  t--
  return -c / 2 * (t * (t - 2) - 1) + b
}

// requestAnimationFrame for Smart Animating http://goo.gl/sx5sts
var requestAnimFrame = (function () {
  return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function (callback) { window.setTimeout(callback, 1000 / 60) }
})()

/**
 * Because it's so fucking difficult to detect the scrolling element, just move them all
 * @param {number} amount
 */
function move (amount) {
  document.documentElement.scrollTop = amount
  document.body.parentNode.scrollTop = amount
  document.body.scrollTop = amount
}

function position () {
  return document.documentElement.scrollTop || document.body.parentNode.scrollTop || document.body.scrollTop
}

/**
 * @param {number} to
 * @param {number} duration
 * @param {Function} callback
 */
export function scrollTo (to, duration, callback) {
  const start = position()
  const change = to - start
  const increment = 20
  let currentTime = 0
  duration = (typeof (duration) === 'undefined') ? 500 : duration
  var animateScroll = function () {
    // increment the time
    currentTime += increment
    // find the value with the quadratic in-out easing function
    var val = Math.easeInOutQuad(currentTime, start, change, duration)
    // move the document.body
    move(val)
    // do the animation unless its over
    if (currentTime < duration) {
      requestAnimFrame(animateScroll)
    } else {
      if (callback && typeof (callback) === 'function') {
        // the animation is done so lets callback
        callback()
      }
    }
  }
  animateScroll()
}

uploader.vue

<template>
  <el-upload
    class="avatar-uploader"
    action="http://localhost:8081/uploadFileToFast"
    :show-file-list="false"
    :on-success="handleAvatarSuccess"
    :before-upload="beforeAvatarUpload">
    <img v-if="url" :src="url" class="avatar">
    <i v-else class="el-icon-plus avatar-uploader-icon"></i>
  </el-upload>
</template>

<style>
  .avatar-uploader .el-upload {
    border: 1px dashed #d9d9d9;
    border-radius: 6px;
    cursor: pointer;
    position: relative;
    overflow: hidden;
  }
  .avatar-uploader .el-upload:hover {
    border-color: #409EFF;
  }
  .avatar-uploader-icon {
    font-size: 28px;
    color: #8c939d;
    width: 178px;
    height: 178px;
    line-height: 178px;
    text-align: center;
  }
  .avatar {
    width: 178px;
    height: 178px;
    display: block;
  }
</style>

<script>
export default {
  props: {
    url: {
      required: true,
      type: String
    }
  },
  /* data () {
    return {
      imageUrl: ''
    }
  }, */
  /*  computed: {
    imageUrl: {
      get () {
        return this.url
      },
      set (val) {
        this.$emit('update:url', val)
      }
    }
  }, */
  methods: {
    handleAvatarSuccess (res, file) {
      this.$emit('update:url', 'http://localhost:8081/' + res)
      this.$emit('setImage', { url: res })
    },
    beforeAvatarUpload (file) {
      const isJPG = file.type === 'image/jpeg'
      const isLt2M = file.size / 1024 / 1024 < 2

      if (!isJPG) {
        this.$message.error('上传头像图片只能是 JPG 格式!')
      }
      if (!isLt2M) {
        this.$message.error('上传头像图片大小不能超过 2MB!')
      }
      return isJPG && isLt2M
    }
  }
}
</script>

组件的prop不可以直接修改,如果要响应式双向绑定,正如代码中的样子,子组件定义prop,父组件使用子组件时,绑定自身相对应的data,如下代码:

<uploader @setImage="setImage" :url.sync="temp.picture"/>

注意到需要加上.sync,然后子组件中不可以直接修改,需要触发$emit方法

 this.$emit('update:url', 'http://localhost:8081/' + res) //修改url 直接this.url=xxx会报错 不允许在子组件中直接修改prop 执行此步骤父组件相对应的temp.picture就会响应式被修改
      this.$emit('setImage', { url: res }) //调用父组件方法,传参为url(此处传参是以数组形式,父组件接收参数使用一个参数即可,如果是执行了上一步代码,实现了双向绑定,这一步就可以省略啦)
//还有一种方式是定义计算属性
 computed: {
imageUrl: {
get () {
return this.url
},
set (val) {
this.$emit('update:url', val)
}
}
}
给imageUrl赋值的时候,自动就更新了子组件的prop url,父组件与之对应的temp.picture

 

posted @ 2021-10-14 17:28  陈扬天  阅读(1650)  评论(0编辑  收藏  举报