1、安装

vue create rockh5

2、项目结构

image.png

3、基础配置

vue.config.js

  1. const isDebug = process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test'
  2. let webpackConfig = {}
  3. // console.log(isDebug,'isDebug>>>>')
  4. if (isDebug) { // development
  5. webpackConfig = {
  6. lintOnSave: false, // 保存时候 lint
  7. devServer: { // devServer 配置
  8. port: 8333,
  9. disableHostCheck: true
  10. },
  11. chainWebpack: config => {
  12. config.resolve
  13. .symlinks(true)
  14. config
  15. .entry('app')
  16. .clear()
  17. .add('./src/example/main.ts')
  18. .end()
  19. .plugin('html')
  20. .tap(args => {
  21. args[0].template = './src/example/index.html'
  22. return args
  23. })
  24. return config
  25. }
  26. }
  27. } else { // production
  28. webpackConfig = {
  29. lintOnSave: false, // 保存时候 lint
  30. outputDir: 'build', // output 目录
  31. productionSourceMap: false, // 是否打 sourcemap
  32. runtimeCompiler: false, // 是否使用包含运行时编译器的 Vue 构建版本,设置成 true 可以使用 template,但是大小会大 10kb 左右
  33. chainWebpack: config => {
  34. // entry 这些在命令行做了
  35. config
  36. .performance
  37. .hints(false) // 去除资源大小提示
  38. return config
  39. }, // webpack 配置
  40. css: {
  41. modules: false, // 是否作为 CSS Modules 模块
  42. extract: true, // 是否抽取 CSS 到独立文件,默认 porduction = true,development = false
  43. sourceMap: false, // 是否为 CSS 开启 source map
  44. loaderOptions: {}, // 向 CSS 相关的 loader 传递选项
  45. },
  46. }
  47. }
  48. module.exports = webpackConfig

tslint.json

  1. {
  2. "defaultSeverity": "warning",
  3. "extends": [
  4. "tslint-config-standard"
  5. ],
  6. "linterOptions": {
  7. "exclude": [
  8. "node_modules/**"
  9. ]
  10. },
  11. "rules": {
  12. "no-use-before-declare": false,
  13. "no-unnecessary-qualifier": false,
  14. "strict-type-predicates": false,
  15. "no-unnecessary-type-assertion": false,
  16. "deprecation": false,
  17. "await-promise": false,
  18. "quotemark": [false, "single"],
  19. "indent": [false, "spaces", 2],
  20. "interface-name": false,
  21. "ordered-imports": false,
  22. "object-literal-sort-keys": false,
  23. "no-consecutive-blank-lines": false,
  24. "no-unused-expressions": false,
  25. "no-floating-promises": false,
  26. "no-unused-variable": false,
  27. "trailing-comma": false,
  28. "no-trailing-whitespace": false,
  29. "no-multi-spaces": false,
  30. "no-empty": false,
  31. "space-before-function-paren": false,
  32. "semicolon": [false, "never"]
  33. }
  34. }

tsconfig.json

  1. {
  2. "compilerOptions": {
  3. "outDir": "./build/",
  4. "target": "es5",
  5. "module": "esnext",
  6. "strict": false,
  7. "jsx": "preserve",
  8. "moduleResolution": "node",
  9. "experimentalDecorators": true,
  10. "noImplicitAny": false,
  11. "allowJs": false,
  12. "allowUnreachableCode": true,
  13. "sourceMap": true,
  14. // "importHelpers": true,
  15. // "esModuleInterop": true,
  16. "baseUrl": "src",
  17. "types": [
  18. "webpack-env",
  19. "jest"
  20. ],
  21. "paths": {
  22. "@/*": [
  23. "src/*"
  24. ]
  25. },
  26. "lib": [
  27. "es2015",
  28. "es2016",
  29. "es2017",
  30. "esnext",
  31. "dom"
  32. ]
  33. },
  34. "include": [
  35. "src/**/*.ts",
  36. "src/**/*.tsx",
  37. "src/**/*.vue",
  38. "tests/**/*.ts",
  39. "tests/**/*.tsx"
  40. ],
  41. "exclude": [
  42. "node_modules"
  43. ]
  44. }

babel.config.json

  1. module.exports = {
  2. presets: [
  3. ['@vue/app', { useBuiltIns: false }, '@vue/cli-plugin-babel/preset'],
  4. ],
  5. };

