1、安装
vue create rockh5
2、项目结构

3、基础配置
vue.config.js
const isDebug = process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test'let webpackConfig = {}// console.log(isDebug,'isDebug>>>>')if (isDebug) { // developmentwebpackConfig = {lintOnSave: false, // 保存时候 lintdevServer: { // devServer 配置port: 8333,disableHostCheck: true},chainWebpack: config => {config.resolve.symlinks(true)config.entry('app').clear().add('./src/example/main.ts').end().plugin('html').tap(args => {args[0].template = './src/example/index.html'return args})return config}}} else { // productionwebpackConfig = {lintOnSave: false, // 保存时候 lintoutputDir: 'build', // output 目录productionSourceMap: false, // 是否打 sourcemapruntimeCompiler: false, // 是否使用包含运行时编译器的 Vue 构建版本,设置成 true 可以使用 template,但是大小会大 10kb 左右chainWebpack: config => {// entry 这些在命令行做了config.performance.hints(false) // 去除资源大小提示return config}, // webpack 配置css: {modules: false, // 是否作为 CSS Modules 模块extract: true, // 是否抽取 CSS 到独立文件,默认 porduction = true,development = falsesourceMap: false, // 是否为 CSS 开启 source maploaderOptions: {}, // 向 CSS 相关的 loader 传递选项},}}module.exports = webpackConfig
tslint.json
{"defaultSeverity": "warning","extends": ["tslint-config-standard"],"linterOptions": {"exclude": ["node_modules/**"]},"rules": {"no-use-before-declare": false,"no-unnecessary-qualifier": false,"strict-type-predicates": false,"no-unnecessary-type-assertion": false,"deprecation": false,"await-promise": false,"quotemark": [false, "single"],"indent": [false, "spaces", 2],"interface-name": false,"ordered-imports": false,"object-literal-sort-keys": false,"no-consecutive-blank-lines": false,"no-unused-expressions": false,"no-floating-promises": false,"no-unused-variable": false,"trailing-comma": false,"no-trailing-whitespace": false,"no-multi-spaces": false,"no-empty": false,"space-before-function-paren": false,"semicolon": [false, "never"]}}
tsconfig.json
{"compilerOptions": {"outDir": "./build/","target": "es5","module": "esnext","strict": false,"jsx": "preserve","moduleResolution": "node","experimentalDecorators": true,"noImplicitAny": false,"allowJs": false,"allowUnreachableCode": true,"sourceMap": true,// "importHelpers": true,// "esModuleInterop": true,"baseUrl": "src","types": ["webpack-env","jest"],"paths": {"@/*": ["src/*"]},"lib": ["es2015","es2016","es2017","esnext","dom"]},"include": ["src/**/*.ts","src/**/*.tsx","src/**/*.vue","tests/**/*.ts","tests/**/*.tsx"],"exclude": ["node_modules"]}
babel.config.json
module.exports = {presets: [['@vue/app', { useBuiltIns: false }, '@vue/cli-plugin-babel/preset'],],};
package.json
{"name": "rockh5","version": "0.1.0","private": false,"description": "","author": "商佳伟 - rock shang","scripts": {"dev": "vue-cli-service serve","build": "vue-cli-service build --target lib --name rockh5 src/rockh5.ts","lint": "vue-cli-service lint","p": "npm publish --registry http://registry.npmjs.org && cnpm sync"},"dependencies": {"core-js": "^3.6.5","jsonuri": "^2.2.8","vue": "^2.6.11"},"devDependencies": {"@babel/runtime-corejs2": "^7.2.0","@types/jest": "^23.1.4","@vue/cli-plugin-babel": "^3.0.1","@vue/cli-plugin-e2e-nightwatch": "^3.0.0-rc.11","@vue/cli-plugin-typescript": "^3.0.0-rc.11","@vue/cli-plugin-unit-jest": "^3.0.0-rc.11","@vue/cli-service": "^3.0.0-rc.11","@vue/test-utils": "^1.0.0-beta.20","autoprefixer": "^7.2.6","babel-core": "7.0.0-bridge.0","cssnano": "^3.10.0","cssnano-preset-advanced": "^4.0.0","lint-staged": "^6.0.0","postcss-aspect-ratio-mini": "^0.0.2","postcss-import": "^11.1.0","postcss-preset-env": "^5.3.0","postcss-px-to-viewport": "^0.0.3","postcss-url": "^7.3.2","postcss-viewport-units": "^0.1.4","postcss-write-svg": "^3.0.1","poststylus": "^1.0.0","rimraf": "^2.6.0","stylus": "^0.54.5","stylus-loader": "^3.0.2","ts-jest": "^23.0.0","tslint": "^5.11.0","tslint-config-standard": "^7.1.0","typescript": "^3.0.0","vue": "^2.5.17","vue-class-component": "^6.0.0","vue-property-decorator": "^7.0.0","vue-router": "^3.0.1","vue-template-compiler": "^2.5.17","vuex": "^3.1.0"},"eslintConfig": {"root": true,"env": {"node": true},"extends": ["plugin:vue/essential","eslint:recommended"],"parserOptions": {"parser": "babel-eslint"},"rules": {"indent_style": "space","indent_size": "2","end_of_line": "lf","insert_final_newline": "true","trim_trailing_whitespace": "true"}},"browserslist": ["> 1%","last 2 versions","Android >= 4.4","iOS >= 9"]}
4、入口文件
src/rockh5.ts
import { Btn, BtnGroup } from './packages/Btn'/*** Utils 工具方法*/import {transDateStr, formatDate,outclick,clone, mod, sleep, getNow, pad,decode, encode, qsParse, qsStringify, escapeHtml, escapeJs,throttle, debounce,px2vw, realPx, targetPx,isAndroid, isiOS, isIPX} from './packages/Utils'export {Btn, BtnGroup,transDateStr, formatDate,outclick,clone, mod, sleep, getNow, pad,decode, encode, qsParse, qsStringify, escapeHtml, escapeJs,throttle, debounce,px2vw, realPx, targetPx,isAndroid, isiOS, isIPX}
5、ts类型定义文件
src/typings/json.d.ts
declare module '*.json' {const value: anyexport default value}
src/typings/shims-tsx.d.ts
import Vue, { VNode } from 'vue'declare global {namespace JSX {// tslint:disable no-empty-interfaceinterface Element extends VNode {}// tslint:disable no-empty-interfaceinterface ElementClass extends Vue {}interface IntrinsicElements {[elem: string]: any}}}
src/typings/shims-vue.d.ts
declare module '*.vue' {import Vue from 'vue'export default Vue}
src/typings/vue-options.d.ts
import Vue from 'vue'declare module 'vue/types/options' {interface ComponentOptions<V extends Vue> {componentName?: string}}
src/typings/vue-types.d.ts
import Vue from 'vue'interface Loading {open: Function,close: Function}declare module 'vue/types/vue' {interface Vue {$loading: Loading,$toast: Function,$dialog: Function,$alert: Function,$confirm: Function}interface VueConstructor {install: (vue: typeof Vue) => void}}
6、包组件内容
按钮组件为例
src/packages/Btn/index.ts
import Btn from './src/btn.vue'import BtnGroup from './src/btnGroup.vue'Btn.install = function (Vue) {Vue.component(Btn.name, Btn)}BtnGroup.install = function (Vue) {Vue.component(BtnGroup.name, BtnGroup)}export { Btn, BtnGroup }
src/packages/Btn/src/btn.vue
<template><button:disabled="disabled":class="computedCls":style="computedStyle":type="actionType"@click="onClick"><span><slot></slot></span></button></template><script lang="ts">import Vue from 'vue';import { BtnStyle } from './interface';const ACTION_MAP = ['button', 'submit', 'reset'];const TYPE_MAP = ['primary', 'danger', 'warning', 'hollow', 'disabled'];const SIZE_MAP = ['mini', 'small', 'large'];const SHAPE_MAP = ['round', 'circle', 'square'];export default Vue.extend({name: 'TnBtn',props: {disabled: Boolean,actionType: {validator(value) {return ACTION_MAP.indexOf(value) > -1;},default: 'button',},type: {validator(value) {return TYPE_MAP.indexOf(value) > -1;},default: 'primary',},size: {validator(value) {return SIZE_MAP.indexOf(value) > -1;},default: 'large',},shape: {validator(value) {return SHAPE_MAP.indexOf(value) > -1;},default: 'round',},bgColor: {type: String,},color: {type: String,},},computed: {computedCls(): string[] {const ret = ['tn-btn',`tn-btn--${this.size}`,`tn-btn--${this.type}`,`tn-btn--${this.shape}`,];if (this.disabled) {ret.push('tn-btn--disabled');}return ret;},computedStyle(): BtnStyle {return {backgroundColor: this.bgColor,color: this.color,};},},methods: {onClick(e) {this.$emit('click', e);},},});</script><style lang="stylus">@import '../../../style/btn.styl'</style>
src/packages/Btn/src/btnGroup.vue
<template><div class="tn-btn-group"><slot></slot></div></template><script lang="ts">import Vue from 'vue'export default Vue.extend({name: 'TnBtnGroup',})</script><style lang="stylus">@import '../../../style/btn.styl'</style>
src/package/Btn/src/interface.ts
export interface BtnStyle {backgroundColor: string,color: string}
Utils
src/packages/Utils/index.ts
export { transDateStr, formatDate } from './date'export { outclick } from './dom'export { clone, mod, sleep, getNow, pad } from './other'export { decode, encode, qsParse, qsStringify, escapeHtml, escapeJs } from './security'export { throttle, debounce } from './throttle'export { px2vw, realPx, targetPx } from './transform'export { isAndroid, isiOS, isIPX } from './ua'
下面几个文件合到了一起
// date.ts// 转化时间字符串,解决兼容性 bugexport function transDateStr (time: string) {return time.replace(/-/g, '/') // iOS 兼容}// 格式化时间export function formatDate (time: string | number | Date, fmt: string = 'YYYY-MM-DD hh:mm:ss') {if (typeof time === 'string') {time = transDateStr(time)}let d = new Date(time)if (!fmt) return timelet obj = {'M+': d.getMonth() + 1,'D+': d.getDate(),'h+': d.getHours(),'m+': d.getMinutes(),'s+': d.getSeconds(),'q+': Math.floor((d.getMonth() + 3) / 3),'S': d.getMilliseconds()}if (/(Y+)/.test(fmt)) {fmt = fmt.replace(RegExp.$1, (d.getFullYear() + '').substr(4 - RegExp.$1.length))}for (let key in obj) {if (new RegExp('(' + key + ')').test(fmt)) {fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1)? (obj[key]): (('00' + obj[key]).substr(('' + obj[key]).length)))}}return fmt}// dom.tsconst $html: any = document.documentElementexport function outclick (elIds: string, callback) {'touchstart,click'.split(',').forEach(type => {$html.addEventListener(type, (e) => {const $els: any = Array.from(document.querySelectorAll(elIds))if (!$els.length) returnlet $target = e.targetwhile ($target) {if ($els.includes($target)) return$target = $target['parentNode']}callback(e)}, false)})}// 获取 rectexport function getRect (el) {if (typeof el === 'string') {el = document.querySelector(el)}if (el) {return el.getBoundingClientRect()}return {}}// other.tsexport const clone = d => JSON.parse(JSON.stringify(d))// 将一维数组按长度 m 分割成二维数组,最后一组不足 m,不传 fill 默认不补export const mod = (arr, m, fill): any[] => {arr = arr || []m < 0 && (m = 0)m = m || 3let ret: any[] = []for (let i = 0; i < arr.length; i += m) {let row: any[] = []for (let j = 0; j < m; ++j) {let col = arr[i + j]if (fill === undefined) {col && row.push(col)} else {row.push(col || fill)}}ret.push(row)}return ret}// setTimeout 语法糖export function sleep (delay: number): Promise<void> {return new Promise(resolve => {setTimeout(resolve, delay)})}// 获取当前时间,为将来扩展为从服务器取时间export async function getNow () {return Date.now()}// 补0export const pad = (num: any, n: number = 2) => {num = num || ''let len = num.toString().lengthwhile (len < n) {num = `0${num}`len++}return num}// security.ts/*** 解析字符串*/export function decode (s) {try {s = decodeURIComponent(s)} catch (e) {}return s}export const encode = encodeURIComponent/*** 解析 query*/let cache = {}export function qsParse (query = location.search): any {if (cache[query]) return cache[query]let params = {}query = query.replace(/^\?/, '')const queryArr = query.split('&')const len = queryArr.lengthfor (let i = 0; i < len; i++) {let [k, v] = queryArr[i].split('=')k && (params[decode(k)] = decode(v || ''))}cache[query] = paramsreturn params}/*** object 反序列化成 string*/export function qsStringify (o): string {let s: string[] = []for (let i in o) {s.push(`${i}=${encode(o[i])}`)}return s.join('&')}/*** 安全*/export function escapeHtml (s) {return s.replace(/&/g, '&').replace(/'/g, ''').replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>').replace(/\//g, '/')}export function escapeJs (s) {return String(s).replace(/\\/g, '\\\\').replace(/'/g, '\\\'').replace(/"/g, '\\"').replace(/`/g, '\\`').replace(/</g, '\\74').replace(/>/g, '\\76').replace(/\//g, '\\/').replace(/\n/g, '\\n').replace(/\r/g, '\\r').replace(/\t/g, '\\t').replace(/\f/g, '\\f').replace(/\v/g, '\\v').replace(/\b/g, '\\b').replace(/\0/g, '\\0')}// throttle.ts// 节流函数,指定时间执行一次export type Procedure = (...args: any[]) => voidexport type Options = {leading?: Boolean,trailing?: Boolean}// 节流函数 制定间隔执行export function throttle<F extends Procedure> (fn: F,wait: number = 100,options: Options = {}) {let timeoutlet contextlet argslet resultlet previous: number = 0const later = function () {previous = options.leading === false ? 0 : Date.now()timeout = nullresult = fn.apply(context, args)if (!timeout) context = args = null}const throttled: any = function (this: any) {const now = Date.now()if (!previous && options.leading === false) previous = nowconst remaining = wait - (now - previous)context = thisargs = argumentsif (remaining <= 0 || remaining > wait) {if (timeout) {clearTimeout(timeout)timeout = null}previous = nowresult = fn.apply(context, args)if (!timeout) context = args = null} else if (!timeout && options.trailing !== false) {timeout = setTimeout(later, remaining)}return result}throttled.cancel = function () {clearTimeout(timeout)previous = 0timeout = context = args = null}return throttled}// 防抖函数,到达指定时间间隔执行export function debounce<F extends Procedure>(func: F,waitMilliseconds = 50,isImmediate: boolean = false): F {let timeoutId: number | undefined | anyreturn function(this: any, ...args: any[]) {const context = thisconst doLater = function() {timeoutId = undefinedif (!isImmediate) {func.apply(context, args)}}const shouldCallNow = isImmediate && timeoutId === undefinedif (timeoutId !== undefined) {clearTimeout(timeoutId)}timeoutId = setTimeout(doLater, waitMilliseconds)if (shouldCallNow) {func.apply(context, args)}} as any}// transform.ts// 将 px 转化为 vwexport const px2vw = (pixel, viewportWidth = 375, unitPrecision = 3, minPixelValue = 1): string => {if (pixel <= minPixelValue) return pixelreturn (pixel * 100 / viewportWidth).toFixed(unitPrecision) + 'vw'}// 转化为实际 pxexport const realPx = (pixel, viewportWidth = 375, unitPrecision = 3): number => {const screenWidth = window.screen.widthreturn +((pixel * screenWidth / viewportWidth).toFixed(unitPrecision))}// 转化为目标 pxexport const targetPx = (pixel, viewportWidth = 375, unitPrecision = 3): number => {let screenWidth = window.screen.widthreturn +((pixel * viewportWidth / screenWidth).toFixed(unitPrecision))}// ua.tsexport function isAndroid () {const u = navigator.userAgentreturn u.indexOf('Android') > -1 || u.indexOf('Adr') > -1}export function isiOS () {const u = navigator.userAgentreturn !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/)}export function isIPX () {let screen = window.screenreturn isiOS() && screen.width === 375 && screen.height === 812}
7、样式文件
src/style/_base.styl
@import './_var.styl'@import './_mixin.styl'@import './_transition.styl'// 使用 px 转 vm 功能的副作用 问题:content会引起部分浏览器下,图片不会显示 所以要加该样式img{content: initial !important;}// 清除浮动.u-clearfixclearfix()// 隐藏滚动条.u-hide-scrollhide-scrollbar()// iconfont.u-iconfonticonfont()// 1px 边框.u-border-1pxborder-1px(all).u-border-bottom-1pxborder-1px(bottom).u-border-top-1pxborder-1px(top).u-border-left-1pxborder-1px(left).u-border-right-1pxborder-1px(right)// 超过省略号.u-ellipsisellipsis().u-ellipsis-2ellipsis(2).u-ellipsis-3ellipsis(3)
src/style/_mixin.styl
@import './_var.styl'// 转化为 vwr(px)round(px / 375 * 100, 6) vwline-1px($dir = bottom, $color = #EBEBEB, $radius = 0, $style = solid)content " "pointer-events nonebox-sizing border-boxposition absolutetransform-origin 0 0if $dir is bottomborder-bottom 1px $style $colorbottom 0left 0width 100%transform-origin: 0 bottom@media (min-resolution: 2dppx)width 200%transform scale(0.5)@media (min-resolution: 3dppx)width 300%transform scale(0.333)else if $dir is topborder-top 1px $style $colortop 0left 0width 100%transform-origin: 0 top@media (min-resolution: 2dppx)width 200%transform scale(0.5)@media (min-resolution: 3dppx)width 300%transform scale(0.333)else if $dir is leftborder-left 1px $style $colortop 0left 0height 100%transform-origin: left 0@media (min-resolution: 2dppx)height 200%transform scale(0.5)@media (min-resolution: 3dppx)height 300%transform scale(0.333)else if $dir is rightborder-right 1px $style $colortop 0right 0height 100%transform-origin: right 0@media (min-resolution: 2dppx)height 200%transform scale(0.5)@media (min-resolution: 3dppx)height 300%transform scale(0.333)elseleft 0top 0border 1px $style $colorborder-radius $radiuswidth 100%height 100%@media (min-resolution: 2dppx)width 200%height 200%border-radius $radius * 2transform scale(0.5)@media (min-resolution: 3dppx)width 300%height 300%border-radius $radius * 3transform scale(0.333)border-1px($dir = bottom, $color = #EBEBEB, $radius = 0, $style = solid)position relativeif $dir is all&::beforeline-1px($dir, $color, $radius, $style)else&::afterline-1px($dir, $color, $radius, $style)// 清除浮动clearfix()&::aftercontent " "display tableclear both// 隐藏滚动条hide-scrollbar()&::-webkit-scrollbarwidth 0height 0// iconfonticonfont()width 1emheight 1emvertical-align -0.15emfill currentColoroverflow hidden// 超出省略号ellipsis(line = 1)if line is 1overflow hiddentext-overflow ellipsiswhite-space nowrapelseoverflow hiddendisplay -webkit-box-webkit-box-orient vertical-webkit-line-clamp $line// 点击高亮tap-color($color, $percent = 10%)background-color: $color&:activebackground-color: darken($color, $percent)
src/style/_transition.styl
// fade 效果@keyframes fade-in0%opacity 0100%opacity 1@keyframes fade-out0%opacity 1100%opacity 0.fade-enter-activeanimation fade-in .3s.fade-leave-activeanimation fade-out .3s// slide 效果@keyframes slide-up-in0%transform translate3d(0, 100%, 0)100%transform translate3d(0, 0, 0)@keyframes slide-up-out0%transform translate3d(0, 0, 0)100%transform translate3d(0, 100%, 0).slide-up-enter-activeanimation slide-up-in .3s.slide-up-leave-activeanimation slide-up-out .3s
src/style/var.styl
// 全局变量@import './var/color.styl'@import './var/size.styl'// @import './var/env.styl'@import './theme/default.styl'
src/style/btn.styl
@import './_var.styl'@import './_mixin.styl'.tn-btn-grouppadding $btn-group-padding& > .tn-btnmargin-top $btn-marginTopmargin-left $btn-marginLeft&:first-childmargin-top 0margin-left 0.tn-btntext-align centerposition relativemargin 0padding 0border nonepointer-events autouser-select noneoutline nonedisplay inline-blockbox-sizing content-boxwhite-space nowrapfont-size: $btn-fontborder-radius $btn-radiuscolor $btn-color-default&--largedisplay blockwidth 100%font-weight boldheight $btn-height-largefont-size $btn-font-large&--smallmin-width $btn-width-smallheight $btn-height-smallpadding $btn-padding&--minimin-width $btn-width-miniheight $btn-height-minifont-size $btn-font-mini&--primarybackground-color $btn-bg-primarytap-color($btn-bg-primary, 10%)&--warningbackground-color $btn-bg-warningtap-color($btn-bg-warning, 10%)&--dangerbackground-color $btn-bg-dangertap-color($btn-bg-danger, 10%)&--hollowbackground-color $btn-bg-hollowcolor $btn-color-hollowtap-color($btn-bg-hollow, 3%)border-1px(all, $btn-border-hollow, $btn-radius)&--circleborder-radius $btn-radius-circle&--squareborder-radius 0&--disabledbackground $btn-bg-disabledcolor $btn-color-disabled
src/style/theme/default.styl
// Theme Variables//======== 【Btn】 ========$btn-group-padding = 12px 9px$btn-bg-primary = #0082FF$btn-bg-warning = #FFB400$btn-bg-danger = #EF4F4F$btn-bg-hollow = #FFF$btn-bg-disabled = #CCC$btn-color-default = #FFF$btn-color-hollow = #626B77$btn-color-disabled = #F0F0F0$btn-border-hollow = #EBEBEB$btn-height-large = 40px$btn-width-small = 90px$btn-height-small = 30px$btn-width-mini = 40px$btn-height-mini = 22px$btn-font = 15px$btn-font-large = 17px$btn-font-mini = 13px$btn-radius = 4px$btn-radius-circle = 100px$btn-padding = 0 5px$btn-marginTop = 12px$btn-marginLeft = 9px// ======== 【DateTime】 ========$datetime-content-height = 280px$datetime-item-height = 40px$datetime-item-font = 17px$datetime-item-font-active = 22px// ======== 【SelectPicker】 ========$selectpicker-content-height = 200px$selectpicker-item-height = 40px$selectpicker-item-font = 17px$selectpicker-item-font-active = 24px
src/style/var/color.styl
// 主题颜色$color-blue = #0082FF$color-green = #3BBD5E$color-black = #282828$color-orange = #FFA43D$color-red = #EA4335// 辅助颜色$color-fail = #440011// $color-success:// $color-warning:// $color-info:// 文本颜色$color-main-text = #333$color-sub-text = #626B77$color-desc-text = #999FAA// 线条颜色$color-border = #EBEBEB// 背景颜色$color-bg-page = #F6F6F9
src/style/var/size.styl
// 文本尺寸$size-main-text = 17px$size-sub-text = 15px$size-desc-text = 13px
8、开发验证example
src/example/index.html
<!DOCTYPE HTML><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"/><meta name="spm-id" content="{{$zebra.spma}}.{{$zebra.spmb}}"><meta name="aplus-terminal" content="1"/><title>vue h5 组件</title><link rel="stylesheet" href="//g.alicdn.com/fish/polyfill/0.0.2/reset.css"></head><body><div id="app"></div><script src="//g.alicdn.com/fish/console/index.js"></script><script src="//g.alicdn.com/fish/polyfill/0.0.1/core.min.js"></script></html>
src/example/main.ts
import Vue from 'vue'import Example from './Example.vue'import router from './router'import store from './store'const app = new Vue({el: '#app',router,store,render: h => h(Example)})
src/example/router.ts
import Vue from 'vue'import Router from 'vue-router'import Home from './pages/Home.vue'/*** Layout 布局组件*/import Btn from './pages/Btn.vue'Vue.use(Router)export const routes = [// Tunas{path: '/',component: Home,meta: {title: 'Home'}},// 布局{path: '/Btn',component: Btn,meta: {title: 'Btn',group: 'Layout',}}]export default new Router({mode: 'hash',routes})
src/example/store.ts
import Vue from 'vue'import Vuex from 'vuex'Vue.use(Vuex)/* eslint-disable no-new */export default new Vuex.Store({state: {status: 100, // 设备状态 0 - 未激活, 1 - 在线, 3 - 离线, 8 - 禁用,check: false},mutations: { // 更改 Vuex 的 store 中的状态的唯一方法是提交 mutation, 只能同步/*** 更新状态树中的属性集合* @param {*} attr 要更新的属性集合*/updateState(state, data) {data.find((key) => {state[key[0]] = key[1]});}},})
src/example/Example.vue
<template><div class="example" :class="{'example__menu--show': showMenu}"><div class="example__topbar"><button class="example__topbar-icon" @click="showMenu=true"></button></div><div class="example__menu"><div class="example__search"><input type="text" class="example__search-input" v-model="query"></div><ul class="example__menulist"><li v-for="(o, i) of list" :key="i"><h2>{{o.title}}</h2><ul><li v-for="(p, j) of o.children" :key="j"><router-link :to="p.path" v-html="highlight(p.meta.title)" @click.native="showMenu = false"></router-link></li></ul></li></ul></div><div class="example__view" :class="{'example__view--scroll': $route.meta.scroll}" :style="{padding: 0}"><router-view></router-view></div></div></template><script lang="ts">import Vue from 'vue'import { walk } from 'jsonuri'import { escapeHtml, outclick } from '../packages/Utils'import router, { routes } from './router'function formatRoutes (routes) {let ret: any[] = []const keys: string[] = routes.map(r => r.meta.group || 'RockH5')const groups: string[] = keys.filter((v, i, _) => _.indexOf(v) === i)groups.forEach(g => {let children: any[] = []if (g === 'RockH5') {children = routes.filter(r => !r.meta.group)} else {children = routes.filter(r => r.meta.group === g)}ret.push({title: g,children})})return ret}export default Vue.extend({data () {return {query: '',showMenu: false,list: formatRoutes(routes)}},computed: {bgColor(): any {return {backgroundColor: this.$route.meta.bgColor || '#fff'}}},created () {outclick('.example__topbar-icon, .example__menu', () => {this.showMenu = false})},watch: {query (qs) {let list = JSON.parse(JSON.stringify(this.list))walk(list, () => {})// this.list = this.list.filter(p => p.children.filter(p => p.title === qs).length > 0)}},methods: {highlight (str) {let query = this.query.trim()if (!query) return strlet reg = new RegExp(`(${query})`, 'ig')let ret = str.replace(reg, ($0, $1) => `<em class="example--highlight">${escapeHtml($1)}</em>`)return ret}}})</script><style lang="stylus">.example {max-width 100vwoverflow hiddena {text-decoration nonecolor #34495e&.router-link-exact-active {color #008EE9}}ul, li {list-style none}&--highlight {background yellow}&__topbar {height 50pxdisplay flexalign-items center}&__topbar-icon {border noneoutline nonemargin-left 10pxbackground url('//gw.alicdn.com/tfs/TB1g0gVrntYBeNjy1XdXXXXyVXa-48-48.png') no-repeatbackground-size coverwidth 24pxheight 24px}&__menu {transition all 0.2stransform translate3d(-100%, 0, 0)padding-top 30pxbox-shadow 0 0 10px rgba(0,0,0,0.2)box-sizing border-boxheight 100vhwidth 50%background #f9f9f9position fixedtop 0left 0overflow auto}&__menulist {padding-left 1.5emmargin-top 15pxli {margin-top 0.5em}ul {padding-left 1.5em}}&__search {height 30pxline-height 30pxbox-sizing border-boxpadding 0 15px 0 30pxborder 1px solid #e3e3e3color #2c3e50outline noneborder-radius 15pxmargin 0 10pxtransition border-color 0.2s easebackground: #fff url('//gw.alicdn.com/tfs/TB1hn5OtbSYBuNjSspiXXXNzpXa-96-96.png') 8px 5px no-repeatbackground-size: 20pxvertical-align: middle !important}&__search-input {width 100%height 100%outline nonebackground noneborder none}&__view {box-sizing border-boxtransition all 0.2stransform translate3d(0, 0, 0)background #fffmin-height calc(100vh - 50px)overflow-x hiddenpadding 0 15px&--full {margin-left 0}&--scroll {// height 120vh}}// 状态&__menu--show {.example__menu {transform translate3d(0, 0, 0)}.example__view {padding: 0 @important;transform translate3d(50%, 0, 0)}}}</style>
src/example/pages/Btn.vue
<template><div class="btn"><btn-group><btn size="mini">mini</btn><btn size="mini" type="warning">mini</btn><btn size="mini" type="danger">mini</btn><btn size="mini" type="hollow">mini</btn></btn-group><btn-group><btn size="small">small</btn><btn size="small" type="warning">small</btn><btn size="small" type="danger">small</btn></btn-group><btn-group><btn size="small" type="hollow">small</btn></btn-group><btn shape="circle">shape="circle"</btn><btn shape="square" type="warning">shape="square"</btn><btn shape="round" type="danger">shape="round"</btn><btn type="hollow">shape="round"</btn><btn disabled>disabled</btn></div></template><script lang="ts">import Vue from 'vue'import { BtnGroup, Btn } from '../../rockh5'export default Vue.extend({components: {BtnGroup, Btn}})</script><style lang="stylus" scoped></style>
src/example/pages/Home.vue
<template><div class="wrap">Hello, VueUI!</div></template><style lang="stylus" scoped>.wrap {margin-top 250pxtext-align centerfont-size 36px}</style><script lang="ts">import Vue from 'vue'export default Vue.extend({created () {},mounted () {}})</script>
