zoukankan      html  css  js  c++  java
  • 手把手做一个基于vue-cli的组件库(下篇)

    基于vue-cli4的ui组件库,上篇:如何做一个初步的组件。下篇:编写说明文档及页面优化。接上篇,开工。
    GitHub源码地址:https://github.com/sq-github/sq-ui
    GitHub预览地址:https://sq-github.github.io/sq-ui/dist
    五、添加markdown说明文本
    1、删除app.vue原有的app.vue内容,及其他一些项目创建时引用的不需要的组件。修改后app.vue如下:
    <template>
      <div id="app">
        <router-view />
      </div>
    </template>

    2、因为文档是用markdown写的,需要项目能识别markdown组件。

    npm i vue-markdown-loader -D
    3、修改vue.config.js 需要能识别md文件。
    const path = require('path')
    module.exports = {
      // 修改 pages 入口
      pages: {
        index: {
          entry: 'examples/main.js', // 入口
          template: 'public/index.html', // 模板
          filename: 'index.html' // 输出文件
        }
      },
      parallel: false,
      // 扩展 webpack 配置
      chainWebpack: config => {
        // @ 默认指向 src 目录,这里要改成 examples
        // 另外也可以新增一个 ~ 指向 packages
        config.resolve.alias
          .set('@', path.resolve('examples'))
          .set('~', path.resolve('packages'))
    
        // 把 packages 和 examples 加入编译,因为新增的文件默认是不被 webpack 处理的
        config.module
          .rule('js')
          .include.add(/packages/)
          .end()
          .include.add(/examples/)
          .end()
          .use('babel')
          .loader('babel-loader')
          .tap(options => {
            // 修改它的选项...
            return options
          })
        config.module
          .rule('md')
          .test(/.md/)
          .use('vue-loader')
          .loader('vue-loader')
          .end()
          .use('vue-markdown-loader')
          .loader('vue-markdown-loader/lib/markdown-compiler')
          .options({
            raw: true
          })
      }
    }

    4、创建文档的目录及文件。

    在 examples 目录下创建 docs 文件夹,在docs文件夹下创建 test1.md,文件内容如下

    ## tip
    
    :::tip
    这是一个 tip。这是一个 tip。这是一个 tip。这是一个 tip。这是一个 tip。这是一个 tip。
    :::
    
    ## warning
    
    :::warning
    这是一个 warning,**这是一个 warning,**。
    这是一个 warning。
    这是一个 warning,这是一个 warning,这是一个 warning,这是一个 warning。
    :::
    
    ## demo
    
    :::demo 这是一个 demo。这是一个 demo。这是一个 demo。这是一个 demo。这是一个 demo。这是一个 demo。这是一个 demo。这是一个 demo。
    
    ```html
    <sq-button></sq-button>
    ```
    
    :::

    将 test1.md 添加进路由进行测试

    在router/index.js中添加

     {
        path: '/test1',
        name: 'test1',
        component: () => import(/* webpackChunkName: "about" */ '../docs/test1.md')
      }

    重新运行测试:如果没有报错,页面能正确显示 test1.md 内的文本,这一步就算成功了。

    5、接下来安装其他的markdown插件
    mpm i markdown-it markdown-it-container -S
    6、再次修改vue.config.js文件
    const path = require('path')
    const md = require('markdown-it')() // 引入markdown-it
    module.exports = {
      // 修改 pages 入口
      pages: {
        index: {
          entry: 'examples/main.js', // 入口
          template: 'public/index.html', // 模板
          filename: 'index.html' // 输出文件
        }
      },
      parallel: false,
      // 扩展 webpack 配置
      chainWebpack: config => {
        // @ 默认指向 src 目录,这里要改成 examples
        // 另外也可以新增一个 ~ 指向 packages
        config.resolve.alias
          .set('@', path.resolve('examples'))
          .set('~', path.resolve('packages'))
    
        // 把 packages 和 examples 加入编译,因为新增的文件默认是不被 webpack 处理的
        config.module
          .rule('js')
          .include.add(/packages/)
          .end()
          .include.add(/examples/)
          .end()
          .use('babel')
          .loader('babel-loader')
          .tap(options => {
            // 修改它的选项...
            return options
          })
        config.module
          .rule('md')
          .test(/.md/)
          .use('vue-loader')
          .loader('vue-loader')
          .end()
          .use('vue-markdown-loader')
          .loader('vue-markdown-loader/lib/markdown-compiler')
          .options({
            raw: true,
            preventExtract: true, // 这个加载器将自动从html令牌内容中提取脚本和样式标签
            // 定义处理规则
            preprocess: (MarkdownIt, source) => {
              // 对于代码块去除v - pre, 添加高亮样式;
              const defaultRender = md.renderer.rules.fence
              MarkdownIt.renderer.rules.fence = (
                tokens,
                idx,
                options,
                env,
                self
              ) => {
                const token = tokens[idx]
                // 判断该 fence 是否在 :::demo 内
                const prevToken = tokens[idx - 1]
                const isInDemoContainer =
                  prevToken &&
                  prevToken.nesting === 1 &&
                  prevToken.info.trim().match(/^demos*(.*)$/)
                if (token.info === 'html' && isInDemoContainer) {
                  return `<template slot="highlight"><pre v-pre><code class="html">${md.utils.escapeHtml(
                    token.content
                  )}</code></pre></template>`
                }
                return defaultRender(tokens, idx, options, env, self)
              }
              return source
            },
            use: [
              // :::demo ****
              //
              // :::
              // 匹配:::后面的内容 nesting == 1,说明:::demo 后面有内容
              // m为数组,m[1]表示 ****
              [
                require('markdown-it-container'),
                'demo',
                {
                  validate: function(params) {
                    return params.trim().match(/^demos*(.*)$/)
                  },
    
                  render: function(tokens, idx) {
                    const m = tokens[idx].info.trim().match(/^demos*(.*)$/)
                    if (tokens[idx].nesting === 1) {
                      //
                      const description = m && m.length > 1 ? m[1] : '' // 获取正则捕获组中的描述内容,即::: demo xxx中的xxx
                      const content =
                        tokens[idx + 1].type === 'fence'
                          ? tokens[idx + 1].content
                          : ''
    
                      return `<demo-block>
                      <div slot="source">${content}</div>
                      ${description ? `<div>${md.render(description)}</div>` : ''}
                      `
                    }
                    return '</demo-block>'
                  }
                }
              ],
              [require('markdown-it-container'), 'tip'],
              [require('markdown-it-container'), 'warning']
            ]
          })
      }
    }
    7、重新运行 会有一个报错 说没有<demo-block>组件,接下来只需要添加这个组件即可。在 examples/components 下添加 DemoBlock.vue,内容如下:
    <template>
      <div class="demo-block" :class="blockClass">
        <!-- 源码运行 -->
        <div class="source">
          <slot name="source"></slot>
        </div>
        <!-- 源码 -->
        <div class="meta" ref="meta">
          <!-- 描述 -->
          <div class="description" v-if="$slots.default">
            <slot></slot>
          </div>
          <!-- 源码 -->
          <div class="highlight">
            <slot name="highlight"></slot>
          </div>
        </div>
        <!-- 源码 显示或者隐藏 -->
        <div
          class="demo-block-control"
          ref="control"
          :class="{ 'is-fixed': fixedControl }"
          @click="isExpanded = !isExpanded"
        >
          <transition name="text-slide">
            <span>{{ controlText }}</span>
          </transition>
        </div>
      </div>
    </template>
    <script>
    export default {
      data() {
        return {
          isExpanded: false,
          fixedControl: false,
          scrollParent: null
        }
      },
    
      computed: {
        lang() {
          return this.$route.path.split('/')[1]
        },
    
        blockClass() {
          return `demo-${this.lang} demo-${this.$router.currentRoute.path
            .split('/')
            .pop()}`
        },
        controlText() {
          return this.isExpanded ? '隐藏代码' : '显示代码'
        },
        codeArea() {
          return this.$el.getElementsByClassName('meta')[0]
        },
        codeAreaHeight() {
          if (this.$el.getElementsByClassName('description').length > 0) {
            return (
              this.$el.getElementsByClassName('description')[0].clientHeight +
              this.$el.getElementsByClassName('highlight')[0].clientHeight +
              20
            )
          }
          return this.$el.getElementsByClassName('highlight')[0].clientHeight
        }
      },
      watch: {
        isExpanded(val) {
          this.codeArea.style.height = val ? `${this.codeAreaHeight + 1}px` : '0'
          console.log(this.$el.getElementsByClassName('description').length)
          console.log(this.$el.getElementsByClassName('highlight'))
          console.log(this.codeAreaHeight)
          if (!val) {
            this.fixedControl = false
            this.$refs.control.style.left = '0'
          }
        }
      }
    }
    </script>
    <style lang="scss">
    .demo-block {
       60%;
      padding: 8px 16px;
      margin: auto;
      margin-top: 10px;
      border-left: solid 5px#fc297f;
      background-color: #f8d1db;
      border-radius: 3px;
      transition: 0.2s;
      &.hover {
        box-shadow: 0 0 8px 0 rgba(232, 237, 250, 0.6),
          0 2px 4px 0 rgba(232, 237, 250, 0.5);
      }
      .meta {
        margin-top: 10px;
        background-color: #fafafa;
        border-radius: 8px;
        overflow: hidden;
        height: 0;
        transition: height 0.2s;
      }
    
      .description {
        box-sizing: border-box;
        border-radius: 3px;
        font-size: 14px;
        line-height: 22px;
        color: #666;
        word-break: break-word;
        margin: 10px;
        background-color: #fff;
        p {
           100%;
        }
      }
      .demo-block-control {
        border-top: solid 1px #eaeefb;
        height: 44px;
        box-sizing: border-box;
        background-color: #fff;
        border-radius: 8px;
        text-align: center;
        margin-top: 10px;
        color: #d3dce6;
        cursor: pointer;
        position: relative;
    
        &.is-fixed {
          position: fixed;
          bottom: 0;
           868px;
        }
    
        i {
          font-size: 16px;
          line-height: 44px;
          transition: 0.3s;
          &.hovering {
            transform: translateX(-40px);
          }
        }
    
        > span {
          position: absolute;
          transform: translateX(-30px);
          font-size: 14px;
          line-height: 44px;
          transition: 0.3s;
          display: inline-block;
        }
    
        &:hover {
          color: #fc297f;
          background-color: #f9fafc;
        }
    
        & .text-slide-enter,
        & .text-slide-leave-active {
          opacity: 0;
          transform: translateX(10px);
        }
    
        .control-button {
          line-height: 26px;
          position: absolute;
          top: 0;
          right: 0;
          font-size: 14px;
          padding-left: 5px;
          padding-right: 25px;
        }
      }
    }
    </style>
    然后在main.js中引用组件
    import DemoBlock from './components/DemoBlock.vue'
    Vue.component('DemoBlock', DemoBlock)
    8、现在应该能看到demo组件的效果了,后面接下来需要添加tip和warning的样式
    添加一个公共scss文件,在assets下新建common.scss文件
    html,
    body {
      margin: 0;
      padding: 0;
      height: 100%;
      background-color: #17171d;
      font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB',
        'Microsoft YaHei', SimSun, sans-serif;
      font-weight: 400;
      -webkit-font-smoothing: antialiased;
      -webkit-tap-highlight-color: transparent;
    
      &.is-component {
        overflow: hidden;
      }
    }
    
    #app {
      height: 100%;
      &.is-component {
        overflow-y: hidden;
        .main-cnt {
          padding: 0;
          margin-top: 0;
          height: 100%;
          min-height: auto;
        }
        .headerWrapper {
          position: fixed;
           100%;
          left: 0;
          top: 0;
          z-index: 1500;
          .container {
            padding: 0;
          }
        }
      }
    }
    
    a {
      color: #409eff;
      text-decoration: none;
    }
    
    code {
      padding: 0 4px;
      border: 1px solid #eaeefb;
      border-radius: 4px;
    }
    
    button,
    input,
    select,
    textarea {
      font-family: inherit;
      font-size: inherit;
      line-height: inherit;
      color: inherit;
    }
    
    .hljs {
      line-height: 20px;
      font-family: Menlo, Monaco, Consolas, Courier, monospace;
      font-size: 12px;
      padding: 10px 24px 18px 24px;
      border: solid 1px #eaeefb;
      border-radius: 4px;
      -webkit-font-smoothing: auto;
    }
    
    .main-cnt {
      margin-top: -80px;
      padding: 80px 0 340px;
      box-sizing: border-box;
      min-height: 100%;
    }
    
    #app {
      h2 {
        font-size: 28px;
        color: #fc297f;
        margin: 0;
      }
      h3 {
        font-size: 22px;
      }
      h2,
      h3,
      h4,
      h5 {
         60%;
        margin: auto;
        margin-top: 10px;
        font-weight: normal;
        color: #fc297f;
        a {
          display: none;
        }
        &:hover a {
          opacity: 0.4;
        }
      }
    
      p {
         60%;
        margin: auto;
        padding: 10px;
        font-size: 16px;
        color: #d3aec2;
        line-height: 30px;
      }
    
      .tip {
         60%;
        margin: auto;
        margin-top: 10px;
        padding: 8px 16px;
        background-color: #ecf8ff;
        border-radius: 4px;
        border-left: #1b9ae4 5px solid;
        p {
           100%;
        }
        code {
          background-color: rgb(255, 255, 255);
          color: #445368;
        }
      }
    
      .warning {
         60%;
        margin: auto;
        margin-top: 10px;
        padding: 8px 16px;
        background-color: #fff6f7;
        border-radius: 4px;
        border-left: rgb(252, 122, 2) 5px solid;
        p {
           100%;
        }
        code {
          background-color: rgba(255, 255, 255, 0.7);
          color: #445368;
        }
      }
    }
    @media (max- 1140px) {
      .container,
      .page-container {
         100%;
      }
    }
    
    @media (max- 768px) {
      .container,
      .page-container {
        padding: 0 20px;
      }
    
      #app.is-component .headerWrapper .container {
        padding: 0 12px;
      }
    }
    在main.js引入
    import './assets/common.scss' // 公共样式
    9、现在效果应该都出来了,可以给代码添加高亮,使其更漂亮。
    npm i highlight.js -S
    再在main.js中添加如下配置,然后代码就能语法高亮了,perfact!
    import hljs from 'highlight.js'
    import 'highlight.js/styles/monokai-sublime.css'
    
    router.afterEach(() => {
      Vue.nextTick(() => {
        const blocks = document.querySelectorAll('pre code:not(.hljs)')
        Array.prototype.forEach.call(blocks, hljs.highlightBlock)
      })
    })
    10、最后有个小问题,如果有eslint检查的话,在md文件中添加vue模板文件时会报错,比如下面这种:
    :::demo 这里贴出的是源码,刷新可重播。
    
    ```html
    <template>
      <div>
        <div>测试</div>
      </div>
    </template>
    
    <script></script>
    <style></style>
    ```
    
    :::
    解决方法是:在跟目录添加一个.eslintignore文件,目录和内容如下:
    *.sh
    node_modules
    lib
    coverage
    *.md
    *.scss
    *.woff
    *.ttf
    aui-web
    build
    六、现在说明文件格式已经弄好了,类似下面这种效果,最后一步就是将路由和左侧的导航菜单弄好。
    1、添加路由配置文件routerCon.json
    [
      {
        "name": "test",
        "groups": [
          {
            "groupName": "测试组件",
            "list": [
              {
                "path": "/test1",
                "title": "测试1"
              },
              {
                "path": "/test2",
                "title": "测试2"
              }
            ]
          }
        ]
      }
    ]
    2、修改路由的index.js文件,倒数第二行的路由重定向 redirect ,可以自己定义。
    // export default router
    import Vue from 'vue'
    import Router from 'vue-router'
    
    import navConfig from './routerCon'
    
    Vue.use(Router)
    const docsRoutefun = navConfig => {
      const route = []
      navConfig.forEach(item => {
        if (item.groups) {
          item.groups.forEach(group => {
            group.list.forEach(nav => {
              route.push({
                path: nav.path,
                name: nav.name,
                component: r =>
                  require.ensure([], () => r(require(`@/docs${nav.path}.md`)))
              })
            })
          })
        } else {
          route.push({
            path: item.path,
            name: item.name,
            component: r =>
              require.ensure([], () => r(require(`@/docs${item.path}.md`)))
          })
        }
      })
      return route
    }
    const docsRoute = docsRoutefun(navConfig)
    export default new Router({
      mode: 'history',
      base: process.env.BASE_URL,
      routes: [{ path: '/', redirect: '/test1' }, ...docsRoute]
    })
    3、添加左侧菜单组件menuCom.vue
    <template>
      <div id="app">
        <div class="main">
          <!-- sidebar -->
          <div class="sidebar">
            <menuCom :data="navsData"></menuCom>
          </div>
          <div class="view page-container">
            <router-view></router-view>
          </div>
        </div>
      </div>
    </template>
    <script>
    import menuCom from './components/menuCom'
    import navsData from './router/routerCon.json'
    export default {
      components: {
        menuCom
      },
      data() {
        return {
          navsData
        }
      }
    }
    </script>
    <style lang="scss">
    html,
    body {
      margin: 0;
      height: 100%;
      background-color: #17171d;
    }
    .header {
      top: 0;
      height: 60px;
      background-color: #121217;
    }
    .footer {
      position: absolute;
      height: 60px;
      background-color: antiquewhite;
       100%;
    }
    .footer {
      bottom: 0;
    }
    .main {
      background-color: #17171d;
      position: absolute;
      bottom: 0;
      top: 60px;
       100%;
      overflow: hidden;
    }
    .sidebar,
    .view {
      overflow: auto;
    }
    .sidebar {
      float: left;
      height: 100%;
       200px;
      padding: 10px 0 10px 0;
      border-right: #000000 3px solid;
    }
    .view {
      padding: 0 0 80px 0;
      float: left;
      height: calc(100% - 50px);
       calc(100% - 203px);
      overflow: auto;
    }
    </style>
    最后:赶紧npm run lib 加 npm publish,引用看看效果吧,别忘了修改发布版本哟。
    总算码完了,期间看了一些博文和源码,有些文章不太完整,踩了一些坑。现在自己从头总结,感觉算是尽力在这篇中将详细的步骤和源码贴出来了,主要是想分享交流,互相避坑,如有不足,希望大家交流指正。
    如果需要完成开头图片那种效果,页头布局、logo以及其他的组件都放在github的源码里面了。如果觉得还有趣,不妨star一下,十分感谢。
    参考项目链接:
    https://github.com/xiaolannuoyi/yuan-ui
    https://segmentfault.com/a/1190000018310478
    https://blog.csdn.net/qq_31126175/article/details/100527322?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522158788190919725247652639%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.57644%2522%257D&request_id=158788190919725247652639&biz_id=0&utm_source=distribute.pc_search_result.none-task-blog-2~all~first_rank_v2~rank_v25-7
    伸手摘星,即使徒劳无功也不致满手污泥。
  • 相关阅读:
    HTTP 协议中的并发限制及队首阻塞问题
    聊聊JMM
    聊聊CacheLine
    git解决本地建立git仓库 连接远程git仓库出现拒绝合并问题
    django 本地项目部署uwsgi 以及云服务器部署 uwsgi+Nginx+Docker+MySQL主从
    我的第一篇播客
    大爷的超市管理系统——冲刺第一天
    能混绝不C——凡事预则立
    2020软件工程团队作业——05
    2020软件工程作业——04
  • 原文地址:https://www.cnblogs.com/sq-blogs/p/12822328.html
Copyright © 2011-2022 走看看