package.json

  1. {
  2. "name": "rockh5",
  3. "version": "0.1.0",
  4. "private": false,
  5. "description": "",
  6. "author": "商佳伟 - rock shang",
  7. "scripts": {
  8. "dev": "vue-cli-service serve",
  9. "build": "vue-cli-service build --target lib --name rockh5 src/rockh5.ts",
  10. "lint": "vue-cli-service lint",
  11. "p": "npm publish --registry http://registry.npmjs.org && cnpm sync"
  12. },
  13. "dependencies": {
  14. "core-js": "^3.6.5",
  15. "jsonuri": "^2.2.8",
  16. "vue": "^2.6.11"
  17. },
  18. "devDependencies": {
  19. "@babel/runtime-corejs2": "^7.2.0",
  20. "@types/jest": "^23.1.4",
  21. "@vue/cli-plugin-babel": "^3.0.1",
  22. "@vue/cli-plugin-e2e-nightwatch": "^3.0.0-rc.11",
  23. "@vue/cli-plugin-typescript": "^3.0.0-rc.11",
  24. "@vue/cli-plugin-unit-jest": "^3.0.0-rc.11",
  25. "@vue/cli-service": "^3.0.0-rc.11",
  26. "@vue/test-utils": "^1.0.0-beta.20",
  27. "autoprefixer": "^7.2.6",
  28. "babel-core": "7.0.0-bridge.0",
  29. "cssnano": "^3.10.0",
  30. "cssnano-preset-advanced": "^4.0.0",
  31. "lint-staged": "^6.0.0",
  32. "postcss-aspect-ratio-mini": "^0.0.2",
  33. "postcss-import": "^11.1.0",
  34. "postcss-preset-env": "^5.3.0",
  35. "postcss-px-to-viewport": "^0.0.3",
  36. "postcss-url": "^7.3.2",
  37. "postcss-viewport-units": "^0.1.4",
  38. "postcss-write-svg": "^3.0.1",
  39. "poststylus": "^1.0.0",
  40. "rimraf": "^2.6.0",
  41. "stylus": "^0.54.5",
  42. "stylus-loader": "^3.0.2",
  43. "ts-jest": "^23.0.0",
  44. "tslint": "^5.11.0",
  45. "tslint-config-standard": "^7.1.0",
  46. "typescript": "^3.0.0",
  47. "vue": "^2.5.17",
  48. "vue-class-component": "^6.0.0",
  49. "vue-property-decorator": "^7.0.0",
  50. "vue-router": "^3.0.1",
  51. "vue-template-compiler": "^2.5.17",
  52. "vuex": "^3.1.0"
  53. },
  54. "eslintConfig": {
  55. "root": true,
  56. "env": {
  57. "node": true
  58. },
  59. "extends": [
  60. "plugin:vue/essential",
  61. "eslint:recommended"
  62. ],
  63. "parserOptions": {
  64. "parser": "babel-eslint"
  65. },
  66. "rules": {
  67. "indent_style": "space",
  68. "indent_size": "2",
  69. "end_of_line": "lf",
  70. "insert_final_newline": "true",
  71. "trim_trailing_whitespace": "true"
  72. }
  73. },
  74. "browserslist": [
  75. "> 1%",
  76. "last 2 versions",
  77. "Android >= 4.4",
  78. "iOS >= 9"
  79. ]
  80. }

4、入口文件

src/rockh5.ts

  1. import { Btn, BtnGroup } from './packages/Btn'
  2. /**
  3. * Utils 工具方法
  4. */
  5. import {
  6. transDateStr, formatDate,
  7. outclick,
  8. clone, mod, sleep, getNow, pad,
  9. decode, encode, qsParse, qsStringify, escapeHtml, escapeJs,
  10. throttle, debounce,
  11. px2vw, realPx, targetPx,
  12. isAndroid, isiOS, isIPX
  13. } from './packages/Utils'
  14. export {
  15. Btn, BtnGroup,
  16. transDateStr, formatDate,
  17. outclick,
  18. clone, mod, sleep, getNow, pad,
  19. decode, encode, qsParse, qsStringify, escapeHtml, escapeJs,
  20. throttle, debounce,
  21. px2vw, realPx, targetPx,
  22. isAndroid, isiOS, isIPX
  23. }

5、ts类型定义文件

src/typings/json.d.ts

  1. declare module '*.json' {
  2. const value: any
  3. export default value
  4. }

src/typings/shims-tsx.d.ts

  1. import Vue, { VNode } from 'vue'
  2. declare global {
  3. namespace JSX {
  4. // tslint:disable no-empty-interface
  5. interface Element extends VNode {}
  6. // tslint:disable no-empty-interface
  7. interface ElementClass extends Vue {}
  8. interface IntrinsicElements {
  9. [elem: string]: any
  10. }
  11. }
  12. }

src/typings/shims-vue.d.ts

  1. declare module '*.vue' {
  2. import Vue from 'vue'
  3. export default Vue
  4. }

src/typings/vue-options.d.ts

  1. import Vue from 'vue'
  2. declare module 'vue/types/options' {
  3. interface ComponentOptions<V extends Vue> {
  4. componentName?: string
  5. }
  6. }

src/typings/vue-types.d.ts

  1. import Vue from 'vue'
  2. interface Loading {
  3. open: Function,
  4. close: Function
  5. }
  6. declare module 'vue/types/vue' {
  7. interface Vue {
  8. $loading: Loading,
  9. $toast: Function,
  10. $dialog: Function,
  11. $alert: Function,
  12. $confirm: Function
  13. }
  14. interface VueConstructor {
  15. install: (vue: typeof Vue) => void
  16. }
  17. }

6、包组件内容

按钮组件为例

src/packages/Btn/index.ts

  1. import Btn from './src/btn.vue'
  2. import BtnGroup from './src/btnGroup.vue'
  3. Btn.install = function (Vue) {
  4. Vue.component(Btn.name, Btn)
  5. }
  6. BtnGroup.install = function (Vue) {
  7. Vue.component(BtnGroup.name, BtnGroup)
  8. }
  9. export { Btn, BtnGroup }

