Age 21 present level P6 + coordinates Chengdu looking for a vue.js mobile H5 job

A resume that is pure truth without any packagingResume poke it

There are two career essays and the other one is hereA NUXT (VUE)+ Mongoose full stack project to talk about my shallow project architecture

Project introduction

Why this project?

After the reconstruction of the company’s project, the components of the project were removed and then the project was formed. The MAINTENANCE of the UI library based on the project was also convenient

The project address

Student computer server ui.qymh.org.cn, Ali Cloud provided a CDN of 0.9 yuan at that time, although the server is a little bad but I hung up the CDN, the access should not be blocked attention to the PC side view, please press F12 to adjust to the mobile side view Also note that when opening the project in the Nuggets app, clicking the back arrow in my project does not work. I don’t know why, need to click the arrow return route provided by gold nuggets app

github

Project github address QymhUI

Project screenshots

Project directory

The project catalog mimics element-UI. Let’s start with an image

Directory analysis

Component package JS Dist column js dist column docs mounted static Github Page examples subdirectory Packages component directory SRC resource directory Typings Built namespace Webpack Webpack directory

The component directory

With so many components constructed, the directory in this place is a copy of the element-UI architecture directory

The project architecture

Webpack configuration

Webpack here is a big knowledge point, it is too troublesome to describe, here I would like to mention the webpack of this project and other differences

  • 1 webpackpackagingtypescriptI introducedForkTsCheckerWebpackPluginThe biggest impact is the speed of packaging, and the plugin is highly adaptablevue, also providedtslintAlthough I didn’t mention it in this project, I’ll mention it later
  • 2 I have one in my projectqymhui.config.jsThis file is a UI configuration item that is exposed to developers, like.babelrc postcss.config.jsSame thing, I’m herewebpackRead him in, and then passwebpack.definePluginwriteprocess.env1. Js leaked to developers can only be usedcommonjsSyntax 2. I disclosed that in JS developers can write functions, howeverJSON.stringifyThe function was ignored directly, and I solved the problem with object deep copy

The architecture analysis

  • Step 1 is inpackagesTo create a component directory, the following steps will useq-radioThis button component does the listing, and let’s take a look at its directory structure

    I use the template enginepug.vueIn the writingtypescriptI use thevue-property-decoratorFor the preprocessorscss

packages/radio/index.ts

import Radio from './src/main.vue'
export default Radio
Copy the code

packages/radio/src/main.vue

<template lang="pug">. Q-radio (:style="computedOuterStyle") // -square selector. 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: "} ") / / - circular selector. 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 // type @prop ({default: config.type}) private type: radio.type @prop ({default: config.type}) Config.hasborder}) private hasBorder: Boolean @prop ({default: config.borderColor}) private borderColor: @prop ({default: config.activecolor}) private activeColor: string @prop ({default: config.activecolor}) private activeColor: string Config.activebkcolor}) private activeBkColor: string activeBkColor @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.5 rem; Width: 0.5 rem; position: relative; } .q-radio-rect { position: absolute; top: 0; left: 0; Height: 0.5 rem; Width: 0.5 rem; The line - height: 0.5 rem; Border - the radius: 0.05 rem; 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.5 rem; Width: 0.5 rem; The line - height: 0.5 rem; 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>Copy the code
  • 2 Step 2 quote and expose

I introduced this component in SRC /index.ts and left out the method for registering the component, which is also written like element-ui Component.name is used to get component names, but ts package component names are compressed. This is a Bug, so we need to store each component name in an array. Let’s look at the code

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
}

Copy the code
  • Vue. Use is used to install the plugin

Project characteristics

Rapid development of

Train of thought

What’s different from other UI frameworks is that we’re innovating in the layout of our components. Normally, when we’re working on a project, we’re writing HTML, and then WE’re writing CSS, and there’s a lot of complicated naming in HTML, and if you use BEM naming conventions, like.a_b_c.A-b_C This is just a test. In a real development environment, the length would be terrible, so we eliminated the element naming in the layout component and kept CSS writing costs to a minimum

architecture

This place is implemented using typesrcipt’s inheritance

