簡單梳理一下這次vue項目的知識點
utils
request.js
主要是封裝axios模塊用得
非組件模塊可以這樣加載使用 element 的 message 提示組件
import { Message } from 'element-ui'
// 創建一個 axios 實例,說白了就是複製了一個 axios
// 我們通過這個實例去發請求,把需要的配置配置給這個實例來處理
const request = axios.create({
baseURL: 'http://ttapi.research.itcast.cn/', // 請求的基礎路徑
// 定義後端返回的原始數據的處理
// 參數 data 就是後端返回的原始數據(未經處理的 JSON 格式字符串)
transformResponse: [function (data) {
// Do whatever you want to transform the data
// console.log(data)
// 後端返回的數據可能不是 JSON 格式字符串
// 如果不是的話,那麼 JSONbig.parse 調用就會報錯
// 所以我們使用 try-catch 來捕獲異常,處理異常的發生
try {
// 如果轉換成功,則直接把結果返回
return JSONbig.parse(data)
} catch (err) {
console.log('轉換失敗', err)
// 如果轉換失敗了,則進入這裏
// 我們在這裏把數據原封不動的直接返回給請求使用
return data
}
// axios 默認在內部使用 JSON.parse 來轉換處理原始數據
// return JSON.parse(data)
}]
})
在這裏面可以設置請求攔截器,響應攔截器
請求攔截器可以對一些請求進行預處理,比如加入token啥的
// 請求攔截器
request.interceptors.request.use(
// 任何所有請求會經過這裏
// config 是當前請求相關的配置信息對象
// config 是可以修改的
function (config) {
const user = JSON.parse(window.localStorage.getItem('user'))
// 如果有登錄用戶信息,則統一設置 token
if (user) {
config.headers.Authorization = `Bearer ${user.token}`
}
// 然後我們就可以在允許請求出去之前定製統一業務功能處理
// 例如:統一的設置 token
// 當這裏 return config 之後請求在會真正的發出去
return config
},
// 請求失敗,會經過這裏
function (error) {
return Promise.reject(error)
}
)
api
permission.js
role.js
user.js
都會從utils中導入request,爲不同的請求設置訪問後端的端口號,請求方法添加參數
然後再export出去,讓別的模塊調用
router
index.js
路由是設置訪問的時候會到達哪個自己定義的組件上去,也可以做一些權限控制,同時也可以形成父子組件的形式。
import Vue from 'vue'
import VueRouter from 'vue-router'
// 在 VueCLI 創建的項目中 @ 表示 src 目錄
// 它是 src 目錄的路徑別名
// 好處:它不受當前文件路徑影響
// 注意:@ 就是 src 路徑,後面別忘了寫那個斜槓
// 使用建議:如果加載的資源路徑就在當前目錄下,那就正常寫
// 如果需要進行父級路徑查找的都使用 @
import Login from '@/views/login/'
import Layout from '@/views/layout/'
import Home from '@/views/root'
import User from '@/views/user'
import Permission from '@/views/permission'
// 非組件模塊可以這樣加載使用 element 的 message 提示組件
// 注意import要寫在Vue.use(VueRouter)前面
import { Message } from 'element-ui'
Vue.use(VueRouter)
// 路由配置表
const routes = [
{
path: '/login',
name: 'login',
component: Login
},
{
path: '/',
// 命名路由 layout 有一個默認子路由,這個名字沒有意義,所以警告
// 正確的做法是:如果有默認子路由,就不要給父路由起名字了
// name: 'layout',
component: Layout,
children: [
{
path: '', // path 爲空,會作爲默認子路由渲染
// 路由的名字是幹啥的?
// 參考:https://gitee.com/lipengzhou/toutiao-publish-admin/issues/I1F1BA
name: 'home',
component: Home
},
{
path: '/user',
name: 'user',
component: User
},
{
path: '/permission',
name: 'permission',
component: Permission
}
]
}
]
const router = new VueRouter({
routes
})
// 路由導航守衛(攔截器)的作用就是控制頁面的訪問狀態
// beforeEach 是全局前置守衛,任何頁面的訪問都要經過這裏
// 路由導航守衛:說白了所有頁面的導航都會經過這裏
// 守衛頁面的導航的
// to:要去的路由信息
// from:來自哪裏的路由信息
// next:放行方法
router.beforeEach((to, from, next) => {
// 如果要訪問的頁面不是 /login,校驗登錄狀態
// 如果沒有登錄,則跳轉到登錄頁面
// 如果登錄了,則允許通過
// 允許通過
// next()
var user = JSON.parse(window.localStorage.getItem('user'))
if (to.path !== '/login') {
if (user) {
// 校驗非登錄頁面的登錄狀態
var roleNames = []
var roleList = user.roleList
for (var i = 0; i < roleList.length; i++) {
roleNames.push(roleList[i].roleName)
console.log(roleNames)
}
if (to.path === '/permission') {
if (roleNames.indexOf('permissionControl') !== -1) {
next()
} else {
Message.error('不好意西,沒有權限')
next('/')
}
} else {
next()
}
} else {
// 沒有登錄,跳轉到登錄頁面
next('/login')
}
} else {
// 登錄頁面,正常允許通過
next()
}
})
// 我們在組件中使用的 this.$router 其實就是這個模塊中的 router
export default router
Views
login/index.vue
登錄組件
表單
el-form 表單組件
每個表單項都必須使用 el-form-item 組件包裹
表單提交可以設置:loading 讓他轉起來等待
配置 Form 表單驗證:
- 必須給 el-from 組件綁定 model 爲表單數據對象
- 給需要驗證的表單項 el-form-item 綁定 prop 屬性
注意:prop 屬性需要指定表單對象中的數據名稱 - 通過 el-from 組件的 rules 屬性配置驗證規則
如果內置的驗證規則不滿足,也可以自定義驗證規則
手動觸發表單驗證:
- 給 el-form 設置 ref 起個名字(隨便起名,不要重複即可)
- 通過 ref 獲取 el-form 組件,調用組件的 validate 進行驗證
formRules: { // 表單驗證規則配置
// 要驗證的數據名稱:規則列表[]
account: [
{ required: true, message: '請輸入手機號', trigger: 'change' },
{ pattern: /^[0-9]*$/, message: '請輸入正確的號碼格式', trigger: 'change' }
],
password: [
{ required: true, message: '驗證碼不能爲空', trigger: 'change' },
{ pattern: /^[0-9]*$/, message: '請輸入正確密碼格式' }
],
agree: [
{
// 自定義校驗規則: 因爲複選框裏面沒有這個required,需要自己定義規則,所以需要使用validator
// 驗證通過:callback()
// 驗證失敗:callback(new Error('錯誤消息'))
// value爲是否勾選上,數據值
validator: (rule, value, callback) => {
if (value) {
callback()
} else {
callback(new Error('請同意用戶協議'))
}
},
// message: '請勾選同意用戶協議',
trigger: 'change'
}
是否同意協議驗證要在點擊登錄的時候進行驗證,所以開啓手動驗證
methods: {
onLogin () {
// 獲取表單數據(根據接口要求綁定數據)
// const user = this.user
// 表單驗證
// validate 方法是異步的 參數可以是兩個,一個valid是驗證的結果,另一個是error,缺失的項
this.$refs['login-form'].validate((valid) => {
// 如果表單驗證失敗,停止請求提交
if (!valid) {
return
}
// 驗證通過,請求登錄
this.login()
})
}
}
登錄成功了,把usre放入本地存儲中
window.localStorage.setItem('user', JSON.stringify(res.data.data))
並且關閉轉圈圈,簡單提示一下
this.$message({
message: '登錄成功',
type: 'success'
})
layout/index.vue
layout/components/asside.vue
先說一下傳遞參數的問題,在index.vue,點擊左上角,然後asside組件向右滑動,再點擊劃出來
首先index頁面點擊圖標觸發事件,@click="isCollapse = !isCollapse",因爲asside組件雙向綁定了isCollapse值
<app-aside class="aside-menu" :is-collapse="isCollapse"/>
所以在asside.vue中
export default {
name: 'AppAside', // 注意這個名字,父組件可以使用AppAside,也可以app-aside
components: {},
props: ['is-collapse'], // 注意這裏傳過來的參數是is-collapse,但是要使用的話寫的是isCollapse
data () {
return {
// isCollapse: true
}
},
computed: {},
watch: {},
created () {},
mounted () {},
methods: {}
}
然後動態的改變圖標樣式
<!--
class 樣式處理
{
css類名: 布爾值
}
true:作用類名
false:不作用類名
-->
<i
:class="{
'el-icon-s-fold': isCollapse,
'el-icon-s-unfold': !isCollapse
}"
@click="isCollapse = !isCollapse"
></i>
一些注意事項
<el-dropdown-item>設置</el-dropdown-item>
<!--
組件默認是不識別原生事件的,除非內部做了處理
https://cn.vuejs.org/v2/guide/components-custom-events.html#%E5%B0%86%E5%8E%9F%E7%94%9F%E4%BA%8B%E4%BB%B6%E7%BB%91%E5%AE%9A%E5%88%B0%E7%BB%84%E4%BB%B6
-->
<el-dropdown-item
@click.native="onLogout"
>退出</el-dropdown-item>
permission/index.vue
主要有個樹形表格的使用,主要在el-table中有個
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
對於屬性表格數據的處理,還是遞歸來寫的
getTreeData (permissionData, pid) {
var res = []
for (var i = 0; i < permissionData.length; i++) {
var node = permissionData[i]
if (pid !== null && node.pid === pid) {
node.children = this.getTreeData(permissionData, node.id)
res.push(node)
}
}
return res
}
.sync用法
當子組件需要更新 title 的值時,它需要顯式地觸發一個更新事件:
this.$emit('update:title', newValue)
這樣title的屬性在子組件內部更新,父組件也能感知的到,實現了“雙向綁定”。
Table 表格組件
-
把需要展示的數組列表數據綁定給 table 組件的 data 屬性
注意:你不用去 v-for 遍歷,它自己會遍歷 -
設計表格列 el-table-column
width 可以設定表格列的寬度
label 可以設定列的標題
prop 用來設定要渲染的列表項數據字段,只能展示文本 -
表格列默認只能渲染普通文本,如果需要展示其它內容,例如放個按鈕啊、放個圖片啊,那就需要自定義表格列模板了:https://element.eleme.cn/#/zh-CN/component/table#zi-ding-yi-lie-mo-ban
<el-table-column
prop="userName"
label="用戶名">
<template slot-scope="scope">
<el-popover
placement="top-start"
title="tips"
width="200"
trigger="hover"
content="點擊查看詳情">
<el-tag slot="reference" @click="getDetail1(scope.row.id)" style="width: 100px">{{ scope.row.userName }}</el-tag>
</el-popover>
</template>
</el-table-column>
<el-table-column
label="操作">
<!-- 如果需要自定義表格列模板,則把需要自定義的內容放到 template 裏面 -->
<template slot-scope="scope">
<!-- Form -->
<el-button
circle
icon="el-icon-edit"
type="primary"
@click="getDetail(scope.row.id)"
></el-button>
<el-dialog :title="addOrUpdate" :visible.sync="dialogFormVisible" :append-to-body="true" >
<el-form :model="form" :rules="rules" style="width: 450px">
<el-form-item label="用戶名" :label-width="formLabelWidth" prop="userName" >
<el-input v-model="form.userName" autocomplete="off" :disabled="inputFlag"></el-input>
</el-form-item>
<el-form-item label="賬號" :label-width="formLabelWidth" prop="account">
<el-input v-model="form.account" autocomplete="off" :disabled="inputFlag"></el-input>
</el-form-item>
<el-form-item label="密碼" :label-width="formLabelWidth" prop="password">
<el-input v-model="form.password" autocomplete="off" :disabled="inputFlag"></el-input>
</el-form-item>
<el-form-item label="角色" style="margin-left: 80px">
<el-checkbox-group v-model="checkList" :disabled="inputFlag">
<el-checkbox v-for="(role,index) in roleList"
:key="index"
:label="role.id"
:value="role.id">{{ role.roleName }}
</el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">取 消</el-button>
<el-button type="primary" @click="toUpdate()">確 定</el-button>
</div>
</el-dialog>
<el-button
style="margin-left: 20px"
type="danger"
icon="el-icon-delete"
circle
@click="toDelete(scope.row.id)"
></el-button>
</template>
</el-table-column>
數據分頁
<!-- /數據列表 -->
<!-- 列表分頁 -->
<!--
total 用來設定總數據的條數
它默認按照 10 條每頁計算總頁碼
page-size 每頁顯示條目個數,支持 .sync 修飾符,默認每頁 10 條
90 3 90 / 3 = 30
-->
<el-pagination
layout="prev, pager, next"
background
:total="totals"
:page-size="limit"
:disabled="loading"
:current-page.sync="page"
@current-change="onCurrentChange"
/>
<!-- onCurrentChange爲自己定義的觸發頁數改變時的方法-->
<!-- /列表分頁 -->
路由的name
如果你要使用 JavaScript 跳轉到這個動態路由,則你需要這樣寫:
this.$router.push('/user/' + 用戶ID)
如果是在模板中進行路由導航,那就是這樣的:
以上的方式雖然簡單粗暴,但是通過拼接字符串得到完整路由路徑進行導航不太直觀。
所以更好的方式就是給路由配置對象起一個名字,就像下面這樣,這個 name 和 path 沒有任何關係,它就是一個代號,需要注意的是路由的 name 不能重複。
const router = new VueRouter({
routes: [
{
path: '/user/:userId',
name: 'user',
component: User
}
]
})
現在你可以這樣處理路由導航:
router.push({ name: 'user', params: { userId: 123 }})
所以結論就是:無論是否需要使用路由的 name,都建議給它寫上,當你需要的時候就非常有用了,這是一個建議的做法。
後端
對於後端,最好都要配上RestController,然後POST或者GET要和前端的保持一致,還有參數問題,要加上RequestBody 或者RequestParam註解
問題點1:
如果Content-Type設置爲“application/x-www-form-urlencoded;charset=UTF-8”無論是POST請求還是GET請求都是可以通過這種方式成功獲取參數,但是如果前端POST請求中的body是Json對象的話,會報上述錯誤。
請求中傳JSON時設置的Content-Type 如果是application/json或者text/json時,JAVA中request.getParameter("")怎麼也接收不到數據。這是因爲,Tomcat的HttpServletRequest類的實現類爲org.apache.catalina.connector.Request(實際上是org.apache.coyote.Request)。
問題點2:
當前端請求的Content-Type是Json時,可以用@RequestBody這個註解來解決。@RequestParam 底層是通過request.getParameter方式獲得參數的,換句話說,@RequestParam 和request.getParameter是同一回事。因爲使用request.getParameter()方式獲取參數,可以處理get 方式中queryString的值,也可以處理post方式中 body data的值。所以,@RequestParam可以處理get 方式中queryString的值,也可以處理post方式中 body data的值。@RequestParam用來處理Content-Type: 爲 application/x-www-form-urlencoded編碼的內容,提交方式GET、POST。
@RequestBody接受的是一個json對象的字符串,而不是Json對象,在請求時往往都是Json對象,用JSON.stringify(data)的方式就能將對象變成json字符串。
總結:
前端請求傳Json對象則後端使用@RequestParam;
前端請求傳Json對象的字符串則後端使用@RequestBody。