src/packages/Btn/src/btn.vue

  1. <template>
  2. <button
  3. :disabled="disabled"
  4. :class="computedCls"
  5. :style="computedStyle"
  6. :type="actionType"
  7. @click="onClick"
  8. >
  9. <span><slot></slot></span>
  10. </button>
  11. </template>
  12. <script lang="ts">
  13. import Vue from 'vue';
  14. import { BtnStyle } from './interface';
  15. const ACTION_MAP = ['button', 'submit', 'reset'];
  16. const TYPE_MAP = ['primary', 'danger', 'warning', 'hollow', 'disabled'];
  17. const SIZE_MAP = ['mini', 'small', 'large'];
  18. const SHAPE_MAP = ['round', 'circle', 'square'];
  19. export default Vue.extend({
  20. name: 'TnBtn',
  21. props: {
  22. disabled: Boolean,
  23. actionType: {
  24. validator(value) {
  25. return ACTION_MAP.indexOf(value) > -1;
  26. },
  27. default: 'button',
  28. },
  29. type: {
  30. validator(value) {
  31. return TYPE_MAP.indexOf(value) > -1;
  32. },
  33. default: 'primary',
  34. },
  35. size: {
  36. validator(value) {
  37. return SIZE_MAP.indexOf(value) > -1;
  38. },
  39. default: 'large',
  40. },
  41. shape: {
  42. validator(value) {
  43. return SHAPE_MAP.indexOf(value) > -1;
  44. },
  45. default: 'round',
  46. },
  47. bgColor: {
  48. type: String,
  49. },
  50. color: {
  51. type: String,
  52. },
  53. },
  54. computed: {
  55. computedCls(): string[] {
  56. const ret = [
  57. 'tn-btn',
  58. `tn-btn--${this.size}`,
  59. `tn-btn--${this.type}`,
  60. `tn-btn--${this.shape}`,
  61. ];
  62. if (this.disabled) {
  63. ret.push('tn-btn--disabled');
  64. }
  65. return ret;
  66. },
  67. computedStyle(): BtnStyle {
  68. return {
  69. backgroundColor: this.bgColor,
  70. color: this.color,
  71. };
  72. },
  73. },
  74. methods: {
  75. onClick(e) {
  76. this.$emit('click', e);
  77. },
  78. },
  79. });
  80. </script>
  81. <style lang="stylus">
  82. @import '../../../style/btn.styl'
  83. </style>

src/packages/Btn/src/btnGroup.vue

  1. <template>
  2. <div class="tn-btn-group">
  3. <slot></slot>
  4. </div>
  5. </template>
  6. <script lang="ts">
  7. import Vue from 'vue'
  8. export default Vue.extend({
  9. name: 'TnBtnGroup',
  10. })
  11. </script>
  12. <style lang="stylus">
  13. @import '../../../style/btn.styl'
  14. </style>

src/package/Btn/src/interface.ts

  1. export interface BtnStyle {
  2. backgroundColor: string,
  3. color: string
  4. }

Utils

src/packages/Utils/index.ts

  1. export { transDateStr, formatDate } from './date'
  2. export { outclick } from './dom'
  3. export { clone, mod, sleep, getNow, pad } from './other'
  4. export { decode, encode, qsParse, qsStringify, escapeHtml, escapeJs } from './security'
  5. export { throttle, debounce } from './throttle'
  6. export { px2vw, realPx, targetPx } from './transform'
  7. export { isAndroid, isiOS, isIPX } from './ua'

