npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

qing-ui-2

v0.0.1

Published

克隆此仓库后运行:

Downloads

0

Readme

UI 组件库(vue2.0)

启动项目

克隆此仓库后运行:

# 安装依赖,推荐使用 yarn
$ npm install

# 打包组件库
$ npm run lib

# 启动测试用例
$ npm run servec

# 打包测试用例
$ npm run build

组件库目录结构

参考 ElementUI 目录结构
vue2.0github 源码地址
vue3.0github 源码地址

├── docs                       // 文档目录
├── examples                   // 测试示例
├── packages                   // 封装的所有组件
│   ├── styles                 // 全局样式
│   └── index.js               // 导出组件配置
├── .gitignore                 // git 忽略项
├── .prettierrc                // 代码格式化
├── babel.config.js            // babel配置
├── vue.config.js              // vue脚手架配置
├── README.md                  // 简介
└── package.json               // package.json

技术栈选型

  • 脚手架工具: @vue/cli
  • 框架:vue2
  • 语法:js
  • CSS 语法 :scss

搭建组件库工程(搭建过程)

1. vue create 组件名包名称

上/下箭头移动光标,Enter 确认,空格 选择/取消选择

选择 Manually select features -> 选择 babel scss 进行安装

2. 调整目录(根目录)

1)新增packages文件夹(存在封装的所有组件,打包也是对此文件进行打包)

2)src 文件夹更改为examples(存放测试用例),更改完是跑不起来的,需配置。

项目根目录新建 vue.config.js

const path = require('path')

module.exports = {
  pages: {
    index: {
      entry: 'examples/main.js', // 修改项目的入口文件
      template: 'public/index.html',
      filename: 'index.html'
    }
  },
  // 扩展webpack 配置,使packages加入编译(高版本语法转低版本语法)
  chainWebpack: (config) => {
    config.module
      .rule('js')
      .include.add(path.resolve(__dirname, 'packages'))
      .end()
      .use('babel')
      .loader('babel-loader')
      .tap((options) => {
        return options
      })
  }
}

3. 导出 vue 插件

packages目录下新建index.js,完整代码如下,其中引入的文件暂时不存在,样式文件不存在可暂时不引入

// 引入全局样式
import './styles/index.scss'

// 基础组件
import Button from './button/index'

// 存储组件列表
const components = [Button]

// 定义 install 方法,接口Vue作为参数。如果使用 use 注册插件,则所有的组件都会注册
const install = function (Vue) {
  // 全局注册所有的组件
  components.forEach((item) => {
    Vue.component(item.name, item)
  })
}

// 判断是否是直接引入文件,如果是就不用再调Vue.use(),像<script>直接使用也可以注册。
if (typeof window !== 'undefined' && window.Vue) {
  install(window.Vue)
}

export default { install }

1) 做 vue 插件,需要了解install

用法:安装 Vue.js 插件。如果插件是一个对象,必须提供 install 方法。如果插件是一个函数,它会被作为 install 方法。install 方法调用时,会将 Vue 作为参数传入。 该方法需要在调用 new Vue() 之前被调用。 当 install 方法被同一个插件多次调用,插件将只会被安装一次。

  • 也就是说如果要做一个插件,只需要导出 install 就好了.(这里导出 install 一个函数)

2) packages 目录新建 index.js(整个包的入口文件,一定要导出一个 install)

没有优化的 index.js

// index.js 这样的坏处是要一个一个注册。所以要数组存一下(也就是下面的components数组),在循环取出注册
import Button from './button/button.vue'

// 定义 install 方法,接口Vue作为参数。如果使用 use 注册插件,则所有的组件都会注册
const install = function (Vue) {
  // 全局注册所有的组件
  Vue.component(Button.name, Button)
}

export default install

优化后的 index.js

import Button from './button/button.vue'

// 存储组件列表
const components = [Button]

// 定义 install 方法,接口Vue作为参数。如果使用 use 注册插件,则所有的组件都会注册
const install = function (Vue) {
  // 全局注册所有的组件
  components.forEach((item) => {
    Vue.component(item.name, item)
  })
}

export default install

3) 如果不是模块化环境开发,像<script>,这些组件直接进行注册。就不用在调 use 方法。

// 判断是否是直接引入文件,如果是就不用再调Vue.use(),像<script>直接使用也可以注册。
if (typeof window !== 'undefined' && window.Vue) {
  install(window.Vue)
}

5. packages 目录下新增 button 文件夹

参考目录如下:

├── packages                   
│   ├── button
|   |   ├── src
|   |   |   ├──button.vue            
│   └── index.js               

