序言
vue作为后端主流的3大构架之一,目前在国内有着十分广为的应用领域,由于其高性能和自自顶向下的渐进结构设计思想,使其更为重要被应用领域于PC控制系统,对于终端端,图形界面应用软件(electronjs)等也有广为的应用领域,与此问世的优秀的开放源码构架比如说elementUI,iView, ant-design-vue等也很大的降低了开发人员的投资成本,并很大的提高了开发效率。本栏最初碰触vue时也是采用的iview构架,生父感受之后确实十分功能强大且强大。
本栏曾经有两年的vue工程项目作战经验,如前所述vue做过终端端工程项目和PC端ERP控制系统,虽然平常组织工作中采用的是react技术栈,但平常还是会积累很多vue相关的最差实践和做许多如前所述vue的开放源码工程项目,因此说归纳vue的工程项目作战经验我觉得是最好的成长,也期望给今年想碰触vue构架或想从事vue组织工作的朋友带来许多作战经验和思索。
你将斩获
vue构架采用小常识和最差作战经验vue工程项目配置实战策尔纳vue模块结构设计实战策尔纳vue工程项目构架与技术化积极探索节录
责任编辑更为重要是归纳许多vue采用踩过的许多坑和工程项目作战经验,更多的是采用构架(vue/react)过程中的认识论和模块的结构设计路子,最后还会有许多个人对产业化的许多总结,期望有更多作战经验的朋友们能一起交流,积极探索vue的奥秘。
在开始文章之前,本栏建议我们对javascript, css, html此基础有很大的介绍,因为会用构架不很大能较好的同时实现业务市场需求和功能,要想实现相同情景下相同维数的市场需求,很大要对web此基础有充足的介绍,因此期望我们熟悉如下基本知识,如果比较复杂能花时间研究介绍一下。
javascript: 数组常见方法的采用,比如说结点有forEach,map,filter,every, some,reduce,操作方式有splice,slice, join,push,shift, pop,sort等 基本上计算机程序,提及类型(第一类,数组) 基本上形式控制系统if else, switch,长瓣演算:?,for/while循环等 数组常见api(如replace,slice, substr,indexOf) 基本上二阶采用 表达式返回值,返回值链,表达式提高,表达式声明提高 * 第一类基本上用语,面向第一类
css: 基本上盒模型(border/content/padding等) 4种常见定位(static/absolute/relative/fixed) 常见布局方式(自由浮动产业布局/灵活性产业布局flex/自适应产业布局/分层产业布局grid) css3基本上式样与动画电影(transition,animation)
html: 新条码基本上用语和采用 head条码作用与用语(主要是meta特性的用语)
因此期望我们掌握好以上基本知识,也是后端开发的此基础,接下来我们直接进入节录。
1. vue构架采用小常识和最差作战经验
vue学习最快的方式就是实践,根据官网多写几个例子是掌握vue最快的方式。 接下来本栏就来归纳一下在开发vue工程项目中的许多实践作战经验。
1.1 vue生命周期以及相同生命周期下的应用领域
以上是vue官网上的生命周期的方法,大致划分一下分为创建前/后,挂载前/后,更新前/后,销毁前/后这四个阶段。各个阶段的状态归纳如下:
beforeCreate:在beforeCreate生命周期执行时,data和methods中的数据还未初始化,因此此时不能采用data中的数据和methods中的方法
create:data 和 methods初始化完毕,此时能采用methods 中的方法和data 中的数据 beforeMount:template模版已经编译好,但还未挂载到页面,此时页面还是上一个状态 mounted:此时Vue实例初始化完成了,DOM挂载完毕,能直接操作dom或者采用第三发dom库
beforeUpdate: 此时data已更新,但还未同步页面
updated:data和页面都已经更新完成
beforeDestory:Vue实例进入销毁阶段,但所有的 data 和 methods ,指令, 过滤器等都处于可用状态
destroyed: 此时模块已经被销毁,data,methods等都不可用
根据以上介绍,页面第一次加载时会执行 beforeCreate, created, beforeMount, mounted这四个生命周期,因此我们一般在created阶段更多操作,因此说每个生命周期的发展状态十分重要,很大要理解,这样才能对vue有更多的控制权。
1.2 vue常见的指令以及动态指令的采用
指令 (Directives) 是带有 v- 前缀的特殊特性,vue常见的指令有:
v-bind 用于响应式地更新 HTML特性
v-if 根据表达式的值的真假来决定是否插入/移除元素
v-on 用于监听 DOM 事件
v-show 用于决定是否展示该元素,底层通过display:none同时实现
v-html 在dom内插入html内容
v-for 循环
v-text 渲染指定dom的内容文本
v-cloak 和CSS规则如 [v-cloak] { display: none } 一起用,能隐藏未编译的 Mustache 条码直到实例准备完毕
以上是比较常见的指令,具体用语就不一一举例了,其中v-cloak主要是用来避免页面加载时出现闪烁的问题,能结合css的[v-cloak] { display: none }方式解决这一问题。关于指令的动态参数,采用也很简单,虽然是2.6.0 新增的,但是方法很灵活,具体采用如下:
<a v-on:[eventName]=”doSomething“> … </a>
我们能根据具体情况动态切换事件名,从而绑定统一个表达式。
1.3 vue常见修饰符及作用
事件修饰符.stop 阻止事件冒泡.prevent 阻止事件默认行为.self 事件绑定的元素本身触发时才触发回调.once 事件只能触发一次,第二次就不会触发了.native 将一个vue模块变成一个普通的html,使其能监听click等原生事件 具体采用如下:<Tag @click.native=“handleClick”>ok</Tag>
表单修饰符.lazy 在输入框输入完内容,光标离开时才更新视图.trim 过滤首尾空格.number 如果先输入数字,那它就会限制你输入的只能是数字;如果先输入数组,那就相当于没有加.number用语如下:
<input type=“text” v-model.trim=“value”>
还有很多修饰符比如键盘,鼠标等修饰符,感兴趣的我们能自行学习研究。
1.4 模块之间,父子模块之间的通信方案
模块之间的通信方案: 通过事件总线(bus),即通过发布订阅的方式 vuex
父子模块: 父模块通过prop向自模块传递数据 子模块绑定自定义事件,通过this.$emit(event,params) 来调用自定义事件 * 采用vue提供的 $parent / $children & $refs方法来通信
1.5 vue同时实现按需加载模块
模块的按需加载是工程项目性能优化的一个环节,也能降低首屏渲染时间,本栏在工程项目中用到的模块按需加载的方式如下:
采用() => import(), 具体代码如下:<template>
<div>
<ComponentA />
<ComponentB />
</div>
</template>
<script>
const ComponentA = () => import(./ComponentA)
const ComponentB = () => import(./ComponentB)
export default {
// …
components: {
ComponentA,
ComponentB
},
// …
}
</script>
采用resolve => require([./ComponentA], resolve),采用方法如下:<template>
<div>
<ComponentA />
</div>
</template>
<script>
const ComponentA = resolve => require([./ComponentA], resolve)
export default {
// …
components: {
ComponentA
},
// …
}
</script>
1.6 vuex的几种特性和作用,以及采用vuex的基本上模式
Vuex 是一个专为 Vue.js 应用领域程序开发的状态管理模式。它采用集中式存储管理应用领域的所有模块的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
vuex的基本上组织工作模式如下图所示:
state的改变完全由mutations控制, 我们也没必要任何工程项目都采用vuex,对于中大型复杂工程项目而言,需要共享的状态很多时,采用vuex才是最差的选择。接下来我将详细介绍各api的概念和作用。 state 单一状态树,用一个第一类就包含了全部的应用领域层级状态,并且作为一个唯一数据源而存在 getters 就像计算特性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算 比如说如下案例:
const store = new Vuex.Store({
state: {
todos: [
{ id: 1, text: …, done: true },
{ id: 2, text: …, done: false }
]
},
getters: {
doneTodos: state => {
return state.todos.filter(todo => todo.done)
}
}
})
// 访问getters里的特性
this.$store.getters.doneTodos
Mutation 更改 Vuex 的 store 中的状态的唯一方法,采用案例如下:const store = new Vuex.Store({
state: {
num: 1
},
mutations: {
add (state) {
// 变更状态
state.num++
}
}
})
// 在工程项目中采用mutation
store.commit(add)
// 添加额外参数
store.commit(add, 10)
Action Action提交的是mutation,而不是直接变更状态,能包含任意异步操作,具体用语如下:const store = new Vuex.Store({
state: {
num: 0
},
mutations: {
add (state) {
state.num++
}
},
actions: {
add (context) {
context.commit(add)
},
asyncAdd ({ commit }) {
setTimeout(() => {
commit(add)
}
}
})
// 分发actionstore.dispatch(add)
// 异步action
store.dispatch(asyncAdd)
// 异步传参
store.dispatch(asyncAdd, { num: 10 })
Module 将store分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块本栏更具实际实战策尔纳了一套标准采用模式,就拿本栏之前的开放源码XPXMS举例,如下:
store目录是用来组织vuex代码用的,我将action,mutation,state分文件管理,这样工程项目大了之后也很容易管理和查询。接下来看看此文件是如何组织的:
// type.ts// 用来定义state等的类型文件
export interface State {
name: string;
isLogin: boolean;
config: Config;
[propName: string]: any; // 用来定义可选的额外特性
}
export interface Config {
header: HeaderType,
banner: Banner,
bannerSider: BannerSider,
supportPay: SupportPay
}
export interface Response {
[propName: string]: any;
}
// state.ts
// 定义全局状态
import { State } from ./type
export const state: State = {
name: ,
isLogin: false,
curScreen: 0, // 0为pc, 1为终端
config: {
header: {
columns: [首页, 产品, 技术, 运营, 商业],
height: 50,
backgroundColor: #000000,
logo:
}
},
// …
articleDetail: null
};
// mutation.tsimport {
State,
Config,
HeaderType,
Banner,
BannerSider,
SupportPay
} from ./type
export default {
// 预览模式
setScreen(state: State, payload: string) {
state.curScreen = payload;
},
// 删除banner图
delBanner(state: State, payload: number) {
state.config.banner.bannerList.splice(payload, 1);
},
// 添加banner图
addBanner(state: State, payload: object) {
state.config.banner.bannerList.push(payload);
},
// …
};
// action.ts
import {
HeaderType,
Response
} from ./type
import http from ../utils/http
import { uuid, formatTime } from ../utils/common
import { message } from ant-design-vue
export default {
/**配置 */
setConfig(context: any, paylod: HeaderType) {
http.get(/config/all).then((res:Response) => {
context.commit(setConfig, res.data)
}).catch((err:any) => {
message.error(err.data)
})
},
/**header */
saveHeader(context: any, paylod: HeaderType) {
http.post(/config/setHeader, paylod).then((res:Response) => {
message.success(res.data)
context.commit(saveHeader, paylod)
}).catch((err:any) => {
message.error(err.data)
})
},
// …
};
// index.ts
import Vue from vue;
import Vuex from vuex;
import { state } from ./state;
import mutations from ./mutation;
import actions from ./action;
Vue.use(Vuex);
export default new Vuex.Store({
state,
mutations,
actions
});
// main.ts
// 最后挂载到入口文件的vue实例上
import Vue from vue;
import App from ./App.vue;
import router from ./router;
import store from ./store/;
import ./component-class-hooks;
import ./registerServiceWorker;
Vue.config.productionTip = false;
new Vue({
router,
store,
render: (h) => h(App),
}).$mount(#app);
我们在实际工程项目中都能采用这种方式组织管理vuex相关的代码。
1.7 vue-router基本上采用模式和导航钩子的用语及作用
vue-router采用我们想必不是很陌生,这里直接写一个案例:
// router.tsimport Vue from vue;
import Router from vue-router;
import Home from ./views/admin/Home.vue;
Vue.use(Router);
const router = new Router({
mode: history,
base: process.env.BASE_URL,
routes: [
{
path: /,
component: Home,
beforeEnter: (to, from, next) => {
next();
},
children: [
{
// 当 /user/:id/profile 匹配成功,
// UserProfile 会被渲染在 User 的 <router-view> 中 path: ,
name: header,
component: () => import(/* webpackChunkName: “header” */ ./views/admin/subpage/Header.vue),
},
{
path: /banner,
name: banner,
component: () => import(/* webpackChunkName: “banner” */ ./views/admin/subpage/Banner.vue),
},
{
path: /admin,
name: admin,
component: () => import(/* webpackChunkName: “admin” */ ./views/admin/Admin.vue),
},
],
},
{
path: /login,
name: login,
component: () => import(/* webpackChunkName: “login” */ ./views/Login.vue),
meta:{
keepAlive:false //不需要被缓存的模块
}
},
{
path: *,
name: 404,
component: () => import(/* webpackChunkName: “404” */ ./views/404.vue),
},
],
});
// 路由导航钩子的用语
router.beforeEach((to, from, next) => {
if(from.path.indexOf(/preview) < 0) {
sessionStorage.setItem(prevToPreviewPath, from.path);
}
next();
})
export default router
以上案例是很典型的静态路由配置和导航钩子的用语(如何加载路由模块,动态加载路由模块,404页面路由配置,路由导航钩子采用)。如果在做后台控制系统,往往会涉及到权限控制系统,因此一般会采用动态配置路由,通过前后端约定的路由方式,路由配置文件更具相同用户的权限由后端处理后返。由于结构设计细节比较繁琐,涉及到前后端协定,因此这里只讲路子就好了。
1.8 vue中检测变化的小常识
受现代 JavaScript 的限制,Vue 无法检测到第一类特性的添加或删除。由于 Vue 会在初始化实例时对特性执行 getter/setter 转化,因此特性必须在 data 第一类上存在才能让 Vue 将它转换为响应式的。还有一种情况是,vue无法检测到data特性值为数组或第一类的修改,因此我们需要用原第一类与要混合进去的第一类的特性一起创建一个新的第一类。能采用this.$set或者对象的深拷贝,如果是数组则能采用splice,扩展演算符等方法来更新。
1.9 对指定页面采用keep-alive路由缓存
keep-alive是Vue的内置模块,能在模块切换过程中将状态保留在内存中,防止重复渲染DOM。我们能采用以下方式设置某些页面是否被缓存: 1. 通过路由配置文件和router-view设置:
// routes 配置
export default [
{
path: /A,
name: A,
component: A,
meta: {
keepAlive: true // 需要被缓存
}
}, {
path: /B,
name: B,
component: B,
meta: {
keepAlive: false // 不需要被缓存 }
}
]
路由视图配置:
// 路由设置
<keep-alive>
<router-view v-if=“$route.meta.keepAlive”>
<!– 会被缓存的视图模块–>
</router-view>
</keep-alive>
<router-view v-if=“!$route.meta.keepAlive”>
<!– 不需要缓存的视图模块–>
</router-view>
通过router-view的key特性 具体方式如下:<template>
<div id=“app”>
<keep–alive>
<router–view :key=“key” />
</keep-alive>
</div>
</template>
<script lang=“ts”>
import { Vue } from vue-property-decorator;
import Component from vue-class-component;
@Component
export default class App extends Vue {
get key() {
// 缓存除预览和登陆页面之外的其他页面
console.log(this.$route.path)
if(this.$route.path.indexOf(/preview) > –1) {
return 0
}else if(this.$route.path === /login) {
return 1
}else {
return 2
}
}
}
</script>
1.10 vue常见工具表达式归纳
归纳一下本栏在vue工程项目中的常见的工具表达式。 * 识别ie浏览器
/**
* 识别ie–浅识别
*/
export const isIe = () => {
let explorer = window.navigator.userAgent;
//判断是否为IE浏览器
if (explorer.indexOf(“MSIE”) >= 0) {
return true;
}else {
return false
}
}
颜色16进制转rgba/**
* 颜色转换16进制转rgba
* @param {String} hex
* @param {Number} opacity
*/
export function hex2Rgba(hex, opacity) {
if(!hex) hex = “#2c4dae”;
return “rgba(“ + parseInt(“0x” + hex.slice(1, 3)) + “,” + parseInt(“0x” + hex.slice(3, 5)) + “,” + parseInt(“0x” + hex.slice(5, 7)) + “,” + (opacity || “1”) + “)”;
}
去除html条码// 去除html条码export const htmlSafeStr = (str) => {
return str.replace(/<[^>]+>/g, “”)
}
export const getQueryString = () => {
let qs = location.href.split(?)[1] || ,
args = {},
items = qs.length ? qs.split(“&”) : [];
items.forEach((item,i) => {
let arr = item.split(=),
name = decodeURIComponent(arr[0]),
value = decodeURIComponent(arr[1]);
name.length && (args[name] = value)
})
return args;
}
解析url参数/* 解析url参数 */
export const paramsToStringify = (params) => {
if(params){
let query = [];
for(let key in params){
query.push(`${key}=${params[key]}`)
}
return `${query.join(&)}`
}else{
return
}
}
将数据转化为数组export const toArray = (data) => {
return Array.isArray(data) ? data : [data]
}
带参数跳转url(hash模式)/**
* 带参数跳转url(hash模式)
* @param {String} url
* @param {Object} params
*/
export const toPage = (url, params) => {
if(params){
let query = [];
for(let key in params){
query.push(`${key}=${params[key]}`)
}
window.location.href = `./index.html#/${url}?${query.join(&)}`;
}else{
window.location.href = `./index.html#/${url}`;
}
}
控制数组显示,超出指定字数则显示省略号/**
* 指定数组 溢出显示省略号
* @param {String} str
* @param {Number} num
*/
export const getSubStringSum = (str = “”, num = 1) => {
let newStr;
if(str){
str = str + ;
if (str.trim().length > num ) {
newStr = str.trim().substring(0, num) + “…”;
} else {
newStr = str.trim();
}
}else{
newStr =
}
return newStr;
}
生成uuid/**
* 生成uuid * @param {number} len 生成指定长度的uuid
* @param {number} radix uuid进制数
*/
export function uuid(len, radix) {
let chars = 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.split();
let uuid = [], i;
radix = radix || chars.length;
if (len) {
for (i = 0; i < len; i++) uuid[i] = chars[0 | Math.random()*radix];
} else {
let r;
uuid[8] = uuid[13] = uuid[18] = uuid[23] = –;
uuid[14] = 4;
for (i = 0; i < 36; i++) {
if (!uuid[i]) {
r = 0 | Math.random()*16;
uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r];
}
}
}
return uuid.join();
}
生成指定格式的时间数组/**
* 生成指定格式的时间
* @param {*} timeStemp 时间戳 * @param {*} flag 格式符号
*/
export function formatTime(timeStemp, flag) {
let time = new Date(timeStemp);
let timeArr = [time.getFullYear(), time.getMonth() + 1, time.getDate()];
return timeArr.join(flag || /)
}
1.11 如何如前所述axios二次封装一个具有请求/响应拦截的http请求
这个主要是对axios的理解,我们能学习axios官方文档,这里给出一个二次封装的模版:
import axios from axios
import qs from qs
// 请求拦截
axios.interceptors.request.use(config => {
// 此处能封装许多加载状态
return config
}, error => {
return Promise.reject(error)
})
// 响应拦截
axios.interceptors.response.use(response => {
return response
}, error => {
return Promise.resolve(error.response)
})
function checkStatus (response) {
// 此处能封装许多加载状态
// 如果http状态码正常,则直接返回数据
if(response) {
if (response.status === 200 || response.status === 304) {
return response.data
// 如果不需要除了data之外的数据,能直接 return response.data } else if (response.status === 401) {
location.href = /login;
} else {
throw response.data
}
} else {
throw {data:网络错误}
}
}
// axios默认参数配置
axios.defaults.baseURL = /api/v0;
axios.defaults.timeout = 10000;
// restful API封装export default {
post (url, data) {
return axios({
method: post,
url,
data: qs.stringify(data),
headers: {
X-Requested-With: XMLHttpRequest,
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
}
}).then(
(res) => {
return checkStatus(res)
}
)
},
get (url, params) {
return axios({
method: get,
url,
params, // get 请求时带的参数 headers: {
X-Requested-With: XMLHttpRequest
}
}).then(
(res) => {
return checkStatus(res)
}
)
},
del (url, params) {
return axios({
method: delete,
url,
params, // get 请求时带的参数
headers: {
X-Requested-With: XMLHttpRequest
}
}).then(
(res) => {
return checkStatus(res)
}
)
}
}
该模版只是一个大致构架,我们能细化成业务市场需求的样子,该案例提供了restful接口方法,比如说get/post/delete/put等。
1.12 vue常见社区模块,插件
本栏在做vue工程项目时为了提高开发效率也会直接用第三方插件,下面整理一下常见的vue社区模块和库。 1. UI构架
elementUIiviewMint UI 如前所述 Vue.js 的终端端模块库Vant 有赞团队的终端端模块库
社区模块
Vuetable-2 如前所述vue的强大的表格模块vue-fa 如前所述vue的图标模块库vue-notification vue优美的信息通知模块vue-progress-path vue个性的路径进度条模块Vue树模块,可让您以美观和逻辑的方式呈现层次结构的数据vue-social-sharing vue社区分享模块vue-qvue-clipboard2 如前所述vue的剪切板模块cool-emoji-picker vue表情包模块Vue-tabs-component 强大而美观的tab模块
更多模块能在vue插件社区查看。
2. vue工程项目配置实战策尔纳
在讲完vue工程项目作战经验之后,为了让我们能独立负责一个工程项目,我们还需要知道从0开始搭建工程项目的步骤,以及通过工程项目实际情况,自己配置一个符合的工程项目构架,比如说有些公司会采用vue+element+vue+less搭建,有些公司采用vue+iview+vue+sass,或者其他更多的技术栈,因此我们要有把控能力,我们需要熟悉webpack或者vue-cli3脚手架的配置,本栏之前有些过详细的webpack和vue-cli3搭建自定义工程项目的文章,这里由于篇幅有限就不一一举例了。感兴趣的朋友能参考以下两篇文章:一张图教你快速玩转vue-cli3 用 webpack 4.0 撸单页/多页脚手架 (jquery, react, vue, typescript)
3. vue模块结构设计实战策尔纳
模块控制系统是 Vue 的另一个重要概念,因为它是一种抽象,允许我们采用小型、独立和通常可复用的模块构建大型应用领域。几乎任意类型的应用领域界面都能抽象为一个模块树。在一个大型应用领域中,有必要将整个应用领域程序划分为模块,以使开发更易管理。
对于一个此基础模块来说,我们该如何下手去结构设计呢?首先本栏觉得应该先从市场需求和功能入手,先划分好模块的功能边界,模块能做什么,理清这些之后再开始写模块。
如上图模块的一个抽象,我们无论如何记住的第一步就是先理清市场需求在去着手开发,这样能避免大量效率的丢失。在上图中我们要注意模块的解耦合,每个模块都负责特定的功能或展现,这样对模块后期维护性和扩展性有十分大的帮助。本栏归纳了一下模块结构设计的小常识: 确定模块结构设计的边界和功能定位 模块尽量遵循单一职责原理,用组合代替功能的杂糅 模块特性暴露要适度,不可过渡暴露特性 模块封装要考虑可重用,可组合,可配置 * 做好模块类型结构设计的划分(展示型模块,录入型模块,此基础模块, 产业布局模块,反馈型模块,业务模块等)
本栏拿之前在开放源码社区发布的一个文件上传模块为例子来说明举例,代码如下:
<template>
<div>
<a-upload
:action=“action”
listType=“picture-card”
:fileList=“fileList”
@preview=“handlePreview”
@change=“handleChange”
:remove=“delFile”
:data=“data”
>
<template v-if=“!fileList.length && defaultValue”>
<img :src=“defaultValue” alt=“” style=“width: 100%”>
</template>
<template v-else>
<div v-if=“fileList.length < 2”>
<a-icon type=“plus” />
<div class=“ant-upload-text”>上传</div>
</div>
</template>
</a-upload>
<a-modal :visible=“previewVisible” :footer=“null” @cancel=“handleCancel”>
<img alt=“example” style=“width: 100%” :src=“previewImage” />
</a-modal>
</div>
</template>
<script lang=“ts”>
import { Component, Vue, Prop } from vue-property-decorator;
@Component
export default class Upload extends Vue {
@Prop({ default: https://www.mocky.io/v2/5cc8019d300000980a055e76 })
action!: string;
@Prop()
defaultValue: string;
@Prop()
data: object;
@Prop({ default: function() {} })
onFileDel: any;
@Prop({ default: function() {} })
onFileChange: any;
public previewVisible: boolean = false;
public previewImage: string = ;
public fileList: object[] = [];
// 预览图片
public handlePreview(file: any) {
this.previewImage = file.url || file.thumbUrl;
this.previewVisible = true;
}
// 删除文件和回调
public delFile(file: any) {
this.fileList = [];
this.onFileDel();
}
// 文件上传变化的处理表达式
public handleChange({ file }: any) {
this.fileList = [file];
if(file.status === done) {
this.onFileChange(file.response.url);
} else if(file.status === error) {
this.$message.error(file.response.msg)
}
}
// 取消预览 public handleCancel() {
this.previewVisible = false;
}
}
</script>
以上文件上传预览采用的是ts来同时实现,但结构设计路子都是一致的,我们能参考交流一下。 关于如何结构设计一个健壮的模块,本栏也写过相关文章,大致思想都好似一样的,能参考一下:
《精通react/vue模块结构设计》之快速同时实现一个可定制的进度条模块《精通react/vue模块结构设计》之用纯css打造类materialUI的按钮点击动画电影并封装成react模块3分钟教你用原生js同时实现具有进度监听的文件上传预览模块
模块的结构设计思想和方法与具体构架无关,因此模块结构设计的核心是认识论,我们只有在工程项目中不断总结和抽象,才能对模块结构设计有更深刻的理解。
4. vue工程项目构架与技术化积极探索
这里是本栏归纳的一套思维导图:
有点微后端构架的感觉,但是还有很多细节需要考虑。此构架适用于相同子控制系统独立部署和开发的环境, 也不需要考虑具体的技术栈选择,相同的构架采用同一套自建模块库来达到模块的复用,这里提倡工程项目开始结构设计时就考虑模块化和模块化,做出功能的最大的拆分和去耦合。本栏后面会单独花时间归纳微后端构架具体的许多结构设计路子和落地方案,感兴趣的能一起探讨交流。
最后
如果想学习更多H5游戏, webpack,node,gulp,css3,javascript,nodeJS,canvas数据可视化等后端知识和实战,欢迎在《趣谈后端》专栏学习讨论,共同积极探索后端边界。
更多推荐