天天看點

vue 使用element-ui基礎布局

首先建立項目,在此之前,我們需要全局安裝npm

接着執行如下指令:

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

通路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>      
<uploader @setImage="setImage" :url.sync="temp.picture"/>      
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