First construct the properties vue and TS. The following example is an example of q-Row. I put the commonly used CSS styles directly into the PROP of q-Row components

packages/proto/row/main.vue

<script lang="tsx">
import { Vue, Component, Prop } from 'vue-property-decorator'

@Component
exportDefault class extends Vue {// @prop ({default: -1}) public h: string @prop ({default: -1}) public LH: @prop ({default: -1}) public w: string @prop ({default: -1}) public row: Margin-top @prop ({default: -1}) public col: string // margin-top @prop ({default: -1}) 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: 0})'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 | the string / / font size @ Prop (} {default: - 1) public fontSize: the string / / font color @ Prop ({default:' '}) public color: string // backcolor @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>
Copy the code

packages/proto/row/index.ts

// Construct the global styleexport default functionCreateStyle (vm: any) {const style: any = {// High: 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}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: 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]} //if (i === 'overflow' && (item === 'auto' || item === 'scroll')) {
        style['-webkit-overflow-scrolling'] = 'touch'}}}return style
}

Copy the code

extensible

Train of thought

Unlike other UI frameworks, we provide config to change the default UI layout. The component size of your project may not be the same as that provided by the UI library, that’s ok, we have the basic UI layout built in, but you can use qymhui.config.js to modify our default configuration and create your own UI library

architecture

We provided a default configuration, then exposed a configuration to the user, which was read in the Node environment via Webpack, merged the two configurations and passed them to the component. Here is the default configuration for 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: 'Please enter... '.clearable: false.rightText: 'search'.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: 'Click upload'.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 = {
  // Drop refresh
  down: (vm) = > {
    return {
      // Whether to enable it
      use: true.// Whether it is the first call
      auto: false./ / callback
      callback(mescroll) {
        vm.$emit('refresh')}}},// Pull up load
  up: (vm) = > {
    return {
      // Whether to enable it
      use: true.// Whether it is the first call
      auto: true.// Whether to enable the scroll bar
      scrollbar: {
        use: true
      },
      / / callback
      callback: (page, mescroll) = > {
        vm.$emit('load', page)
      },
      // No data is available
      htmlNodata: '

-- no more data --

'
}}}// $notice export const $notice = { / / to remind toast: { position: 'bottom'.timeout: 1500 }, / / window confirm: { text: 'Please enter text'.btnLeft: 'sure'.btnRight: 'cancel'}}// $cookie export const $cookie = { // Expiration time enpireDays: 7 } // $axios export const $axios = { // Whether to enter logs log: true./ / timeout timeout: 20000.// Request interceptor requestFn: (config) = > { return config }, // Response interceptor responseFn: (response) = > { return response } } Copy the code

Not just UI components

Widget

We provide common methods for widgets in addition to UI components in the project and mount them directly on the vue prototype. You can reference them directly in the Vue environment such as $cookie setting cookie $storage setting storage $toast reminder plugin $axios Ajax wrapper The $cookie wrapper is pasted below

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= {/ * * * for a cookie * @ param key 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 ' '}, /** * set cookie * @param key * @param value value * @param expireDays retention date */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}`}, / delete cookies * * * * @ param * / delete key key (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()}'}}, /** * delete all cookies */ directlydeleteAll() {
      let cookie = document.cookie
      let arr = cookie.split('; ')
      let later = ' '
      let now = new Date()
      now.setDate(now.getDate() - 1)

      for (let i in arr) {
        letitem = arr[i] later = item + `; expires=${now.toUTCString()}`
        document.cookie = later
      }
    }
  }
}

Vue.use(Cookie)

Copy the code

What we’re going to do

  • Mobile adaptation currently only supports flexibly. js REM layout, which is problematic, as officially mentioned by flexibly. js, and will be rewritten via VH later

  • UI modules need to be added. The current UI framework is a commonly used module extracted from our project, but it does not mean that there are too few commonly used modules

  • Documentation is currently only available on mobile, but will be available on PC in the future

conclusion

In fact, the project is going to open source at the end of the year. I will make more functions, do more tests, improve the documentation and modify the interface to make it more friendly and simple. Now there is only an embryonic form of the project. Now I will share the architectural ideas and main features of the project in advance. I will try my best to make the project a qualified open source project by the end of this year