這是一篇求職文章 年齡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
打包後的元件js
dist
列子打包後的檔案
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
<template lang="pug">
.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:''}")
</template>
<script lang="ts">
import { Vue, Component, Prop, Emit } from 'vue-property-decorator'
import Proto from '../../proto/tag/main.vue'
import createStyle from '../../proto/tag'
const config = require('../../../src/qymhui.config').default.qradio
@Component({})
export default class QRadio extends Proto {
// 啟用狀態
private active: boolean = false
// 型別
@Prop({ default: config.type })
private type: radio.type
// 是否有邊框
@Prop({ default: config.hasBorder })
private hasBorder: boolean
// 邊框顏色
@Prop({ default: config.borderColor })
private borderColor: string
// 啟用下的顏色
@Prop({ default: config.activeColor })
private activeColor: string
// 啟用下的背景顏色
@Prop({ default: config.activeBkColor })
private activeBkColor: string
// 啟用下的border顏色
@Prop({ default: config.activeBorderColor })
private activeBorderColor: string
private get computedStyle() {
let style = Object.create(null)
if (this.hasBorder) {
style.borderStyle = 'solid'
style.borderWidth = '1px'
if (this.active) {
style.borderColor = this.activeBorderColor
} else {
style.borderColor = this.borderColor
}
}
if (this.active && this.activeBkColor && this.type === 'circle') {
style.backgroundColor = this.activeBkColor
}
return style
}
private get computedOuterStyle() {
let style = createStyle(this)
return style
}
@Emit()
private change(active: boolean) {
this.active = !this.active
}
}
</script>
<style lang="scss" scoped>
.q-radio {
display: inline-block;
height: 0.5rem;
width: 0.5rem;
position: relative;
}
.q-radio-rect {
position: absolute;
top: 0;
left: 0;
height: 0.5rem;
width: 0.5rem;
line-height: 0.5rem;
border-radius: 0.05rem;
display: inline-block;
font-size: 10px;
text-align: center;
> span {
display: inline-block;
height: 100%;
width: 100%;
> i {
font-size: 14px;
}
}
}
.q-radio-circle {
position: absolute;
top: 0;
left: 0;
height: 0.5rem;
width: 0.5rem;
line-height: 0.5rem;
border-radius: 50%;
display: inline-block;
font-size: 10px;
text-align: center;
&-value {
color: #fff;
}
> span {
display: inline-block;
height: 100%;
width: 100%;
> i {
font-size: 14px;
}
}
}
</style>
複製程式碼
- 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
<script lang="tsx">
import { Vue, Component, Prop } from 'vue-property-decorator'
@Component
export default class Proto extends Vue {
// 高
@Prop({ default: -1 })
public h: string
// 行高
@Prop({ default: -1 })
public lh: string
// 寬
@Prop({ default: -1 })
public w: string
// 高度百分比
@Prop({ default: -1 })
public row: string
// 寬度百分比
@Prop({ default: -1 })
public col: string
// margin-top
@Prop({ default: 0 })
public mt: string
// margin-right
@Prop({ default: 0 })
public mr: string
// margin-bottom
@Prop({ default: 0 })
public mb: string
// margin-left
@Prop({ default: 0 })
public ml: string
// padding-top
@Prop({ default: 0 })
public pt: string
// padding-right
@Prop({ default: 0 })
public pr: string
// padding-bottom
@Prop({ default: 0 })
public pb: string
// padding-left
@Prop({ default: 0 })
public pl: string
// 定位
@Prop({ default: 'static' })
public position: common.position
// top
@Prop({ default: -1 })
public t: number | string
// right
@Prop({ default: -1 })
public r: number | string
// bottom
@Prop({ default: -1 })
public b: number | string
// left
@Prop({ default: -1 })
public l: number | string
// 字型大小
@Prop({ default: -1 })
public fontSize: string
// 字型顏色
@Prop({ default: '' })
public color: string
// 背景顏色
@Prop({ default: '' })
public bkColor: string
// text-align
@Prop({ default: '' })
public textAlign: common.textAlign
// z-index
@Prop({ default: 'auto' })
public zIndex: string
// display
@Prop({ default: '' })
public display: common.display
// vertical-align
@Prop({ default: 'baseline' })
public vertical: common.vertical
// overflow
@Prop({ default: 'visible' })
public overflow: common.overflow
// text-decoration
@Prop({ default: 'none' })
public decoration: common.decoration
// border-radius
@Prop({ default: -1 })
public radius: number | string
// word-break
@Prop({ default: 'normal' })
public wordBreak: common.wordBreak
// text-indent
@Prop({ default: -1 })
public indent: string
// border
@Prop({ default: '' })
public border: string
// border-top
@Prop({ default: '' })
public borderTop: string
// border-right
@Prop({ default: '' })
public borderRight: string
// border-bottom
@Prop({ default: '' })
public borderBottom: string
// border-left
@Prop({ default: '' })
public borderLeft: string
}
</script>
複製程式碼
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: '<p class="upwarp-nodata">-- 沒有更多的資料 --</p>'
}
}
}
// $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端版本
結語
其實專案想在年末的時候開源,我多做一些功能,多做一點測試,多完善文件,多修改介面保證更友好更簡單.但沒辦法,要找工作了,專案現在僅有一個雛形,現在提前把架構思路和專案最主要的特點分享出來,我會盡我的全力爭取在年末讓這個專案成為一個合格的開源專案