参考内容如下:

  // packages/index.js
  import Button from './src/button'

  export default Button
  // packages/button/src/button.vue
  <template>
    <button>
      <slot></slot>
    </button>
  </template>

  <script>
  export default {
    name: 'demo-button'
  }
  </script>

4. 打包(packages 打包)

需要了解:vue cli 有一个构建目标成

// 项目根目录 package.json "scripts" 下增加 "lib" 命令

{
  "scripts": {
    // "lib": "vue-cli-service build --target lib 打包路径"
    "lib": "vue-cli-service build --target lib packages/index.js"
  }
}

搭建组件库文档(搭建过程)

1. 配置文档结构

// 1. 根目录新增docs文件夹(文档目录)

// 2. 使用你喜欢的包管理器进行初始化
npm init -y

// 3.将 VuePress 安装为本地依赖
npm install -D vuepress

// 4. 将 命令配置为启动项,docs/packages.json
"scripts": {
  "serve": "vuepress dev",
  "build": "vuepress build"
},

// 4. 调整文件夹(此步配置完 就可以npm run serve查看基础文档了)
├── docs                       // 文档库
│   ├── .vuepress              // vuepress配置路径(名字不能更换,注意大小写)
|   |   ├── components         // 组件文件夹
|   |   |   ├── button         // button组件示例文件夹
|   |   |   |   ├── base.vue   // button组件示例
|   |   |   ├── demo-block.vue // button组件示例
|   |   ├── config.js          // vuepress配置文件
|   |   ├── enhanceApp.js      // 入口文件,引用写好的组件库
│   ├── components             // 所有组件文档
│   |   ├── button.md          // button组件使用文档
│   |   ├── install.md         // 安装说明文档
│   |   ├── introduce.md       // 介绍文档
│   |   ├── README.md          // 组件使用文档首页
│   ├── README.md              // 首页
// docs/.vuepress/config.js
module.exports = {
  title: 'UI', // 网站名称
  base: '/ui/',
  description: '一个基于Vue.js的高质量桌面端组件库', // 简介
  themeConfig: { // 导航
    subSidebar: 'auto',
    noFoundPageByTencent: false,
    nav: [
      { text: 'Home', link: '/' },
      { text: '组件', link: '/components/install' }
    ],
    sidebar: { // 侧边栏菜单
      '/component': [
        {
          title: '开发指南',
          collapsable: false,
          children: ['/components/install', '/components/introduce']
        },
        {
          title: '基础',
          collapsable: false,
          children: ['/components/button']
        }
      ]
    }
  },
  // 默认设置网站为中文
  locales: {
    '/': {
      lang: 'zh-CN'
    }
  }
}

2. 编写组件示例(button 示例)

  1. docs/.vuepress/components/demo-block.vue(代码块组件,支持复制) 先复制,后期自我优化
<template>
  <div
    class="demo-block"
    :class="[blockClass, { hover: hovering }]"
    @mouseenter="hovering = true"
    @mouseleave="hovering = false"
  >
    <div class="demo-content">
      <slot name="source"></slot>
    </div>
    <div class="meta" ref="meta">
      <div class="description" v-if="$slots.description">
        <slot name="description"></slot>
      </div>
      <div class="code-content">
        <slot></slot>
      </div>
    </div>
    <div
      class="demo-block-control"
      :class="{ 'is-fixed': fixedControl }"
      :style="{ width: fixedControl ? `${codeContentWidth}px` : 'unset' }"
      ref="control"
      @click="isExpanded = !isExpanded"
    >
      <transition name="arrow-slide">
        <i :class="[iconClass, { hovering: hovering }, 'icon']"></i>
      </transition>
      <transition name="text-slide">
        <span v-show="hovering">{{ controlText }}</span>
      </transition>
      <span
        v-show="!copied"
        :class="['copy-action', { 'copying ': copied }]"
        @click.stop="copyCode"
        >{{ copiedText }}</span
      >
      <transition name="bounce">
        <span v-show="copied" class="copy-action copy-action-success">{{ copiedText }}</span>
      </transition>
    </div>
  </div>
</template>

