首先建立項目,在此之前,我們需要全局安裝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