这是一篇求职文章 年龄21 目前级别p6+ 坐标成都 找一份vue.js移动端H5工作
一份没有任何包装纯真实的简历 简历戳这
求职文章一共有两篇 另外一篇请点这一个nuxt(vue)+mongoose全栈项目聊聊我粗浅的项目架构
项目简介
为什么有这个项目?
之前重构完公司的项目后将项目的组件进行抽离然后构成了这个项目,UI库基于项目之后维护也比较方便
项目地址
学生机服务器ui.qymh.org.cn,阿里云当时提供了一个0.9元的cdn,服务器虽然差了点但我挂了cdn,访问应该不会卡
注意在pc端下查看,请按f12调至移动端视角
同样要注意的是在掘金app中打开这个项目,点我项目中的返回箭头是无效的.我也不知道为什么,需要点掘金app提供的箭头返回路由
github
项目github地址QymhUI
项目截图
项目目录
项目目录仿element-ui
,先来看张图片
目录分析
component
打包后的组件jsdist
列子打包后的文件docs
挂载的静态github page
examples
列子目录packages
组件目录src
资源目录typings
构建的命名空间webpack
webpack目录
组件目录
构造了这么多组件,这个地方的目录是仿的element-ui的架构目录
项目架构
webpack配置
webpack
这里是一个大的知识点,叙述起来太麻烦了,这里提一下这个项目的webpack
和其他有什么不同
- 1
webpack
打包typescript
我引入了ForkTsCheckerWebpackPlugin
,感觉最大的影响就是打包速度快了,而且这个插件高度适配vue
,还提供了tslint
,虽然我在这个项目没引用,之后会提到 - 2 我项目中有一个
qymhui.config.js
,这个文件是UI的配置项,是暴漏给开发者的,就类似于.babelrc
postcss.config.js
一样,我在webpack
中读取他,然后通过webpack.definePlugin
写入process.env
,这个位置有一个大坑 1.暴漏给开发者的js只能用commonjs
语法 2.我暴漏的js里面开发者是可以写入函数的,然而JSON.stringify
是直接忽略函数,之后我通过了对象深度拷贝解决了这个问题
架构分析
- 1 第一步 在
packages
中创建组件目录,下面的步骤会以q-radio
这个按钮组件进行举列,我们来看看他的目录结构
模版引擎我用的pug
,vue
中写typescript
我使用了vue-property-decorator
,预处理器用的scss
packages/radio/index.ts
import Radio from './src/main.vue'
export default Radio
复制代码
packages/radio/src/main.vue
.q-radio(:style="computedOuterStyle")
//- 方形选择器
.q-radio-rect(
v-if="type==='rect'"
@click="change(!active)"
:style="computedStyle")
span(v-show="active")
i.q-icon.icon-check(:style="{color:active?activeColor:''}")
//- 圆形选择器
.q-radio-circle(
v-if="type==='circle'"
@click="change(!active)"
:style="computedStyle")
span.q-radio-circle-value(
v-show="active")
i.q-icon.icon-check(:style="{color:active?activeColor:''}")
复制代码
- 2 第二步引用并暴漏
我在src/index.ts
中引入这个组件,并暴漏注册组件的方法,这个位置的写法也仿的element-ui
不过这个地方有一个坑,element-ui
注册组件直接用的component.name
就可以拿到组件的名字,但ts打包组件的名字会被压缩,不知道这算不算一个Bug,所以我们得单独把每个组件的名字用数组保存,我们来看看代码
import './fonts/iconfont.css'
import './style/highLight.scss'
import './style/widget.scss'
import './style/animate.scss'
import './style/mescroll.scss'
import 'swiper/dist/css/swiper.min.css'
import 'mobile-select/mobile-select.css'
import Vue from 'vue'
import lazyLoad from 'vue-lazyload'
import CONFIG from './qymhui.config'
Vue.use(lazyLoad, CONFIG.qimage)
import '../packages/widget'
import QRow from '../packages/row'
import QCol from '../packages/col'
import QText from '../packages/text'
import QCell from '../packages/cell'
import QHeadBar from '../packages/headBar'
import QSearchBar from '../packages/searchBar'
import QTabBar from '../packages/tabBar'
import QTag from '../packages/tag'
import QCode from '../packages/code'
import QForm from '../packages/form'
import QInput from '../packages/input'
import QRadio from '../packages/radio'
import QStepper from '../packages/stepper'
import QTable from '../packages/table'
import QOverlay from '../packages/overlay'
import QFiles from '../packages/files'
import QImage from '../packages/image'
import QSwiper from '../packages/swiper'
import QPhoto from '../packages/photo'
import QSelect from '../packages/select'
import QScroll from '../packages/scroll'
const components = [
QRow,
QCol,
QText,
QCell,
QHeadBar,
QSearchBar,
QTabBar,
QTag,
QCode,
QForm,
QInput,
QRadio,
QStepper,
QTable,
QOverlay,
QFiles,
QImage,
QSwiper,
QPhoto,
QSelect,
QScroll
]
const componentsName: string[] = [
'QRow',
'QCol',
'QText',
'QCell',
'QHeadBar',
'QSearchBar',
'QTabBar',
'QTag',
'QCode',
'QForm',
'QInput',
'QRadio',
'QStepper',
'QTable',
'QOverlay',
'QFiles',
'QImage',
'QSwiper',
'QPhoto',
'QSelect',
'QScroll'
]
const install = function(Vue: any, opts: any) {
components.map((component: any, i) => {
Vue.component(componentsName[i], component)
})
}
export default {
install,
QRow,
QCol,
QText,
QCell,
QHeadBar,
QSearchBar,
QTabBar,
QTag,
QCode,
QForm,
QInput,
QRadio,
QStepper,
QTable,
QOverlay,
QFiles,
QImage,
QSwiper,
QPhoto,
QSelect,
QScroll
}
复制代码
- 3 直接在引用其中的install,然后通过Vue.use 注册插件就可以使用了
项目特点
快速开发
思路
与其他UI框架的不同在于,我们在组件的布局上进行了创新
平常我们在项目时,会写html
,再写css
,html
中存在大量复杂的命名,如果采用BEM命名准则
,比如 .a_b_c
.a-b_c
通过下划线链接命名,刚才的列子还只是测试,在真实的开发环境下长度是可怕的,所以我们在布局layout组件中,直接省去了元素命名,并将css
书写成本降到最低
架构
这个地方是用typesrcipt
的继承实现的
首先构造属性vue
和ts
,下面的列子举了一个q-row
的列子,我把常用的css样式直接放在了q-row
组建的prop
中
packages/proto/row/main.vue
复制代码
packages/proto/row/index.ts
// 构造全局样式
export default function createStyle(vm: any) {
const style: any = {
// 可选属性为auto
// 高
height:
vm.h === -1 && vm.row === -1
? 'auto'
: vm.h !== -1
? `${vm.h / 10}rem`
: `${vm.row}%`,
// 行高
lineHeight: vm.lh === -1 ? 'auto' : `${vm.lh / 10}rem`,
// 宽
width:
vm.w === -1 && vm.col === -1
? 'normal'
: vm.w !== -1
? `${vm.w / 10}rem`
: `${vm.col}%`,
// 定位
position: vm.position,
// top
top:
vm.t === -1
? 'auto'
: typeof vm.t === 'number'
? `${vm.t / 10}rem`
: `${vm.t}%`,
// right
right:
vm.r === -1
? 'auto'
: typeof vm.r === 'number'
? `${vm.r / 10}rem`
: `${vm.r}%`,
// bottom
bottom:
vm.b === -1
? 'auto'
: typeof vm.b === 'number'
? `${vm.b / 10}rem`
: `${vm.b}%`,
// left
left:
vm.l === -1
? 'auto'
: typeof vm.l === 'number'
? `${vm.l / 10}rem`
: `${vm.l}%`,
// 字体
fontSize: vm.fontSize === -1 ? 'inherit' : `${vm.fontSize}px`,
// 可选属性为空
// margin-top
marginTop: vm.mt === 0 ? '' : `${vm.mt / 10}rem`,
// margin-right
marginRight: vm.mr === 0 ? '' : `${vm.mr / 10}rem`,
// margin-bottom
marginBottom: vm.mb === 0 ? '' : `${vm.mb / 10}rem`,
// margin-left
marginLeft: vm.ml === 0 ? '' : `${vm.ml / 10}rem`,
// padding-top
paddingTop: vm.pt === 0 ? '' : `${vm.pt / 10}rem`,
// padding-right
paddingRight: vm.pr === 0 ? '' : `${vm.pr / 10}rem`,
// padding-bottom
paddingBottom: vm.pb === 0 ? '' : `${vm.pb / 10}rem`,
// padding-left
paddingLeft: vm.pl === 0 ? '' : `${vm.pl / 10}rem`,
// border-radius
borderRadius:
vm.radius === -1
? ''
: typeof vm.radius === 'number'
? `${vm.radius / 10}rem`
: `${vm.radius}%`,
// color
color: vm.color,
// 背景颜色
backgroundColor: vm.bkColor,
// text-align
textAlign: vm.textAlign,
// z-index
zIndex: vm.zIndex,
// display
display: vm.display,
// vertical-align
verticalAlign: vm.vertical,
// overflow
overflow: vm.overflow,
// word-break
wordBreak: vm.wordBreak,
// text-indent
textIndent: vm.indent === -1 ? '' : `${vm.indent / 10}rem`,
// text-decoration
textDecoration: vm.decoration === 'none' ? '' : vm.decoration,
// border
border: vm.border || '',
// border-top
borderTop: vm.borderTop || '',
// border-right
borderRight: vm.borderRight || '',
// border-bottom
borderBottom: vm.borderBottom || '',
// border-left
borderLeft: vm.borderLeft || ''
}
for (const i in style) {
if (style.hasOwnProperty(i)) {
const item: string = style[i]
if (
item === '' ||
(item === 'auto' && i !== 'overflow') ||
item === 'inherit' ||
item === 'static' ||
item === 'normal' ||
item === 'baseline' ||
item === 'visible' ||
(item === 'none' && i === 'textDecoration')
) {
delete style[i]
}
// 更符合移动端overflow auto的标准
if (i === 'overflow' && (item === 'auto' || item === 'scroll')) {
style['-webkit-overflow-scrolling'] = 'touch'
}
}
}
return style
}
复制代码
可扩展
思路
与其他UI框架不同,我们提供了config
去改变默认的UI布局.你的项目的组件大小可能和UI库提供的不一样,没关系,我们内置了基础的UI布局,但你可以通过 qymhui.config.js
去修改我们的默认配置,打造一个属于自己项目的UI库
架构
我们提供了一个默认配置,然后暴漏给用户一个配置,用户的配置是通过webpack
在node
环境读取的,最后合并两个配置并传向组件,下面就是qymhui.config.js
的默认配置
// q-cell
export const qcell = {
bkColor: '',
hasPadding: true,
borderTop: false,
borderBottom: false,
borderColor: '#d6d7dc',
leftIcon: '',
leftIconColor: '',
leftText: '',
leftTextColor: '#333',
leftWidth: '',
title: '',
titleColor: '',
rightText: '',
rightTextColor: '',
rightArrow: false,
rightArrowColor: '#a1a1a1',
baseHeight: 1.2
}
// q-head-bar
export const qheadbar = {
color: '',
bkColor: '',
bothWidth: 1,
hasPadding: true,
padding: 0.2,
borderTop: false,
borderBottom: false,
borderColor: '#d6d7dc',
leftEmpty: false,
leftArrow: false,
centerEmpty: false,
centerText: '',
centerTextColor: '',
rightEmpty: false,
rightArrow: false,
rightText: '',
rightTextColor: '',
baseHeight: 1.2
}
// q-search-bar
export const qsearchbar = {
color: '',
bkColor: '',
hasPadding: true,
padding: 0.2,
bothWidth: 1,
borderTop: false,
borderBottom: false,
borderColor: '#d6d7dc',
value: '',
leftArrow: false,
leftText: '',
leftTextColor: '',
searchBkColor: 'white',
placeholder: '请输入...',
clearable: false,
rightText: '搜索',
rightTextColor: '',
baseHeight: 1.2
}
// q-tabbar
export const qtabbar = {
bkColor: '',
borderTop: '',
borderBottom: '',
borderColor: '#d6d7dc',
baseHeight: 1.2
}
// q-text
export const qtext = {
lines: 0
}
// q-tag
export const qtag = {
bkColor: '#d6d7dc',
color: 'white',
fontSize: 12,
value: '',
hasBorder: false,
hasRadius: true,
borderColor: '#d6d7dc',
active: false,
activeBkColor: '',
activeColor: 'white'
}
// q-input
export const qinput = {
hasBorder: false,
borderBottom: true,
borderColor: '#d6d7dc',
bkColor: '',
color: '',
type: 'text',
fix: 4,
placeholder: ''
}
// q-radio
export const qradio = {
type: 'rect',
hasBorder: true,
borderColor: '#a1a1a1',
activeColor: '',
activeBkColor: '',
activeBorderColor: 'transparent'
}
// q-stepper
export const qstepper = {
color: '#F65A44',
min: 0,
max: '',
fix: 4
}
// q-overlay
export const qoverlay = {
position: '',
opacity: 0.3,
bkColor: 'white',
minHeight: 10,
maxHeight: 13,
show: false
}
// q-files
export const qfiles = {
multiple: true,
maxCount: 3,
maxSize: 4,
value: '点击上传',
hasBorder: true,
borderColor: '#a1a1a1'
}
// q-image
export const qimage = {
preLoad: 1.3,
loading: '',
attemp: 1,
bkSize: 'contain',
bkRepeat: 'no-repeat',
bkPosition: '50%'
}
// q-scroll
export const qscroll = {
// 下拉刷新
down: (vm) => {
return {
// 是否启用
use: true,
// 是否初次调用
auto: false,
// 回调
callback(mescroll) {
vm.$emit('refresh')
}
}
},
// 上拉加载
up: (vm) => {
return {
// 是否启用
use: true,
// 是否初次调用
auto: true,
// 是否启用滚动条
scrollbar: {
use: true
},
// 回调
callback: (page, mescroll) => {
vm.$emit('load', page)
},
// 无数据时的提示
htmlNodata: '-- 没有更多的数据 --
'
}
}
}
// $notice
export const $notice = {
// 提醒
toast: {
position: 'bottom',
timeout: 1500
},
// 弹窗
confirm: {
text: '请输入文字',
btnLeft: '确定',
btnRight: '取消'
}
}
// $cookie
export const $cookie = {
// 过期时间
enpireDays: 7
}
// $axios
export const $axios = {
// 是否输入日志
log: true,
// 超时
timeout: 20000,
// 请求拦截器
requestFn: (config) => {
return config
},
// 响应拦截器
responseFn: (response) => {
return response
}
}
复制代码
不止UI组件
Widget
我们在项目中提供了除了UI组件的widget常用方法并将他们直接挂载在vue
的原型上,你可以在vue
环境中直接引用
比如$cookie
设置 cookie$storage
设置 storage$toast
提醒插件$axios
ajax封装
下面贴一下$cookie
的封装
packages/widget/cookie/index.ts
import Vue from 'vue'
const Cookie = Object.create(null)
const config = require('../../../src/qymhui.config').default.$notice
Cookie.install = (Vue: any) => {
Vue.prototype.$cookie = {
/**
* 获取cookie
* @param key 键
*/
get(key: string): string | number {
let bool = document.cookie.indexOf(key) > -1
if (bool) {
let start: number = document.cookie.indexOf(key) + key.length + 1
let end: number = document.cookie.indexOf(';', start)
if (end === -1) {
end = document.cookie.length
}
let value: any = document.cookie.slice(start, end)
return escape(value)
}
return ''
},
/**
* 设置cookie
* @param key 键
* @param value 值
* @param expireDays 保留日期
*/
set(key: string, value: any, expireDays: number = config.enpireDays) {
let now = new Date()
now.setDate(now.getDate() + expireDays)
document.cookie = `${key}=${escape(value)};expires=${now.toUTCString}`
},
/**
* 删除Cookie
* @param key 键
*/
delete(key: string | string[]) {
let now = new Date()
now.setDate(now.getDate() - 1)
if (Array.isArray(key)) {
for (let i in key) {
let item: string = key[i]
let value: any = this.get(item)
document.cookie = `${item}=${escape(
value
)};expires=${now.toUTCString()}`
}
} else {
let value = this.get(key)
document.cookie = `${key}=${escape(value)};expires=${now.toUTCString()}`
}
},
/**
* 直接删除所有cookie
*/
deleteAll() {
let cookie = document.cookie
let arr = cookie.split(';')
let later = ''
let now = new Date()
now.setDate(now.getDate() - 1)
for (let i in arr) {
let item = arr[i]
later = item + `;expires=${now.toUTCString()}`
document.cookie = later
}
}
}
}
Vue.use(Cookie)
复制代码
我们将要做的
-
移动端适配,目前仅支持
flexible.js
的rem
布局,这是有问题的,flexible.js
官方也提到了,之后会通过vh
重写布局 -
UI模块需要增加,目前的UI框架是从我们的项目中抽离出来的常用的模块,但不代表是大家常用的,模块量过少
-
文档现在只有移动端版,将来会支持到PC端版本
结语
其实项目想在年末的时候开源,我多做一些功能,多做一点测试,多完善文档,多修改接口保证更友好更简单.但没办法,要找工作了,项目现在仅有一个雏形,现在提前把架构思路和项目最主要的特点分享出来,我会尽我的全力争取在年末让这个项目成为一个合格的开源项目