<script type="text/babel">
// import defaultLang from './i18n/default_lang.json'
const defaultLang = [
  {
    lang: 'zh-CN',
    'demo-block': {
      'hide-text': '隐藏代码',
      'show-text': '显示代码',
      'copy-text': '复制代码',
      'copy-success': '复制成功'
    }
  },
  {
    lang: 'en-US',
    'demo-block': {
      'hide-text': '隐藏代码',
      'show-text': '显示代码',
      'copy-text': '复制代码',
      'copy-success': '复制成功'
    }
  }
]
export default {
  data() {
    return {
      hovering: false,
      copied: false,
      isExpanded: false,
      fixedControl: false,
      codeContentWidth: 0,
      scrollParent: null
    }
  },
  props: {
    options: {
      type: Object,
      default: () => {
        return {}
      }
    }
  },
  computed: {
    compoLang() {
      return this.options.locales || defaultLang
      return this.options.locales
    },
    langConfig() {
      return this.compoLang.filter((config) => config.lang === this.$lang)[0]['demo-block']
    },
    blockClass() {
      return `demo-${this.$lang} demo-${this.$router.currentRoute.path.split('/').pop()}`
    },
    iconClass() {
      return this.isExpanded ? 'caret-top' : 'caret-bottom'
    },
    controlText() {
      return this.isExpanded ? this.langConfig['hide-text'] : this.langConfig['show-text']
    },
    copiedText() {
      return this.copied ? this.langConfig['copy-success'] : this.langConfig['copy-text']
    },
    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('code-content')[0].clientHeight +
          20
        )
      }
      return this.$el.getElementsByClassName('code-content')[0].clientHeight
    }
  },
  methods: {
    copyCode() {
      if (this.copied) {
        return
      }
      const pre = this.$el.querySelectorAll('pre')[0]
      pre.setAttribute('contenteditable', 'true')
      pre.focus()
      document.execCommand('selectAll', false, null)
      this.copied = document.execCommand('copy')
      pre.removeAttribute('contenteditable')
      setTimeout(() => {
        this.copied = false
      }, 1500)
    },
    scrollHandler() {
      const { top, bottom, left } = this.$refs.meta.getBoundingClientRect()
      this.fixedControl =
        bottom > document.documentElement.clientHeight &&
        top + 44 <= document.documentElement.clientHeight
      this.$refs.control.style.left = this.fixedControl ? `${left}px` : '0'
    },
    removeScrollHandler() {
      this.scrollParent && this.scrollParent.removeEventListener('scroll', this.scrollHandler)
    }
  },
  watch: {
    isExpanded(val) {
      this.codeArea.style.height = val ? `${this.codeAreaHeight + 1}px` : '0'
      if (!val) {
        this.fixedControl = false
        this.$refs.control.style.left = '0'
        this.removeScrollHandler()
        return
      }
      setTimeout(() => {
        this.scrollParent = document
        this.scrollParent && this.scrollParent.addEventListener('scroll', this.scrollHandler)
        this.scrollHandler()
      }, 200)
    }
  },
  mounted() {
    this.$nextTick(() => {
      let codeContent = this.$el.getElementsByClassName('code-content')[0]
      this.codeContentWidth = this.$el.offsetWidth
      if (this.$el.getElementsByClassName('description').length === 0) {
        codeContent.style.width = '100%'
        codeContent.borderRight = 'none'
      }
    })
  },
  beforeDestroy() {
    this.removeScrollHandler()
  }
}
</script>
<style scoped>
.demo-block {
  border: solid 1px #ebebeb;
  border-radius: 3px;
  transition: 0.2s;
  margin-top: 15px;
  margin-bottom: 15px;
}
.demo-block.hover {
  box-shadow: 0 0 8px 0 rgba(232, 237, 250, 0.6), 0 2px 4px 0 rgba(232, 237, 250, 0.5);
}
.demo-block code {
  font-family: Menlo, Monaco, Consolas, Courier, monospace;
}
.demo-block .demo-button {
  float: right;
}
.demo-block .demo-content {
  padding: 24px;
}
.demo-block .meta {
  background-color: #282c34;
  border: solid 1px #ebebeb;
  border-radius: 3px;
  overflow: hidden;
  height: 0;
  transition: height 0.2s;
}
.demo-block .description {
  padding: 20px;
  box-sizing: border-box;
  border: solid 1px #ebebeb;
  border-radius: 3px;
  font-size: 14px;
  line-height: 22px;
  color: #666;
  word-break: break-word;
  margin: 10px;
  background-color: #fafafa;
}
.demo-block .demo-block-control {
  border-top: solid 1px #eaeefb;
  height: 44px;
  box-sizing: border-box;
  background-color: #fafafa;
  border-bottom-left-radius: 4px;
  border-bottom-right-radius: 4px;
  text-align: center;
  margin-top: -1px;
  color: #d3dce6;
  cursor: pointer;
  position: relative;
}
.demo-block .demo-block-control.is-fixed {
  position: fixed;
  bottom: 0;
  width: 660px;
  z-index: 999;
}
.demo-block .demo-block-control .icon {
  font-family: element-icons !important;
  font-style: normal;
  font-weight: 400;
  font-variant: normal;
  text-transform: none;
  line-height: 1;
  vertical-align: baseline;
  display: inline-block;
  -webkit-font-smoothing: antialiased;
}
.demo-block .demo-block-control .caret-top::before {
  content: '';
  position: absolute;
  right: 50%;
  width: 0;
  height: 0;
  border-bottom: 6px solid #ccc;
  border-right: 6px solid transparent;
  border-left: 6px solid transparent;
}
.demo-block .demo-block-control .caret-bottom::before {
  content: '';
  position: absolute;
  right: 50%;
  width: 0;
  height: 0;
  border-top: 6px solid #ccc;
  border-right: 6px solid transparent;
  border-left: 6px solid transparent;
}
.demo-block .demo-block-control i {
  font-size: 16px;
  line-height: 44px;
  transition: 0.3s;
}
.demo-block .demo-block-control i.hovering {
  transform: translateX(-40px);
}
.demo-block .demo-block-control > span {
  position: absolute;
  transform: translateX(-30px);
  font-size: 14px;
  line-height: 44px;
  transition: 0.3s;
  display: inline-block;
}
.demo-block .demo-block-control .copy-action {
  right: 0px;
  color: #409eff;
}
.demo-block .demo-block-control.copying {
  transform: translateX(-44px);
}
.demo-block .demo-block-control .copy-action-success {
  color: #f5222d;
}
.demo-block .demo-block-control:hover {
  color: #409eff;
  background-color: #f9fafc;
}
.demo-block .demo-block-control .text-slide-enter,
.demo-block .demo-block-control .text-slide-leave-active {
  opacity: 0;
  transform: translateX(10px);
}
.demo-block .demo-block-control .bounce-enter-active {
  animation: bounce-in 0.5s;
}
@keyframes bounce-in {
  0% {
    transform: scale(0);
  }
  50% {
    transform: scale(1.5);
  }
  100% {
    transform: scale(1);
  }
}
.demo-block .demo-block-control .control-button {
  line-height: 26px;
  position: absolute;
  top: 0;
  right: 0;
  font-size: 14px;
  padding-left: 5px;
  padding-right: 25px;
}
</style>
  1. docs/.vuepress/config.js 增加 plugins 配置(配置使用 demo-block 组件)
