一直想把一大篇的總結寫完、寫好,感受本身拖延太嚴重還總想寫完美,而後好多筆記都死在編輯器裏了,之後還按照一個小節一個小節的更新吧,小步快跑😂,先發出來,之後再迭代吧。javascript
最近咱們參與開發了一個(年前了)BI項目,前端使用vue全家桶,項目功能基本開發完成,剩下的修修補補,開發過程還算順暢,期間遇到好多問題,也記錄了一下,發出來一塊兒交流,主要是思路,怎麼利用vue給的API實現功能,避免你們在一樣的坑裏待太長時間,若是有更好實現思路能夠一塊兒交流討論😎🤗。css
先後端分離形式開發,vue+vueRouter+vueX+iviewUI+elementUI
,大部分功能咱們都用的iviewUI,有部分組件咱們用了elementUI,好比表格、日曆插件,咱們沒接mock工具,接口用文檔的形式交流,團隊氛圍比較和諧,三個PHP三個前端,效率還能夠,兩個前端夥伴比較厲害,第一次使用vue,就承擔了90%的開發工做任務,我沒到上線就跑回家休陪產假了,特別感謝同事們的支持,我才能回家看娃。html
前端其實不太複雜,可是隻要用vue開發基本上都會遇到的幾個問題,好比菜單組件多級嵌套、刷新後選中當前項、前端
涉及幾個點,表格表頭表體合併、文件上傳、富文本編輯器、權限樹等等。vue
系統的主要功能就是面向各個部門查看報表數據,後端同窗們很厲害,能彙總到一個集團的全部數據,各類炫酷大數據技術;java
菜單功能:node
項目預覽圖:jquery
對勾爲已更新。ios
大部分的交互的流程都是 「ajax請求數據=>傳入組件渲染」,不少屬性須要異步傳入子組件而後進行相關的計算,若是綁定不少computed或者watch,性能開銷會很大,並且有些場景並不須要使用computed和watch,咱們只須要在最初建立的時候獲取一次就夠了。ajax
以下gif例子,點擊上方TAB後從新刷新折線組件:
<!--模板-->
<mapBox v-if="mapData" :data="mapData"></mapBox>
複製代碼
<!--點擊搜索後執行-->
let This = this
// setp1 重點
this.mapData = false
this.$http
.post('/api/show/mapcondition',{key:key,type:type})
.then(function(response){
// setp2 重點
this.mapData = response.data
})
複製代碼
有時候會出現DOM元素與數據不一樣步,可使用使用其餘方式讓DOM強刷
- setTimeou
- $forceUpdate()
- $nextTick()
- $set()
複製代碼
有時候會涉及到父組件調用子組件方法的狀況,例如,iview的Tree組件暴露出來的getCheckedAndIndeterminateNodes
方法,詳見官網文檔link。
<!--模板-->
<Tree v-if="menu" :data="menu" show-checkbox multiple ref="Tree"></Tree>
複製代碼
let rules = this.$refs.Tree.getCheckedAndIndeterminateNodes();
複製代碼
遞歸組件用的不少,咱們的左側菜單還有無限拆分的表格合併,都用到了遞歸組件,詳見官網連接link。
效果圖:
大體思路就是先建立一個子組件,而後再建立一個父組件,循環引用,拿左側菜單說明,代碼以下,數據結構也在父組件中。
<!--index.vue 父組件 數據接口在default中-->
<template>
<Menu width="auto" theme="dark" :active-name="activeName" :open-names="openNames" @on-select="handleSelect" :accordion="true" >
<template v-for="(item,index) in items">
<side-menu-item v-if="item.children&&item.children.length!==0" :parent-item="item" :name="index+''" :index="index" >
</side-menu-item>
<menu-item v-else :name="index+''" :to="item.path" >
<Icon :type="item.icon" :size="15"/>
<span>{{ item.title }}</span>
</menu-item>
</template>
</Menu>
</template>
<script> import sideMenuItem from '@/components/Menu/side-menu-item.vue' export default { name: 'sideMenu', props: { activeName: { type: String, default: 'auth' }, openNames: { type: Array, default: () => [ 'other', 'role', 'auth' ] }, items: { type: Array, default: () => [ { name : 'system', title : '數據看板', icon : 'ios-analytics', children: [ { name : 'user', title : '用戶管理', icon : 'outlet', children : [ { name : 'auth', title : '權限管理1', icon : 'outlet' }, { name : 'auth', title : '權限管理', icon : 'outlet', children:[ { name : '334', title : '子菜單', icon : 'outlet' }, { name : '453', title : '子菜單', icon : 'outlet' } ] } ] } ] }, { name : 'other', title: '其餘管理', icon : 'outlet', } ] } }, components: { sideMenuItem }, methods: { handleSelect(name) { this.$emit('on-select', name) } } } </script>
複製代碼
<!--side-menu-item.vue 子組件-->
<template>
<Submenu :name="index+''">
<template slot="title" >
<Icon :type="parentItem.icon" :size="10"/>
<span>{{ parentItem.title }}</span>
</template>
<template v-for="(item,i) in parentItem.children">
<side-menu-item v-if="item.children&&item.children.length!==0" :parent-item="item" :to="item.path" :name="index+'-'+i" :index="index+'-'+i" >
</side-menu-item>
<menu-item v-else :name="index+'-'+i" :to="item.path">
<Icon :type="item.icon" :size="15" />
<span>{{ item.title }}</span>
</menu-item>
</template>
</Submenu>
</template>
<script> export default { name: 'sideMenuItem', props: { parentItem: { type: Object, default: () => {} }, index:{} }, created:function(){ } } </script>
複製代碼
不少菜單項都只是入參不同,是不會從新走業務邏輯的,咱們就用watch監聽$router,若是改變就從新請求新的數據。
export default {
watch: {
'$route':'isChange'
},
methods:{
getData(){
// Do something
},
isChange(){
this.getData()
},
}
}
複製代碼
頁面刷新後左側菜單的默認選中項就和頁面對應不上了,咱們用$router的beforeEnter方法作判斷,根據地址得到路由的key(每個路由都有一個key的參數),儲存到localStorage中,而後菜單組件再從localStorage中取出key,再遍歷匹配到當前選項目,比較冗餘的是咱們要在beforeEnter中獲取一遍菜單數據,而後到菜單組件又獲取一次數據,請求兩次接口。
step1 router.js中設置beforeEnter方法,得到地址欄中的key 存儲到localStorage
step2 菜單組件取出localStorage中key,遞歸匹配
複製代碼
Axios的interceptors方法有request和response兩個方法對請求的入參和返回結果作統一的處理。
<!--request 除登陸請求外,其餘均增長token字段 -->
axios.interceptors.request.use(function (config) {
let token = localStorage.getItem('token')
if(token== null && router.currentRoute.path == '/login'){// 本地無token,未登陸 跳轉至登陸頁面
router.push('/login')
}else{
if(config.data==undefined){
config.data = {
"token":token
}
}else{
Object.assign(config.data,{"token":token})
}
}
return config
}, function (error) {
iView.Message.error('請求失敗')
return Promise.reject(error)
})
<!--response 返回狀態統一處理 -->
axios.interceptors.response.use(function (response) {
if(response.hasOwnProperty("data") && typeof response.data == "object"){
if(response.data.code === 998){// 登陸超時 跳轉至登陸頁面
iView.Message.error(response.data.msg)
router.push('/login')
return Promise.reject(response)
}else if (response.data.code === 1000) {// 成功
return Promise.resolve(response)
} else if (response.data.code === 1060){ //數據定製中
return Promise.resolve(response)
}else {// 失敗
iView.Message.error(response.data.msg)
return Promise.reject(response)
}
} else {
return Promise.resolve(response)
}
}, function (error) {
iView.Message.error('請求失敗')
// 請求錯誤時作些事
return Promise.reject(error)
})
複製代碼
測試同窗提出bug,左側菜單選中後,再次點擊選中項沒有刷新,用戶體驗很差,產品同窗一致經過,咱們就用野路子來解決了。 給菜單組件設置on-select事件,點擊後存儲當前選中項的path,每次執行當前點擊的path和存儲的path作對比,若是一致,跳轉到空白頁,空白頁再返回到當前頁,實現假刷新,注:不知道是router.push有節流控制仍是怎麼回事,不加setTimeout無論用。
<!--菜單的handleSelect事件-->
handleSelect(name) {
let This = this
if((this.selectIndex == 'reset') || (name == this.selectIndex)){
// 點擊再次刷新
setTimeout(function function_name(argument) {
This.$router.push({
path: '/Main/about',
query: {
t: Date.now()
}
})
},1)
}
this.selectIndex = name
this.$emit('on-select', name)
},
複製代碼
<!--空白頁-->
created(){
let This = this
setTimeout(function function_name(argument) {
This.$router.go(-1);
},1)
}
複製代碼
有一部分狀況是切換路由時,只改變參數,在「4. 使用watch監聽路由參數從新獲取數據」中提到過,還有一部分功能的接口數據返回的特別慢,會出現切換菜單後,數據才加載出來,須要增長切換菜單後取消原來的請求,代碼註釋中 setp一、二、3爲順序
export default {
data(){
return {
// setp1 建立data公共的source變量
source:''
}
},
created:function(){
// 獲取搜索數據
this.getData()
},
watch:{
'$route':'watchGetSearchData',
},
methods:{
getData(){
// setp2 請求時建立source實例
let CancelToken = this.$http.CancelToken
this.source = CancelToken.source();
},
watchGetSearchData(){
// setp3 切換路由時取消source實例
this.source.cancel('0000')
this.getData()
this.$http
.post('/api/show/map',data,{cancelToken:this.source.token})
.then(function(response){
})
}
}
}
複製代碼
咱們項目用到的的組件表格有兩種,一種用iview的table,帶操做按鈕的表格,支持表頭跨行跨列,另外一種element的table組件,純數據展現,支持表頭和標題的跨行跨列。
element的table組件支持表頭標題合併,咱們定義數據結構包含三部分,表頭、表體、表體合併項。 表頭直接使用遞歸組件嵌套就能夠了,表體數據直接扔給table組件,合併經過cellMerge方法遍歷合併項數據遍歷合併,代碼以下。
數據結構
data:{
historyColumns:[ // 表頭數據
{
"title": " ",
"key": "column"
},
{
"title": "指標",
"key": "target"
},
{
"title": "11/22",
"key": "11/22"
},
{
"title": "日環比",
"key": "日環比"
},
{
"title": "當週值",
"key": "當週值"
},
{
"title": "上週同期",
"key": "上週同期"
},
{
"title": "周環比",
"key": "周環比"
},
{
"title": "近7日累計",
"key": "近7日累計"
},
{
"title": "當月累計",
"key": "當月累計"
}
],
histories:[ // 表體數據
{
"target": "在售量",
"11/22": 912,
"日環比": "-",
"當週值": 912,
"上週同期": 0,
"周環比": "100%",
"近7日累計": 912,
"當月累計": 912,
"column": "基礎指標"
},
{
"target": "-在售外庫車量",
"11/22": 29,
"日環比": "-",
"當週值": 29,
"上週同期": 0,
"周環比": "100%",
"近7日累計": 29,
"當月累計": 29,
"column": "基礎指標"
}
],
merge:[ // 表體合併項
{
"rowNum": 0,
"colNum": 0,
"ropSpan": 1,
"copSpan": 4
},
{
"rowNum": 4,
"colNum": 0,
"ropSpan": 1,
"copSpan": 27
}
]
}
複製代碼
表體合併說明: 表格有cellMerge方法,每一td在渲染時都會執行這個方法,在cellMerge裏遍歷merge數據,根據cellMerge的入參行、列定位到td,若是是要合併的表格,則return出要合併的行數和列數,若是在合併的範圍內,則要return [0,0],隱藏當前td。
好比要把A、B、C、D,merge的數據rowNum爲A的行、colNum爲A的列、ropSpan爲二、copSpan爲2,在cellMerge方法中,若是座標爲A的單元格,return ropSpan和copSpan,若是座標爲B、C、D則要return [0,0]隱藏,不然會出現表格錯亂。
// 表格合併主方法 row:行數組 column:列數據 rowIndex、columnIndex行列索引
cellMerge({ row, column, rowIndex, columnIndex }) {
let This = this;
if(This.configJson){
for(let i = 0; i < This.configJson.length; i++){
let rowNum = This.configJson[i].rowNum // 行
let colNum = This.configJson[i].colNum // 列
let ropSpan = This.configJson[i].ropSpan // 跨列數
let copSpan = This.configJson[i].copSpan // 跨行數
if(rowIndex == rowNum && columnIndex == colNum ){// 當前表格index 合併項
return [copSpan,ropSpan]
// 隱藏範圍內容的單元格
// 行範圍 rowNum <= rowIndex && rowIndex < (rowNum+copSpan)
// 列範圍 colNum <= columnIndex && columnIndex < (colNum+ropSpan)
}else if( rowNum <= rowIndex && rowIndex < (rowNum+copSpan) && colNum <= columnIndex && columnIndex < (colNum+ropSpan) ){
return [0,0]
}
}
}
}
複製代碼
**表頭合併說明:**element和iview的表頭合併數據格式能夠同樣,都是遞歸形式,區別是iview的table組件直接把數據扔給組件就能夠了,而element須要本身封裝一下表頭。
// 子組件
<template>
<el-table-column :prop="thList.key" :label="thList.title" align="center"> <template v-for="(item,i) in thList.children" > <tableItem v-if="item.children&&item.children.length!==0" :thList="item" /></tableItem> <el-table-column align="center" v-else :prop="item.key" :label="item.title" :formatter="toThousands" > </el-table-column> </template> </el-table-column> </template> <script> export default { name: 'tableItem', props: { thList: { type: Object, default: () => {} }, }, } </script> 複製代碼
封裝後的table組件:
<template>
<div>
<el-table :data="Tbody" :stripe="stripe" :border="true" :span-method="cellMerge" align="center" :header-cell-style="tableHeaderColor" height="600" >
<template v-for="(item,i) in Thead">
<template v-if="item.children&&item.children.length!==0" >
<tableItem :thList="item" />
</template>
<template v-else >
<el-table-column align="center"
:prop="item.key"
:label="item.title"
:formatter="toThousands"
>
</el-table-column>
</template>
</template>
</el-table>
</div>
</template>
<script>
import tableItem from '@/components/table/tableHeader/table-Item.vue'
export default {
name: 'table-header',
props: {
Thead: {
type: Array,
default: () => {}
},
Tbody:{
type: Array,
default: () => {}
},
stripe:{
type:Boolean,
default:false
},
cellMerge:Function,
default:()=>{}
},
created:function(){
},
components:{
tableItem
},
methods:{
tableHeaderColor({ row, column, rowIndex, columnIndex }) {
if (rowIndex === 0) {
return 'background-color: #f8f8f9;'
}
}
}
}
</script>
複製代碼
其餘頁面複用table
<!--引入-->
import TableList from '@/components/table/tableHeader/index.vue'
<!--調用-->
<TableList :Thead="historyColumns" :Tbody="historyData" :cellMerge="cellMerge" />
複製代碼
iview的Menu組件有on-select方法,能夠得到當選選中項的name,咱們的name按照數據索引來遍歷的,好比三級菜單,選中後會返回2-0-1
這樣的字符串,表示樹菜單第3個菜單下的第1個子菜單下的第2個菜單項,經過這個字符串再篩選出數組['業務報表','B2C報表','成交明細']
對應菜單的title,而後發給vuex的Store.state,而後麪包屑組件經過計算數據屬性監聽Store.state拿屬性展現就能夠了。
<!-- 根據字符串篩出title數組 發給$store -->
toBreadcrumb(arrIndex){
let This = this;
let mapIndex = arrIndex.split('-');
// 獲取對應name
let box={};
let mapText = mapIndex.map(function(item,index){
if(index == 0){
box = This.MenuData[eval(item)];
}else{
box = box.children[eval(item)];
}
return box.title;
});
this.$store.commit('toBreadcrumb',mapText)
}
複製代碼
vueX代碼
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
Breadcrumb:[], // 麪包屑導航
userName: '',
readyData:""
},
mutations: {
toBreadcrumb(state,arr){
state.Breadcrumb = arr;
}
},
getters: {
getBreadcrumb: state => {
return state.Breadcrumb
}
}
})
複製代碼
麪包屑組件
<template>
<Header style="background: #fff;">
<Row>
<Col span="12">
<!-- {{doneTodosCount}} -->
<Breadcrumb>
<BreadcrumbItem v-for="item in doneTodosCount">{{item}}</BreadcrumbItem>
</Breadcrumb>
</Col>
<Col span="12">
<Login />
</Col>
</Row>
</Header>
</template>
<script> import Login from '@/components/Login' export default { data(){ return { } }, created:function(){ this.$store.commit('toBreadcrumb',['首頁']) }, computed: { doneTodosCount () { return this.$store.state.Breadcrumb } }, components:{ Login } } </script>
複製代碼
iview提供的組件特別豐富,咱們在作圖片上傳的時候,須要手動上傳,須要調用子組件的file對象經過本身的post方法提交到服務端,actionDate爲文件數據,而後再經過on-success回調反饋上傳成功或失敗。 手動上傳:
<Upload ref="upload" :data= "actionDate" :on-success="handleSuccess" :format="['png','jpg']" action="/api/upload/ccupload">
<Button icon="ios-cloud-upload-outline">點擊上傳文件</Button>
</Upload>
<div v-if="file !== null">
上傳文件: {{ file.name }}
<Button type="text" @click="upload" :loading="loadingStatus">{{ loadingStatus ? 'Uploading' : '上傳' }}</Button>
</div>
複製代碼
// upload 方法
let uploadFile = this.$refs.upload.file
this.$refs.upload.data = this.actionDate;
this.$refs.upload.post(uploadFile);
this.loadingStatus = true;
// handleSuccess 方法
this.loadingStatus = false;
if(res.code == 1000){
this.$Message.success('上傳成功')
}else{
this.$Message.error('上傳失敗')
}
複製代碼
咱們在聯調的過程當中後端說接收不到文件,咱們只能用node來驗證一下是否是組件有問題,因而用express寫了一下文件上傳。
var express = require('express');
var router = express.Router();
let fs = require('fs')
var formidable = require('formidable');//表單控件
var path = require('path');
var app = express();
app.use(express.static('/public/'));
router.post('/test',(req,res)=>{
var imgPath = path.dirname(__dirname) + '/public';
var form = new formidable.IncomingForm();
form.encoding = 'utf-8'; //設置編輯
form.uploadDir = imgPath; //設置上傳目錄
form.keepExtensions = true; //保留後綴
form.maxFieldsSize = 2 * 1024 * 1024; //文件大小
form.type = true;
form.parse(req, function(err, fields, files){
let src = files.img.path.split('/');
let urlString = src[src.length-1]
if (err) {
console.log(err);
req.flash('error','圖片上傳失敗');
return;
}
res.json({
code: '200',
type:'single',
url:'http://10.70.74.167:3000/'+urlString
})
});
});
module.exports = router;
複製代碼
咱們在測試的時候增長了一個圖片test的轉發配置,而後把組件的action地址替換一下爲/test/
就能夠了,親測無問題[陰險臉]。 vue.config.js
module.exports = {
baseUrl: baseUrl,
devServer: {
proxy: {
'/api': { // 開發服務器
target: ' http://*******',
changeOrigin: true,
},
'/test': { // 圖片上傳測試
target: ' http://10.70.74.167:3000',
changeOrigin: true,
}
}
},
productionSourceMap: false,
}
複製代碼
富文本編輯器的圖片上傳有兩種模式,一種是把圖片轉成base64,經過一個接口把html內容提交給服務端,另外一種模式是兩個接口,分別把圖片上傳到服務器,而後返回url字符串到編輯器中,再把編輯器中的html保存到服務器上,咱們用的編輯器是vue-quill-editor
,使用第二種模式,藉助element的el-upload組件自動上傳圖片,而後返回地址插入到編輯器。
import {format} from '@/lib/js/utils.js'
import {quillEditor} from 'vue-quill-editor'
const toolbarOptions = [
['bold', 'italic', 'underline', 'strike'], // toggled buttons
[{'header': 1}, {'header': 2}], // custom button values
[{'list': 'ordered'}, {'list': 'bullet'}],
[{'indent': '-1'}, {'indent': '+1'}], // outdent/indent
[{'direction': 'rtl'}], // text direction
[{'size': ['small', false, 'large', 'huge']}], // custom dropdown
[{'header': [1, 2, 3, 4, 5, 6, false]}],
[{'color': []}, {'background': []}], // dropdown with defaults from theme
[{'font': []}],
[{'align': []}],
['link', 'image'],
['clean']
]
export default {
data () {
return {
options2:{},
quillUpdateImg: false, // 根據圖片上傳狀態來肯定是否顯示loading動畫,剛開始是false,不顯示
content:'', // 富文本內容
title:'新建',
editorOption:{
placeholder: '',
theme: 'snow', // or 'bubble'
modules:{
toolbar: {
container: toolbarOptions,
handlers: {
'image': function (value) {
if (value) {
// 觸發input框選擇圖片文件
document.querySelector('.avatar-uploader input').click()
} else {
this.quill.format('image', false);
}
}
}
}
}
},
serverUrl: '/api/add/upload?key='+this.$route.params.key, // 這裏寫你要上傳的圖片服務器地址
header: {
// token: sessionStorage.token
},
current: 0,
formValidate: {
device_name: '集團BI',
versions: '',
publish_time: '',
desc: '',
},
ruleValidate: {
device_name: [
{ required: true, message: '請選擇系統名稱', trigger: 'change' }
],
versions: [
{ required: true, message: '請輸入版本信息', trigger: 'blur' }
],
publish_time: [
{ required: true, type: 'date', message: '請選擇發版時間', trigger: 'change' }
],
desc: [
{ required: true, message: '請輸入對於該版本的整體描述', trigger: 'blur' },
{ type: 'string', min: 20, message: '版本的整體描述很多於20個字', trigger: 'blur' }
]
},
isFirst: true,
isSecond: false,
isThird: false,
versionid:''
}
},
created:function(){
this.limit();
this.initialization();
},
methods: {
limit(){
this.options2 = {
disabledDate (date) {
return (date && date.valueOf() > new Date().getTime()) || (date && date.valueOf() < new Date("2017-12-31"))
}
}
},
//初始斷定是新增/修改
initialization(){
let id = this.$route.params.id;
if(id !=0){
this.title = "編輯";
let obj = {};
obj.version_id = this.$route.params.id;
obj.key = this.$route.params.key;
this.$http
.post('/api/show/version',obj).then(response => (
this.formValidate.device_name = response.data.data.device_name,
this.formValidate.versions = response.data.data.versions,
this.formValidate.publish_time = response.data.data.publish_time,
this.formValidate.desc = response.data.data.desc,
this.content = response.data.data.pc_html
))
}else{
this.title = "新建";
}
},
//第一步基本信息(發佈)
firstSubmit(name){
this.$refs[name].validate((valid) => {
if (valid) {
this.$Message.success('信息添加成功');
this.current += 1;
this.isFirst = !this.isFirst;
this.isSecond = !this.isSecond;
}else{
this.$Message.error('請完善必填信息');
}
})
},
//第二步的表單數據提交(發佈)
save(){
let id = this.$route.params.id;
let addObj = this.formValidate;
addObj.publish_time = format(this.formValidate.publish_time);
addObj.pc_html = this.content;
addObj.key = this.$route.params.key;
if(this.$route.params.id != 0){
addObj.version_id = id;
}
this.$http
.post('/api/add/version',addObj).then(response => (
this.secondSubmit(response.data.version_id)
))
},
//第二步提交成功後轉至第三步(發佈)
secondSubmit(id){
this.current += 1;
this.isSecond = false;
this.isThird = !this.isThird;
this.versionid = id;
},
//第三步跳轉至[預覽]
preview(){
this.$router.push({ path:"/Main/VersionManagementInfo/system_versions/"+this.versionid});
},
//第三步發佈
release(){
let status = this.$route.params.status;
if(status != 2){
let obj = {};
if(this.$route.params.id == 0){
obj.version_id = this.versionid;
}else{
obj.version_id = this.$route.params.id;
}
obj.key = this.$route.params.key;
this.$http
.post('/api/edit/publish/version',obj).then(response => (
this.releaseLink()
))
}else{
this.releaseLink()
}
},
//第三步發佈跳轉
releaseLink(){
this.$router.push({ path:"/Main/VersionManagement/system_versions"});
},
//上一步操做
returns () {
if (this.current != 0) {
this.current -= 1;
this.isFirst = true;
this.isSecond = false;
}
},
//富文本內容改變事件
onEditorChange({editor, html, text}) {
this.content = html
},
//富文本圖片上傳前
beforeUpload() {
// 顯示loading動畫
this.quillUpdateImg = true
},
//富文本圖片上傳成功
uploadSuccess(res, file) {
// res爲圖片服務器返回的數據
// 獲取富文本組件實例
console.log(res,file);
let quill = this.$refs.myQuillEditor.quill
// 若是上傳成功
if (res.code == 1000 ) {
// 獲取光標所在位置
let length = quill.getSelection().index;
// 插入圖片 res.url爲服務器返回的圖片地址
quill.insertEmbed(length, 'image', res.data)
// 調整光標到最後
quill.setSelection(length + 1)
} else {
this.$message.error('圖片插入失敗')
}
// loading動畫消失
this.quillUpdateImg = false
},
// 富文本圖片上傳失敗
uploadError() {
// loading動畫消失
this.quillUpdateImg = false
this.$message.error('圖片插入失敗')
},
}
}
複製代碼
有一部分表格數據比較難處理,是後端直接把xlsx文件轉成字符串發給前端,cheerio能夠把字符串轉爲相似jquery對象的虛擬DOM,而後用jquery的api操做這個虛擬DOM。
import cheerio from "cheerio"
this.$http.post('/api/list/statement-table',p).then(function(response){
if(response.data==""){
This.isShow=false;This.content=true;This.title=false//無數據時數據加載中和標題數據的盒子隱藏
This.message="<div style='text-align:center'>暫無數據</div>"
}else{
//console.log(response)
This.isShow=false;
This.content=true;//有數據時 數據加載中隱藏 標題和表體顯示
let $ = cheerio.load(response.data);
//刪除自帶的行內樣式
$("body style").remove();
$("body table").css({"border":"1px solid #e8eaec" });
$("body table td").css({"border":"1px solid #e8eaec","padding":"10px","color":"#515a6e"});
//全文匹配 剔除&quot;
This.message = $("body").html().replace(/&quot;/g,"");
}
})
複製代碼
產品的需求是從列表頁面點擊查看按鈕進入詳情頁面,詳情頁面再點擊返回,列表頁面要不能刷新,就須要把組件緩存起來。
keep-live
就能夠了,比較麻煩的是咱們在這個組件裏判斷三種狀況,1.第一次進入 2.從其餘欄目進入 3.從詳情頁進入,若是從爲一、2這兩種狀況,咱們須要刷新頁面,若是是3,則不刷新。
思路是: created
鉤子中着增長isFirstEnter
標識,beforeRouteEnter
鉤子中判斷是否爲詳情頁面返回,若是是則加上meta.isBack
的標識,在activated
鉤子裏判斷是第幾種狀況,若是爲1或2,則從新請求列表頁數據,若是是3就不用動管了。
router.js
增長標識meta的keepAlive
和isBack
/******** 業務報表 Start ********/
{
path: '/Main/BusinessReport/:key', // 業務報表-列表
name: 'BusinessReport',
meta: { keepAlive: true,isBack:false},
component: () => import('./pages/BusinessReport/index.vue'),
},
{
path: '/Main/BusinessReportInfo/:sn/:is_check/:key/:type/:cmd5/:time/:is_down', // 業務報表-詳情
name: 'BusinessReportInfo',
component: () => import('./pages/BusinessReportInfo/index.vue'),
},
/******** 業務報表 End ********/
複製代碼
根據mate.keepAlive
渲染不一樣的router-view
(忘記爲何是這麼寫的了,感受很low)。
<keep-alive>
<router-view v-if="$route.meta.keepAlive" ></router-view>
</keep-alive>
<router-view v-if="!$route.meta.keepAlive" >
<!-- 這裏是不被緩存的視圖組件,好比 Edit! -->
</router-view>
複製代碼
組件代碼鉤子事件created
、beforeRouteEnter
、activated
方法
data(){
return{
isFirstEnter:false
}
},
created:function(){
this.isFirstEnter = true;
},
beforeRouteEnter(to, from, next) {
if(from.name === 'BusinessReportInfo') { //判斷是從哪一個路由過來的,如果BusinessReportInfo頁面不須要刷新獲取新數據,直接用以前緩存的數據便可
to.meta.isBack = true
}
next();
},
activated() {
if(!this.$route.meta.isBack || this.isFirstEnter) {
this.data=""
//若是isBack是false,代表須要獲取新數據,不然就再也不請求,直接使用緩存的數據
this.getPath(); // ajax獲取數據方法
}
this.$route.meta.isBack = false;
this.isFirstEnter=false;
//恢復成默認的false,避免isBack一直是true,致使下次沒法獲取數據
}
複製代碼
確實能夠在子組件中修改父組件的數據,但強烈建議不要在子組件中操做父組件數據,期間我接手過一個功能,梳理了半天邏輯,沒找到觸發點在哪裏,原來是在子組件中操做了父組件的數據,不利於維護,我本身起了個名字,讓數據保持單向流動,不知道是否是能夠定義爲單項數據了原則😂。
在開發的過程當中咱們發現,每一個人寫的業務組件代碼風格都不一致,怎樣是一致,關於業務組件,有沒有好的規範或者原則呢?還但願你們給點資料和建議很是感謝。