下面几个文件合到了一起

  1. // date.ts
  2. // 转化时间字符串,解决兼容性 bug
  3. export function transDateStr (time: string) {
  4. return time.replace(/-/g, '/') // iOS 兼容
  5. }
  6. // 格式化时间
  7. export function formatDate (time: string | number | Date, fmt: string = 'YYYY-MM-DD hh:mm:ss') {
  8. if (typeof time === 'string') {
  9. time = transDateStr(time)
  10. }
  11. let d = new Date(time)
  12. if (!fmt) return time
  13. let obj = {
  14. 'M+': d.getMonth() + 1,
  15. 'D+': d.getDate(),
  16. 'h+': d.getHours(),
  17. 'm+': d.getMinutes(),
  18. 's+': d.getSeconds(),
  19. 'q+': Math.floor((d.getMonth() + 3) / 3),
  20. 'S': d.getMilliseconds()
  21. }
  22. if (/(Y+)/.test(fmt)) {
  23. fmt = fmt.replace(RegExp.$1, (d.getFullYear() + '').substr(4 - RegExp.$1.length))
  24. }
  25. for (let key in obj) {
  26. if (new RegExp('(' + key + ')').test(fmt)) {
  27. fmt = fmt.replace(
  28. RegExp.$1, (RegExp.$1.length === 1)
  29. ? (obj[key])
  30. : (('00' + obj[key]).substr(('' + obj[key]).length)))
  31. }
  32. }
  33. return fmt
  34. }
  35. // dom.ts
  36. const $html: any = document.documentElement
  37. export function outclick (elIds: string, callback) {
  38. 'touchstart,click'.split(',').forEach(type => {
  39. $html.addEventListener(type, (e) => {
  40. const $els: any = Array.from(document.querySelectorAll(elIds))
  41. if (!$els.length) return
  42. let $target = e.target
  43. while ($target) {
  44. if ($els.includes($target)) return
  45. $target = $target['parentNode']
  46. }
  47. callback(e)
  48. }, false)
  49. })
  50. }
  51. // 获取 rect
  52. export function getRect (el) {
  53. if (typeof el === 'string') {
  54. el = document.querySelector(el)
  55. }
  56. if (el) {
  57. return el.getBoundingClientRect()
  58. }
  59. return {}
  60. }
  61. // other.ts
  62. export const clone = d => JSON.parse(JSON.stringify(d))
  63. // 将一维数组按长度 m 分割成二维数组,最后一组不足 m,不传 fill 默认不补
  64. export const mod = (arr, m, fill): any[] => {
  65. arr = arr || []
  66. m < 0 && (m = 0)
  67. m = m || 3
  68. let ret: any[] = []
  69. for (let i = 0; i < arr.length; i += m) {
  70. let row: any[] = []
  71. for (let j = 0; j < m; ++j) {
  72. let col = arr[i + j]
  73. if (fill === undefined) {
  74. col && row.push(col)
  75. } else {
  76. row.push(col || fill)
  77. }
  78. }
  79. ret.push(row)
  80. }
  81. return ret
  82. }
  83. // setTimeout 语法糖
  84. export function sleep (delay: number): Promise<void> {
  85. return new Promise(resolve => {
  86. setTimeout(resolve, delay)
  87. })
  88. }
  89. // 获取当前时间,为将来扩展为从服务器取时间
  90. export async function getNow () {
  91. return Date.now()
  92. }
  93. // 补0
  94. export const pad = (num: any, n: number = 2) => {
  95. num = num || ''
  96. let len = num.toString().length
  97. while (len < n) {
  98. num = `0${num}`
  99. len++
  100. }
  101. return num
  102. }
  103. // security.ts
  104. /**
  105. * 解析字符串
  106. */
  107. export function decode (s) {
  108. try {
  109. s = decodeURIComponent(s)
  110. } catch (e) {}
  111. return s
  112. }
  113. export const encode = encodeURIComponent
  114. /**
  115. * 解析 query
  116. */
  117. let cache = {}
  118. export function qsParse (query = location.search): any {
  119. if (cache[query]) return cache[query]
  120. let params = {}
  121. query = query.replace(/^\?/, '')
  122. const queryArr = query.split('&')
  123. const len = queryArr.length
  124. for (let i = 0; i < len; i++) {
  125. let [k, v] = queryArr[i].split('=')
  126. k && (params[decode(k)] = decode(v || ''))
  127. }
  128. cache[query] = params
  129. return params
  130. }
  131. /**
  132. * object 反序列化成 string
  133. */
  134. export function qsStringify (o): string {
  135. let s: string[] = []
  136. for (let i in o) {
  137. s.push(`${i}=${encode(o[i])}`)
  138. }
  139. return s.join('&')
  140. }
  141. /**
  142. * 安全
  143. */
  144. export function escapeHtml (s) {
  145. return s.replace(/&/g, '&amp;')
  146. .replace(/'/g, '&#39;')
  147. .replace(/"/g, '&quot;')
  148. .replace(/</g, '&lt;')
  149. .replace(/>/g, '&gt;')
  150. .replace(/\//g, '&#x2f;')
  151. }
  152. export function escapeJs (s) {
  153. return String(s)
  154. .replace(/\\/g, '\\\\')
  155. .replace(/'/g, '\\\'')
  156. .replace(/"/g, '\\"')
  157. .replace(/`/g, '\\`')
  158. .replace(/</g, '\\74')
  159. .replace(/>/g, '\\76')
  160. .replace(/\//g, '\\/')
  161. .replace(/\n/g, '\\n')
  162. .replace(/\r/g, '\\r')
  163. .replace(/\t/g, '\\t')
  164. .replace(/\f/g, '\\f')
  165. .replace(/\v/g, '\\v')
  166. .replace(/\b/g, '\\b')
  167. .replace(/\0/g, '\\0')
  168. }
  169. // throttle.ts
  170. // 节流函数,指定时间执行一次
  171. export type Procedure = (...args: any[]) => void
  172. export type Options = {
  173. leading?: Boolean,
  174. trailing?: Boolean
  175. }
  176. // 节流函数 制定间隔执行
  177. export function throttle<F extends Procedure> (
  178. fn: F,
  179. wait: number = 100,
  180. options: Options = {}
  181. ) {
  182. let timeout
  183. let context
  184. let args
  185. let result
  186. let previous: number = 0
  187. const later = function () {
  188. previous = options.leading === false ? 0 : Date.now()
  189. timeout = null
  190. result = fn.apply(context, args)
  191. if (!timeout) context = args = null
  192. }
  193. const throttled: any = function (this: any) {
  194. const now = Date.now()
  195. if (!previous && options.leading === false) previous = now
  196. const remaining = wait - (now - previous)
  197. context = this
  198. args = arguments
  199. if (remaining <= 0 || remaining > wait) {
  200. if (timeout) {
  201. clearTimeout(timeout)
  202. timeout = null
  203. }
  204. previous = now
  205. result = fn.apply(context, args)
  206. if (!timeout) context = args = null
  207. } else if (!timeout && options.trailing !== false) {
  208. timeout = setTimeout(later, remaining)
  209. }
  210. return result
  211. }
  212. throttled.cancel = function () {
  213. clearTimeout(timeout)
  214. previous = 0
  215. timeout = context = args = null
  216. }
  217. return throttled
  218. }
  219. // 防抖函数,到达指定时间间隔执行
  220. export function debounce<F extends Procedure>(
  221. func: F,
  222. waitMilliseconds = 50,
  223. isImmediate: boolean = false
  224. ): F {
  225. let timeoutId: number | undefined | any
  226. return function(this: any, ...args: any[]) {
  227. const context = this
  228. const doLater = function() {
  229. timeoutId = undefined
  230. if (!isImmediate) {
  231. func.apply(context, args)
  232. }
  233. }
  234. const shouldCallNow = isImmediate && timeoutId === undefined
  235. if (timeoutId !== undefined) {
  236. clearTimeout(timeoutId)
  237. }
  238. timeoutId = setTimeout(doLater, waitMilliseconds)
  239. if (shouldCallNow) {
  240. func.apply(context, args)
  241. }
  242. } as any
  243. }
  244. // transform.ts
  245. // 将 px 转化为 vw
  246. export const px2vw = (pixel, viewportWidth = 375, unitPrecision = 3, minPixelValue = 1): string => {
  247. if (pixel <= minPixelValue) return pixel
  248. return (pixel * 100 / viewportWidth).toFixed(unitPrecision) + 'vw'
  249. }
  250. // 转化为实际 px
  251. export const realPx = (pixel, viewportWidth = 375, unitPrecision = 3): number => {
  252. const screenWidth = window.screen.width
  253. return +((pixel * screenWidth / viewportWidth).toFixed(unitPrecision))
  254. }
  255. // 转化为目标 px
  256. export const targetPx = (pixel, viewportWidth = 375, unitPrecision = 3): number => {
  257. let screenWidth = window.screen.width
  258. return +((pixel * viewportWidth / screenWidth).toFixed(unitPrecision))
  259. }
  260. // ua.ts
  261. export function isAndroid () {
  262. const u = navigator.userAgent
  263. return u.indexOf('Android') > -1 || u.indexOf('Adr') > -1
  264. }
  265. export function isiOS () {
  266. const u = navigator.userAgent
  267. return !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/)
  268. }
  269. export function isIPX () {
  270. let screen = window.screen
  271. return isiOS() && screen.width === 375 && screen.height === 812
  272. }

7、样式文件

src/style/_base.styl

  1. @import './_var.styl'
  2. @import './_mixin.styl'
  3. @import './_transition.styl'
  4. // 使用 px 转 vm 功能的副作用 问题:content会引起部分浏览器下,图片不会显示 所以要加该样式
  5. img{
  6. content: initial !important;
  7. }
  8. // 清除浮动
  9. .u-clearfix
  10. clearfix()
  11. // 隐藏滚动条
  12. .u-hide-scroll
  13. hide-scrollbar()
  14. // iconfont
  15. .u-iconfont
  16. iconfont()
  17. // 1px 边框
  18. .u-border-1px
  19. border-1px(all)
  20. .u-border-bottom-1px
  21. border-1px(bottom)
  22. .u-border-top-1px
  23. border-1px(top)
  24. .u-border-left-1px
  25. border-1px(left)
  26. .u-border-right-1px
  27. border-1px(right)
  28. // 超过省略号
  29. .u-ellipsis
  30. ellipsis()
  31. .u-ellipsis-2
  32. ellipsis(2)
  33. .u-ellipsis-3
  34. ellipsis(3)

src/style/_mixin.styl

  1. @import './_var.styl'
  2. // 转化为 vw
  3. r(px)
  4. round(px / 375 * 100, 6) vw
  5. line-1px($dir = bottom, $color = #EBEBEB, $radius = 0, $style = solid)
  6. content " "
  7. pointer-events none
  8. box-sizing border-box
  9. position absolute
  10. transform-origin 0 0
  11. if $dir is bottom
  12. border-bottom 1px $style $color
  13. bottom 0
  14. left 0
  15. width 100%
  16. transform-origin: 0 bottom
  17. @media (min-resolution: 2dppx)
  18. width 200%
  19. transform scale(0.5)
  20. @media (min-resolution: 3dppx)
  21. width 300%
  22. transform scale(0.333)
  23. else if $dir is top
  24. border-top 1px $style $color
  25. top 0
  26. left 0
  27. width 100%
  28. transform-origin: 0 top
  29. @media (min-resolution: 2dppx)
  30. width 200%
  31. transform scale(0.5)
  32. @media (min-resolution: 3dppx)
  33. width 300%
  34. transform scale(0.333)
  35. else if $dir is left
  36. border-left 1px $style $color
  37. top 0
  38. left 0
  39. height 100%
  40. transform-origin: left 0
  41. @media (min-resolution: 2dppx)
  42. height 200%
  43. transform scale(0.5)
  44. @media (min-resolution: 3dppx)
  45. height 300%
  46. transform scale(0.333)
  47. else if $dir is right
  48. border-right 1px $style $color
  49. top 0
  50. right 0
  51. height 100%
  52. transform-origin: right 0
  53. @media (min-resolution: 2dppx)
  54. height 200%
  55. transform scale(0.5)
  56. @media (min-resolution: 3dppx)
  57. height 300%
  58. transform scale(0.333)
  59. else
  60. left 0
  61. top 0
  62. border 1px $style $color
  63. border-radius $radius
  64. width 100%
  65. height 100%
  66. @media (min-resolution: 2dppx)
  67. width 200%
  68. height 200%
  69. border-radius $radius * 2
  70. transform scale(0.5)
  71. @media (min-resolution: 3dppx)
  72. width 300%
  73. height 300%
  74. border-radius $radius * 3
  75. transform scale(0.333)
  76. border-1px($dir = bottom, $color = #EBEBEB, $radius = 0, $style = solid)
  77. position relative
  78. if $dir is all
  79. &::before
  80. line-1px($dir, $color, $radius, $style)
  81. else
  82. &::after
  83. line-1px($dir, $color, $radius, $style)
  84. // 清除浮动
  85. clearfix()
  86. &::after
  87. content " "
  88. display table
  89. clear both
  90. // 隐藏滚动条
  91. hide-scrollbar()
  92. &::-webkit-scrollbar
  93. width 0
  94. height 0
  95. // iconfont
  96. iconfont()
  97. width 1em
  98. height 1em
  99. vertical-align -0.15em
  100. fill currentColor
  101. overflow hidden
  102. // 超出省略号
  103. ellipsis(line = 1)
  104. if line is 1
  105. overflow hidden
  106. text-overflow ellipsis
  107. white-space nowrap
  108. else
  109. overflow hidden
  110. display -webkit-box
  111. -webkit-box-orient vertical
  112. -webkit-line-clamp $line
  113. // 点击高亮
  114. tap-color($color, $percent = 10%)
  115. background-color: $color
  116. &:active
  117. background-color: darken($color, $percent)

src/style/_transition.styl

  1. // fade 效果
  2. @keyframes fade-in
  3. 0%
  4. opacity 0
  5. 100%
  6. opacity 1
  7. @keyframes fade-out
  8. 0%
  9. opacity 1
  10. 100%
  11. opacity 0
  12. .fade-enter-active
  13. animation fade-in .3s
  14. .fade-leave-active
  15. animation fade-out .3s
  16. // slide 效果
  17. @keyframes slide-up-in
  18. 0%
  19. transform translate3d(0, 100%, 0)
  20. 100%
  21. transform translate3d(0, 0, 0)
  22. @keyframes slide-up-out
  23. 0%
  24. transform translate3d(0, 0, 0)
  25. 100%
  26. transform translate3d(0, 100%, 0)
  27. .slide-up-enter-active
  28. animation slide-up-in .3s
  29. .slide-up-leave-active
  30. animation slide-up-out .3s

src/style/var.styl

  1. // 全局变量
  2. @import './var/color.styl'
  3. @import './var/size.styl'
  4. // @import './var/env.styl'
  5. @import './theme/default.styl'

src/style/btn.styl

  1. @import './_var.styl'
  2. @import './_mixin.styl'
  3. .tn-btn-group
  4. padding $btn-group-padding
  5. & > .tn-btn
  6. margin-top $btn-marginTop
  7. margin-left $btn-marginLeft
  8. &:first-child
  9. margin-top 0
  10. margin-left 0
  11. .tn-btn
  12. text-align center
  13. position relative
  14. margin 0
  15. padding 0
  16. border none
  17. pointer-events auto
  18. user-select none
  19. outline none
  20. display inline-block
  21. box-sizing content-box
  22. white-space nowrap
  23. font-size: $btn-font
  24. border-radius $btn-radius
  25. color $btn-color-default
  26. &--large
  27. display block
  28. width 100%
  29. font-weight bold
  30. height $btn-height-large
  31. font-size $btn-font-large
  32. &--small
  33. min-width $btn-width-small
  34. height $btn-height-small
  35. padding $btn-padding
  36. &--mini
  37. min-width $btn-width-mini
  38. height $btn-height-mini
  39. font-size $btn-font-mini
  40. &--primary
  41. background-color $btn-bg-primary
  42. tap-color($btn-bg-primary, 10%)
  43. &--warning
  44. background-color $btn-bg-warning
  45. tap-color($btn-bg-warning, 10%)
  46. &--danger
  47. background-color $btn-bg-danger
  48. tap-color($btn-bg-danger, 10%)
  49. &--hollow
  50. background-color $btn-bg-hollow
  51. color $btn-color-hollow
  52. tap-color($btn-bg-hollow, 3%)
  53. border-1px(all, $btn-border-hollow, $btn-radius)
  54. &--circle
  55. border-radius $btn-radius-circle
  56. &--square
  57. border-radius 0
  58. &--disabled
  59. background $btn-bg-disabled
  60. color $btn-color-disabled

src/style/theme/default.styl

  1. // Theme Variables
  2. //======== 【Btn】 ========
  3. $btn-group-padding = 12px 9px
  4. $btn-bg-primary = #0082FF
  5. $btn-bg-warning = #FFB400
  6. $btn-bg-danger = #EF4F4F
  7. $btn-bg-hollow = #FFF
  8. $btn-bg-disabled = #CCC
  9. $btn-color-default = #FFF
  10. $btn-color-hollow = #626B77
  11. $btn-color-disabled = #F0F0F0
  12. $btn-border-hollow = #EBEBEB
  13. $btn-height-large = 40px
  14. $btn-width-small = 90px
  15. $btn-height-small = 30px
  16. $btn-width-mini = 40px
  17. $btn-height-mini = 22px
  18. $btn-font = 15px
  19. $btn-font-large = 17px
  20. $btn-font-mini = 13px
  21. $btn-radius = 4px
  22. $btn-radius-circle = 100px
  23. $btn-padding = 0 5px
  24. $btn-marginTop = 12px
  25. $btn-marginLeft = 9px
  26. // ======== 【DateTime】 ========
  27. $datetime-content-height = 280px
  28. $datetime-item-height = 40px
  29. $datetime-item-font = 17px
  30. $datetime-item-font-active = 22px
  31. // ======== 【SelectPicker】 ========
  32. $selectpicker-content-height = 200px
  33. $selectpicker-item-height = 40px
  34. $selectpicker-item-font = 17px
  35. $selectpicker-item-font-active = 24px

src/style/var/color.styl

  1. // 主题颜色
  2. $color-blue = #0082FF
  3. $color-green = #3BBD5E
  4. $color-black = #282828
  5. $color-orange = #FFA43D
  6. $color-red = #EA4335
  7. // 辅助颜色
  8. $color-fail = #440011
  9. // $color-success:
  10. // $color-warning:
  11. // $color-info:
  12. // 文本颜色
  13. $color-main-text = #333
  14. $color-sub-text = #626B77
  15. $color-desc-text = #999FAA
  16. // 线条颜色
  17. $color-border = #EBEBEB
  18. // 背景颜色
  19. $color-bg-page = #F6F6F9

src/style/var/size.styl

  1. // 文本尺寸
  2. $size-main-text = 17px
  3. $size-sub-text = 15px
  4. $size-desc-text = 13px

8、开发验证example

src/example/index.html

  1. <!DOCTYPE HTML>
  2. <html>
  3. <head>
  4. <meta charset="utf-8">
  5. <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"/>
  6. <meta name="spm-id" content="{{$zebra.spma}}.{{$zebra.spmb}}">
  7. <meta name="aplus-terminal" content="1"/>
  8. <title>vue h5 组件</title>
  9. <link rel="stylesheet" href="//g.alicdn.com/fish/polyfill/0.0.2/reset.css">
  10. </head>
  11. <body>
  12. <div id="app"></div>
  13. <script src="//g.alicdn.com/fish/console/index.js"></script>
  14. <script src="//g.alicdn.com/fish/polyfill/0.0.1/core.min.js"></script>
  15. </html>

src/example/main.ts

  1. import Vue from 'vue'
  2. import Example from './Example.vue'
  3. import router from './router'
  4. import store from './store'
  5. const app = new Vue({
  6. el: '#app',
  7. router,
  8. store,
  9. render: h => h(Example)
  10. })

src/example/router.ts

  1. import Vue from 'vue'
  2. import Router from 'vue-router'
  3. import Home from './pages/Home.vue'
  4. /**
  5. * Layout 布局组件
  6. */
  7. import Btn from './pages/Btn.vue'
  8. Vue.use(Router)
  9. export const routes = [
  10. // Tunas
  11. {
  12. path: '/',
  13. component: Home,
  14. meta: {
  15. title: 'Home'
  16. }
  17. },
  18. // 布局
  19. {
  20. path: '/Btn',
  21. component: Btn,
  22. meta: {
  23. title: 'Btn',
  24. group: 'Layout',
  25. }
  26. }
  27. ]
  28. export default new Router({
  29. mode: 'hash',
  30. routes
  31. })

src/example/store.ts

  1. import Vue from 'vue'
  2. import Vuex from 'vuex'
  3. Vue.use(Vuex)
  4. /* eslint-disable no-new */
  5. export default new Vuex.Store({
  6. state: {
  7. status: 100, // 设备状态 0 - 未激活, 1 - 在线, 3 - 离线, 8 - 禁用,
  8. check: false
  9. },
  10. mutations: { // 更改 Vuex 的 store 中的状态的唯一方法是提交 mutation, 只能同步
  11. /**
  12. * 更新状态树中的属性集合
  13. * @param {*} attr 要更新的属性集合
  14. */
  15. updateState(state, data) {
  16. data.find((key) => {
  17. state[key[0]] = key[1]
  18. });
  19. }
  20. },
  21. })

src/example/Example.vue

  1. <template>
  2. <div class="example" :class="{'example__menu--show': showMenu}">
  3. <div class="example__topbar">
  4. <button class="example__topbar-icon" @click="showMenu=true"></button>
  5. </div>
  6. <div class="example__menu">
  7. <div class="example__search">
  8. <input type="text" class="example__search-input" v-model="query">
  9. </div>
  10. <ul class="example__menulist">
  11. <li v-for="(o, i) of list" :key="i">
  12. <h2>{{o.title}}</h2>
  13. <ul>
  14. <li v-for="(p, j) of o.children" :key="j">
  15. <router-link :to="p.path" v-html="highlight(p.meta.title)" @click.native="showMenu = false"></router-link>
  16. </li>
  17. </ul>
  18. </li>
  19. </ul>
  20. </div>
  21. <div class="example__view" :class="{'example__view--scroll': $route.meta.scroll}" :style="{padding: 0}">
  22. <router-view></router-view>
  23. </div>
  24. </div>
  25. </template>
  26. <script lang="ts">
  27. import Vue from 'vue'
  28. import { walk } from 'jsonuri'
  29. import { escapeHtml, outclick } from '../packages/Utils'
  30. import router, { routes } from './router'
  31. function formatRoutes (routes) {
  32. let ret: any[] = []
  33. const keys: string[] = routes.map(r => r.meta.group || 'RockH5')
  34. const groups: string[] = keys.filter((v, i, _) => _.indexOf(v) === i)
  35. groups.forEach(g => {
  36. let children: any[] = []
  37. if (g === 'RockH5') {
  38. children = routes.filter(r => !r.meta.group)
  39. } else {
  40. children = routes.filter(r => r.meta.group === g)
  41. }
  42. ret.push({
  43. title: g,
  44. children
  45. })
  46. })
  47. return ret
  48. }
  49. export default Vue.extend({
  50. data () {
  51. return {
  52. query: '',
  53. showMenu: false,
  54. list: formatRoutes(routes)
  55. }
  56. },
  57. computed: {
  58. bgColor(): any {
  59. return {
  60. backgroundColor: this.$route.meta.bgColor || '#fff'
  61. }
  62. }
  63. },
  64. created () {
  65. outclick('.example__topbar-icon, .example__menu', () => {
  66. this.showMenu = false
  67. })
  68. },
  69. watch: {
  70. query (qs) {
  71. let list = JSON.parse(JSON.stringify(this.list))
  72. walk(list, () => {
  73. })
  74. // this.list = this.list.filter(p => p.children.filter(p => p.title === qs).length > 0)
  75. }
  76. },
  77. methods: {
  78. highlight (str) {
  79. let query = this.query.trim()
  80. if (!query) return str
  81. let reg = new RegExp(`(${query})`, 'ig')
  82. let ret = str.replace(reg, ($0, $1) => `<em class="example--highlight">${escapeHtml($1)}</em>`)
  83. return ret
  84. }
  85. }
  86. })
  87. </script>
  88. <style lang="stylus">
  89. .example {
  90. max-width 100vw
  91. overflow hidden
  92. a {
  93. text-decoration none
  94. color #34495e
  95. &.router-link-exact-active {
  96. color #008EE9
  97. }
  98. }
  99. ul, li {
  100. list-style none
  101. }
  102. &--highlight {
  103. background yellow
  104. }
  105. &__topbar {
  106. height 50px
  107. display flex
  108. align-items center
  109. }
  110. &__topbar-icon {
  111. border none
  112. outline none
  113. margin-left 10px
  114. background url('//gw.alicdn.com/tfs/TB1g0gVrntYBeNjy1XdXXXXyVXa-48-48.png') no-repeat
  115. background-size cover
  116. width 24px
  117. height 24px
  118. }
  119. &__menu {
  120. transition all 0.2s
  121. transform translate3d(-100%, 0, 0)
  122. padding-top 30px
  123. box-shadow 0 0 10px rgba(0,0,0,0.2)
  124. box-sizing border-box
  125. height 100vh
  126. width 50%
  127. background #f9f9f9
  128. position fixed
  129. top 0
  130. left 0
  131. overflow auto
  132. }
  133. &__menulist {
  134. padding-left 1.5em
  135. margin-top 15px
  136. li {
  137. margin-top 0.5em
  138. }
  139. ul {
  140. padding-left 1.5em
  141. }
  142. }
  143. &__search {
  144. height 30px
  145. line-height 30px
  146. box-sizing border-box
  147. padding 0 15px 0 30px
  148. border 1px solid #e3e3e3
  149. color #2c3e50
  150. outline none
  151. border-radius 15px
  152. margin 0 10px
  153. transition border-color 0.2s ease
  154. background: #fff url('//gw.alicdn.com/tfs/TB1hn5OtbSYBuNjSspiXXXNzpXa-96-96.png') 8px 5px no-repeat
  155. background-size: 20px
  156. vertical-align: middle !important
  157. }
  158. &__search-input {
  159. width 100%
  160. height 100%
  161. outline none
  162. background none
  163. border none
  164. }
  165. &__view {
  166. box-sizing border-box
  167. transition all 0.2s
  168. transform translate3d(0, 0, 0)
  169. background #fff
  170. min-height calc(100vh - 50px)
  171. overflow-x hidden
  172. padding 0 15px
  173. &--full {
  174. margin-left 0
  175. }
  176. &--scroll {
  177. // height 120vh
  178. }
  179. }
  180. // 状态
  181. &__menu--show {
  182. .example__menu {
  183. transform translate3d(0, 0, 0)
  184. }
  185. .example__view {
  186. padding: 0 @important;
  187. transform translate3d(50%, 0, 0)
  188. }
  189. }
  190. }
  191. </style>

src/example/pages/Btn.vue

  1. <template>
  2. <div class="btn">
  3. <btn-group>
  4. <btn size="mini">mini</btn>
  5. <btn size="mini" type="warning">mini</btn>
  6. <btn size="mini" type="danger">mini</btn>
  7. <btn size="mini" type="hollow">mini</btn>
  8. </btn-group>
  9. <btn-group>
  10. <btn size="small">small</btn>
  11. <btn size="small" type="warning">small</btn>
  12. <btn size="small" type="danger">small</btn>
  13. </btn-group>
  14. <btn-group>
  15. <btn size="small" type="hollow">small</btn>
  16. </btn-group>
  17. <btn shape="circle">shape="circle"</btn>
  18. <btn shape="square" type="warning">shape="square"</btn>
  19. <btn shape="round" type="danger">shape="round"</btn>
  20. <btn type="hollow">shape="round"</btn>
  21. <btn disabled>disabled</btn>
  22. </div>
  23. </template>
  24. <script lang="ts">
  25. import Vue from 'vue'
  26. import { BtnGroup, Btn } from '../../rockh5'
  27. export default Vue.extend({
  28. components: {
  29. BtnGroup, Btn
  30. }
  31. })
  32. </script>
  33. <style lang="stylus" scoped>
  34. </style>

src/example/pages/Home.vue

  1. <template>
  2. <div class="wrap">Hello, VueUI!</div>
  3. </template>
  4. <style lang="stylus" scoped>
  5. .wrap {
  6. margin-top 250px
  7. text-align center
  8. font-size 36px
  9. }
  10. </style>
  11. <script lang="ts">
  12. import Vue from 'vue'
  13. export default Vue.extend({
  14. created () {
  15. },
  16. mounted () {
  17. }
  18. })
  19. </script>