module.exports = {
  plugins: [
    [
      'container',
      {
        type: 'demo',
        before: (info) => `<demo-block><template slot="description">${info || ''}</template>\n`,
        after: () => '</demo-block>\n'
      }
    ]
  ]
}
  1. docs/.vuepress/enhanceApp.js(入口文件)
import UI from '../../packages/index'

export default ({ Vue }) => {
  Vue.use(UI)
}
  1. docs/.vuepress/components/button/base.vue
 因为 vue 中要使用 scss,所以需要独立安装。
 在docs目录下安装
 npm install sass sass-loader -D
 使用中发现 scss-loader 版本过高会有问题(sass-loader is undefined)。可指定版本号为`10.1.0`,解决
 如启动不起来,可以固定剩下项版本号为
 "sass": "^1.32.8",
 "sass-loader": "^10.1.0",
 "vuepress": "^1.8.2"
<template>
  <demo-button>111</demo-button>
</template>

<script>
export default {
  name: 'button-base'
}
</script>
  1. docs/components/button.md (button 示例文档)
---
title: Button 按钮
---

## Button 组件

基础组件,触发业务逻辑时使用。

### 基础用法

::: demo 使用 type、plain、round 和 circle 属性来定义 Button 的样式。

<template v-slot:source>
  <button-base />
</template>

<<< @/.vuepress/components/button/base.vue
:::

优化组件库文档UI

选型vuepress主题库

  • vuepress-theme-reco
  • AntDocs
  • vuepress-theme-vdoing
# 采用 vuepress-theme-reco,使用人群大
# docs 目录下安装
npm install vuepress-theme-reco

配置vuepress-theme-reco

// docs/.vuepress/config.js 增加主题配置
module.exports = {
  theme: 'reco'
}

docs/.vuepress/config.js 完整内容如下

module.exports = {
  title: 'UI',
  base: '/ui/',
  description: '一个基于Vue.js的高质量桌面端组件库',
  themeConfig: {
    subSidebar: 'auto',
    noFoundPageByTencent: false,
    nav: [
      { text: 'Home', link: '/' },
      { text: '组件', link: '/components/install' }
    ],
    sidebar: {
      '/component': [
        {
          title: '开发指南',
          collapsable: false,
          children: ['/components/install', '/components/introduce']
        },
        {
          title: '基础',
          collapsable: false,
          children: ['/components/button']
        }
      ]
    }
  },
  theme: 'reco',
  // 默认设置网站为中文
  locales: {
    '/': {
      lang: 'zh-CN'
    }
  },
  plugins: [
    [
      'container',
      {
        type: 'demo',
        before: info => `<demo-block><template slot="description">${info || ''}</template>\n`,
        after: () => '</demo-block>\n'
      }
    ]
  ]
}