├── .browserslistrc ├── src ├── (不会被打包)App │ ├── 啊!声明.md │ ├── 骨架.vue │ ├── 日期选择器.vue │ ├── 分页器.vue │ ├── 弹出框.vue │ ├── popover.vue │ ├── 计数器.vue │ ├── tree.vue │ ├── tab.vue │ ├── 懒加载.vue │ ├── 小星星.vue │ └── 按钮输入.vue ├── components │ ├── Alert │ │ ├── index.js │ │ └── main │ │ │ ├── alert.js │ │ │ └── alert.vue │ ├── Lazy │ │ ├── index.js │ │ └── main │ │ │ └── lazy.js │ ├── Toast │ │ ├── index.js │ │ └── main │ │ │ ├── toast.js │ │ │ └── toast.vue │ ├── Popover │ │ ├── index.js │ │ └── main │ │ │ ├── index.js │ │ │ └── popover.vue │ ├── loading │ │ ├── index.js │ │ └── main │ │ │ ├── loading.js │ │ │ └── loading.vue │ ├── Icon │ │ ├── index.js │ │ └── main │ │ │ └── icon.vue │ ├── Rate │ │ ├── index.js │ │ └── main │ │ │ └── rate.vue │ ├── Ske │ │ ├── index.js │ │ └── main │ │ │ └── ske.vue │ ├── Tree │ │ ├── index.js │ │ └── main │ │ │ └── tree.vue │ ├── Input │ │ ├── index.js │ │ └── main │ │ │ └── input.vue │ ├── Button │ │ ├── index.js │ │ └── main │ │ │ └── button.vue │ ├── DatePicker │ │ ├── index.js │ │ └── main │ │ │ └── datePicker.vue │ ├── InputNumber │ │ ├── index.js │ │ └── main │ │ │ └── input-number.vue │ ├── Pagination │ │ ├── index.js │ │ └── main │ │ │ └── pagination.vue │ ├── Tab │ │ ├── index.js │ │ └── main │ │ │ ├── tab-pane.vue │ │ │ └── tab.vue │ └── index.js ├── style │ ├── config │ │ └── index.scss │ ├── Icon.scss │ ├── index.scss │ ├── Loading.scss │ ├── common │ │ ├── var.scss │ │ ├── animation.scss │ │ ├── extend.scss │ │ ├── reset.scss │ │ └── mixin.scss │ ├── inputNumber.scss │ ├── Rate.scss │ ├── Toast.scss │ ├── Alert.scss │ ├── Pagination.scss │ ├── Tree.scss │ ├── Input.scss │ ├── Button.scss │ ├── Ske.scss │ ├── DatePicker.scss │ ├── Tab.scss │ ├── Popover.scss │ └── fonts │ │ └── iconfont.js ├── assets │ └── js │ │ ├── handelDate.js │ │ ├── Clickoutside.js │ │ ├── prevent.js │ │ ├── utils.js │ │ └── vuePopper.js └── main.js ├── babel.config.js ├── tests ├── unit │ ├── .eslintrc.js │ ├── Toast.test.js │ ├── Loading.test.js │ ├── Button.test.js │ ├── Pagination.test.js │ └── Input.test.js └── utils │ └── util.js ├── postcss.config.js ├── .gitignore ├── .eslintrc.js ├── public └── index.html ├── README.md ├── jest.config.js └── package.json /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not ie <= 8 4 | -------------------------------------------------------------------------------- /src/(不会被打包)App/啊!声明.md: -------------------------------------------------------------------------------- 1 | ### 特别声明 2 | 1. 此处为示例文件夹, 不会被打包到项目中, 所以中文命名也无妨. -------------------------------------------------------------------------------- /src/components/Alert/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './main/alert'; 2 | -------------------------------------------------------------------------------- /src/components/Lazy/index.js: -------------------------------------------------------------------------------- 1 | 2 | export { default} from './main/lazy' -------------------------------------------------------------------------------- /src/components/Toast/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './main/toast.js'; -------------------------------------------------------------------------------- /src/components/Popover/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './main/index'; 2 | -------------------------------------------------------------------------------- /src/components/loading/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './main/loading.js'; -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /tests/unit/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | jest: true 4 | } 5 | } -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/style/config/index.scss: -------------------------------------------------------------------------------- 1 | $namespace: 'cc'; 2 | $state-prefix: 'is-'; 3 | 4 | $cc-separator: '__'; 5 | $cc-modifier-separator: '--'; 6 | -------------------------------------------------------------------------------- /tests/utils/util.js: -------------------------------------------------------------------------------- 1 | // 1: 简易选择 2 | export const findTestWrapper = (wrapper, tag) => { 3 | return wrapper.findAll(`[data-test="${tag}"]`); 4 | }; 5 | -------------------------------------------------------------------------------- /src/components/Icon/index.js: -------------------------------------------------------------------------------- 1 | import Icon from './main/icon.vue' 2 | 3 | Icon.install = function(Vue) { 4 | Vue.component(Icon.name, Icon); 5 | }; 6 | 7 | export default Icon -------------------------------------------------------------------------------- /src/components/Rate/index.js: -------------------------------------------------------------------------------- 1 | import Rate from './main/rate.vue' 2 | 3 | Rate.install = function(Vue) { 4 | Vue.component(Rate.name, Rate); 5 | }; 6 | 7 | export default Rate -------------------------------------------------------------------------------- /src/components/Ske/index.js: -------------------------------------------------------------------------------- 1 | import Ske from './main/ske.vue'; 2 | 3 | Ske.install = function(Vue) { 4 | Vue.component(Ske.name, Ske); 5 | }; 6 | 7 | export default Ske 8 | -------------------------------------------------------------------------------- /src/components/Tree/index.js: -------------------------------------------------------------------------------- 1 | import Tree from './main/tree.vue'; 2 | 3 | Tree.install = function(Vue) { 4 | Vue.component(Tree.name, Tree); 5 | }; 6 | 7 | export default Tree 8 | -------------------------------------------------------------------------------- /src/components/Input/index.js: -------------------------------------------------------------------------------- 1 | import Input from './main/input.vue' 2 | 3 | Input.install = function(Vue) { 4 | Vue.component(Input.name, Input); 5 | }; 6 | 7 | export default Input 8 | -------------------------------------------------------------------------------- /src/components/Button/index.js: -------------------------------------------------------------------------------- 1 | import Button from './main/button.vue' 2 | 3 | Button.install = function(Vue) { 4 | Vue.component(Button.name, Button); 5 | }; 6 | 7 | export default Button 8 | -------------------------------------------------------------------------------- /src/components/DatePicker/index.js: -------------------------------------------------------------------------------- 1 | import DatePicker from './main/datePicker.vue' 2 | 3 | DatePicker.install = function(Vue) { 4 | Vue.component(DatePicker.name, DatePicker); 5 | }; 6 | 7 | export default DatePicker 8 | -------------------------------------------------------------------------------- /src/components/InputNumber/index.js: -------------------------------------------------------------------------------- 1 | import inputNumber from './main/input-number.vue' 2 | 3 | inputNumber.install = function(Vue) { 4 | Vue.component(inputNumber.name, inputNumber); 5 | }; 6 | 7 | export default inputNumber -------------------------------------------------------------------------------- /src/components/Pagination/index.js: -------------------------------------------------------------------------------- 1 | import Pagination from './main/pagination.vue'; 2 | 3 | Pagination.install = function(Vue) { 4 | Vue.component(Pagination.name, Pagination); 5 | }; 6 | 7 | export default Pagination; 8 | -------------------------------------------------------------------------------- /src/components/Tab/index.js: -------------------------------------------------------------------------------- 1 | import Tab from './main/tab.vue' 2 | import TabPane from './main/tab-pane.vue' 3 | 4 | Tab.install = function(Vue) { 5 | Vue.component(Tab.name, Tab); 6 | Vue.component(TabPane.name, TabPane); 7 | }; 8 | 9 | export default Tab -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw* 22 | -------------------------------------------------------------------------------- /src/style/Icon.scss: -------------------------------------------------------------------------------- 1 | @import './common/mixin.scss'; 2 | @import "./common/animation.scss"; 3 | 4 | @include b(icon) { 5 | vertical-align: middle; 6 | .#{$state-prefix}disabled { 7 | cursor: not-allowed; 8 | fill: $--color-disabled; 9 | } 10 | .icon-loading { 11 | animation: rotating 1s infinite linear; 12 | } 13 | } -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | 'extends': [ 7 | 'plugin:vue/essential', 8 | 'eslint:recommended' 9 | ], 10 | rules: { 11 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 12 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 13 | }, 14 | parserOptions: { 15 | parser: 'babel-eslint' 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/(不会被打包)App/骨架.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | -------------------------------------------------------------------------------- /src/style/index.scss: -------------------------------------------------------------------------------- 1 | 2 | @import './Tab.scss'; 3 | @import './Ske.scss'; 4 | @import './Icon.scss'; 5 | @import './Rate.scss'; 6 | @import './Tree.scss'; 7 | @import './Toast.scss'; 8 | @import './Input.scss'; 9 | @import './Alert.scss'; 10 | @import './Button.scss'; 11 | @import './Loading.scss'; 12 | @import './Popover.scss'; 13 | @import './Pagination.scss'; 14 | @import './DatePicker.scss'; 15 | @import './inputNumber.scss'; 16 | @import './common/reset.scss'; -------------------------------------------------------------------------------- /src/style/Loading.scss: -------------------------------------------------------------------------------- 1 | @import './common/var.scss'; 2 | @import './common/extend.scss'; 3 | @import './common/mixin.scss'; 4 | 5 | @include b(loading) { 6 | @include position(); 7 | &__curtain{ 8 | @include position(); 9 | } 10 | &__icon{ 11 | position: absolute; 12 | color: $--color-nomal; 13 | z-index: 10; 14 | @include flexCenter; 15 | @include position(); 16 | :nth-child(1){ 17 | margin-bottom:6px; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/style/common/var.scss: -------------------------------------------------------------------------------- 1 | // 基本色 2 | $--color-black:#000000 !default; 3 | $--color-white:#FFFFFF !default; 4 | // 基本色鲜艳 5 | $--color-weak:#606266 !default; 6 | $--color-nomal:#409EFF !default; 7 | $--color-success:#7CCD7C !default; 8 | $--color-warning:#FFB90F !default; 9 | $--color-danger: #FF0000 !default; 10 | $--color-disabled: #bbbbbb !default; 11 | $--color-difference: #F5F7FA !default; 12 | // 字体 13 | $--size-big: 16px; 14 | $--size-nomal: 15px; 15 | $--size-small: 14px; 16 | // 输入框 17 | $--color-input-placeholder:#aaa 18 | -------------------------------------------------------------------------------- /src/components/Tab/main/tab-pane.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 30 | -------------------------------------------------------------------------------- /src/(不会被打包)App/日期选择器.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | cc_ui 9 | 10 | 11 | 14 |
15 | 16 | 17 | -------------------------------------------------------------------------------- /tests/unit/Toast.test.js: -------------------------------------------------------------------------------- 1 | import toast from '../../src/components/Toast/main/toast'; 2 | import Vue from 'vue'; 3 | 4 | describe('测试toast组件', () => { 5 | it('1: 将方法挂载原型上', () => { 6 | toast.install(Vue); 7 | expect(Vue.prototype.$ccToast).toBeTruthy(); 8 | }); 9 | 10 | it('2: 传入参数, 形成dom', () => { 11 | toast.install(Vue); 12 | Vue.prototype.$ccToast({ 13 | message: '123' 14 | }); 15 | let node = document.body.childNodes[0], 16 | classList = 'cc-toast cc-toast--nomal cc-toast--big'; 17 | expect(node.className).toBe(classList); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /src/assets/js/handelDate.js: -------------------------------------------------------------------------------- 1 | export function getYMD(date){ 2 | let day = date.getDate(); 3 | let month = date.getMonth(); 4 | let year = date.getFullYear(); 5 | return { 6 | year, month, day 7 | } 8 | } 9 | 10 | export const getDayCountOfMonth = function(year, month) { 11 | if (month === 3 || month === 5 || month === 8 || month === 10) { 12 | return 30; 13 | } 14 | if (month === 1) { 15 | if (year % 4 === 0 && year % 100 !== 0 || year % 400 === 0) { 16 | return 29; 17 | } else { 18 | return 28; 19 | } 20 | } 21 | 22 | return 31; 23 | }; -------------------------------------------------------------------------------- /src/assets/js/Clickoutside.js: -------------------------------------------------------------------------------- 1 | // 判断点击是不是自己 2 | const clickoutside = { 3 | bind(el, bindings, vnode) { 4 | const handleClick = function(e) { 5 | // 如果当前的不包含目标元素, 也就是隐藏日期组件 6 | // 这个vnode就是组件的虚拟dom 7 | if (!el.contains(e.target)) { 8 | // 拿到日历组件的实例, expression就是我们指令传进来的操作; 9 | vnode.context[bindings.expression](); 10 | } 11 | }; 12 | // 为了方便移除 13 | el.handleClick = handleClick; 14 | document.addEventListener('click', handleClick); 15 | }, 16 | unbind(el) { 17 | document.removeEventListener('click', el.handleClick); 18 | } 19 | }; 20 | 21 | export default clickoutside; 22 | -------------------------------------------------------------------------------- /src/style/common/animation.scss: -------------------------------------------------------------------------------- 1 | @keyframes rotating { 2 | 0% { 3 | transform: rotateZ(0deg); 4 | } 5 | 6 | 100% { 7 | transform: rotateZ(360deg); 8 | } 9 | } 10 | 11 | @keyframes size { 12 | 0% { 13 | transform: scale(1); 14 | } 15 | 16 | 100% { 17 | transform: scale(1.1); 18 | } 19 | } 20 | 21 | @keyframes bling { 22 | 0% { 23 | left: 0; 24 | } 25 | 26 | 100% { 27 | left: 310%; 28 | } 29 | } 30 | 31 | 32 | @keyframes pass { 33 | 0% { 34 | transform: rotate(-45deg) translate(0px); 35 | } 36 | 37 | 100% { 38 | transform: rotate(-45deg) translate(2000px); 39 | } 40 | } 41 | 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cc-ui 是一个 vue 的 ui 组件库 2 | 3 | ## 介绍 4 | 5 |

关于作者

6 |

自信自律的终身学习者: lulu

7 |

工作方面: 本人更喜欢稳健的作风, 代码的编写要守规矩, 插件要使用网上成熟的开源库, 采取的解决方案以稳妥为主.

8 |

学习方面: 本人更喜欢天马行空的想法, 不拘一格的做派, 可以中文编程, 可以各种各样的效果, 遇到问题都要自己解决, 我可以学别人的方法, 但我要自己实现自己写, 所以有时候会做出一些样子乱七八糟的组件, 还请大家包涵😁

9 | 10 |

关于ui

11 |

'cc'是本人养的大金毛,很可爱的!

12 |

本组件适用于pc端, 因为本人从事过大型后台管理系统的开发, 感觉很多组件库写的太棒了, 当然这些库也存在一些不足之处, 所以才产生了编写一套自己的组件库的想法.

13 |

生活不止代码, 所以也想通过本套工程与大家一起学习一起交流😁共同实现人生价值

14 | 15 | ## 安装 16 | 17 | npm i vue-cc-ui -S 18 | 19 | ## 详细的相关文章请查看: 20 | 21 | https://segmentfault.com/u/lulu_up/articles 22 | 23 | ## ui 详情请打开 lulu 个人开发网站 24 | 25 | https://zhangzhaosong.com 26 | -------------------------------------------------------------------------------- /src/components/loading/main/loading.js: -------------------------------------------------------------------------------- 1 | import Loading from './loading.vue'; 2 | 3 | Loading.install = function(Vue) { 4 | Vue.component(Loading.name, Loading); 5 | Vue.prototype.$ccShowLoading = function(options) { 6 | let Constructor = Vue.extend(Loading); 7 | let node = new Constructor({ 8 | propsData: options 9 | }); 10 | node.vm = node.$mount(); 11 | document.body.appendChild(node.$el); 12 | }; 13 | Vue.prototype.$ccHiddenLoading = function() { 14 | document.body.childNodes.forEach(item => { 15 | if (item.className === 'cc-loading') { 16 | document.body.removeChild(item); 17 | } 18 | }); 19 | }; 20 | }; 21 | 22 | export default Loading; -------------------------------------------------------------------------------- /src/components/Toast/main/toast.js: -------------------------------------------------------------------------------- 1 | import toast from './toast.vue'; 2 | export default { 3 | install(Vue) { 4 | Vue.prototype.$ccToast = function(options) { 5 | let Constructor = Vue.extend(toast), node; 6 | if (typeof options === 'object' && options instanceof Object) { 7 | node = new Constructor({ 8 | propsData: options 9 | }); 10 | node.$slots.default = [options.message]; 11 | } else if (typeof options === 'string') { 12 | node = new Constructor(); 13 | node.$slots.default = [options]; 14 | } 15 | node.vm = node.$mount(); 16 | node.vm.visible = true 17 | document.body.appendChild(node.$el); 18 | }; 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /tests/unit/Loading.test.js: -------------------------------------------------------------------------------- 1 | import { shallowMount } from '@vue/test-utils'; 2 | import Loading from '../../src/components/Loading'; 3 | import { findTestWrapper } from '../utils/util'; 4 | 5 | describe('测试Loading组件', () => { 6 | it('1: 可以渲染出Loading组件', () => { 7 | const wrapper = shallowMount(Loading); 8 | // 渲染出的dom里面 有class为.cc-loading的dom 9 | expect(wrapper.classes()).toContain('cc-loading'); 10 | }); 11 | 12 | it('2: 传入title显示是否正确', () => { 13 | const wrapper = shallowMount(Loading, { 14 | propsData: { 15 | title: '正在努力ing' 16 | } 17 | }); 18 | const title = findTestWrapper(wrapper, 'title').at(0); 19 | expect(title.text()).toBe('正在努力ing'); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/style/common/extend.scss: -------------------------------------------------------------------------------- 1 | @import './var.scss'; 2 | @import './animation.scss'; 3 | 4 | .cc-bling { 5 | &:after { 6 | content: ''; 7 | position: absolute; 8 | animation: bling 1s infinite linear; 9 | background-image: linear-gradient(to right, rgb(232, 229, 229), white); 10 | left: 0; 11 | top: -20px; 12 | width: 15px; 13 | height: calc(100% + 30px); 14 | transform: rotate(-30deg); 15 | } 16 | } 17 | 18 | .active-item { 19 | color: $--color-white; 20 | background-color: $--color-nomal; 21 | border-radius: 10px; 22 | } 23 | 24 | .check-ball{ 25 | display: inline-block; 26 | width: .5em; 27 | height: .5em; 28 | margin:0 5px; 29 | border-radius: 100%; 30 | } -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleFileExtensions: ['js', 'jsx', 'json', 'vue'], 3 | transform: { 4 | '^.+\\.vue$': 'vue-jest', 5 | '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 6 | 'jest-transform-stub', 7 | '^.+\\.jsx?$': 'babel-jest' 8 | }, 9 | transformIgnorePatterns: ['/node_modules/','/.eslintrc.js/','/dist/'], 10 | moduleNameMapper: { 11 | '^@/(.*)$': '/src/$1' 12 | }, 13 | snapshotSerializers: ['jest-serializer-vue'], 14 | testMatch: [ 15 | '**/tests/unit/**/*.(spec|test).(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)' 16 | ], 17 | testURL: 'http://localhost/', 18 | // watchPlugins: [ 19 | // 'jest-watch-typeahead/filename', 20 | // 'jest-watch-typeahead/testname' 21 | // ] 22 | }; 23 | -------------------------------------------------------------------------------- /src/style/inputNumber.scss: -------------------------------------------------------------------------------- 1 | @import './common/var.scss'; 2 | @import './common/extend.scss'; 3 | @import './common/mixin.scss'; 4 | @import './config/index.scss'; 5 | 6 | @include b(input-number) { 7 | cursor: pointer; 8 | align-items: center; 9 | display: inline-flex; 10 | background-color: white; 11 | transition:all .1s; 12 | &:hover { 13 | z-index: 6; 14 | transform: scale(1.2); 15 | } 16 | @include commonShadow($--color-black); 17 | @include e(add) { 18 | padding: 4px 6px; 19 | @include flexCenter(); 20 | } 21 | @include e(reduce) { 22 | padding: 4px 6px; 23 | @include flexCenter(); 24 | } 25 | @include e(input) { 26 | border: none; 27 | outline:none; 28 | display: block; 29 | text-align: center; 30 | width:60px; 31 | height: 20px; 32 | } 33 | } -------------------------------------------------------------------------------- /src/components/Popover/main/index.js: -------------------------------------------------------------------------------- 1 | import Popover from './popover.vue'; 2 | import prevent from '@/assets/js/prevent'; 3 | 4 | Popover.install = function(Vue) { 5 | Vue.component(Popover.name, Popover); 6 | Vue.prototype.$clearPopover = function() { 7 | let ary = document.getElementsByClassName('cc-popover__content'); 8 | for (let i = 0; i < ary.length; i++) { 9 | ary[i].style.display = 'none'; 10 | } 11 | }; 12 | // 监听指令 13 | window.addEventListener('scroll',()=>{ 14 | prevent(1,() => { 15 | Vue.prototype.$clearPopover() 16 | },400); 17 | },false) 18 | 19 | Vue.directive('scroll-clear-popover', { 20 | bind: el => { 21 | el.addEventListener('scroll', ()=>{ 22 | prevent(1,() => { 23 | Vue.prototype.$clearPopover() 24 | },400); 25 | }, false); 26 | } 27 | }); 28 | }; 29 | 30 | export default Popover; 31 | -------------------------------------------------------------------------------- /src/style/Rate.scss: -------------------------------------------------------------------------------- 1 | @import './common/var.scss'; 2 | @import './common/extend.scss'; 3 | @import './common/mixin.scss'; 4 | 5 | @include b(rate) { 6 | position: relative; 7 | align-items: center; 8 | display: inline-flex; 9 | justify-content: center; 10 | 11 | @include e (dark) { 12 | display: flex; 13 | align-items: center; 14 | height: 100%; 15 | padding-right:3px ; 16 | transition: all .3; 17 | } 18 | 19 | @include e (bright) { 20 | overflow: hidden; 21 | flex-wrap: nowrap; 22 | position: absolute; 23 | align-items: center; 24 | display: inline-flex; 25 | top: 0; 26 | left: 0; 27 | height: 100%; 28 | transition: all .3; 29 | } 30 | 31 | @include e(score) { 32 | margin-left: 6px; 33 | } 34 | 35 | @include m(big) { 36 | transform: scale(.8) 37 | } 38 | } -------------------------------------------------------------------------------- /tests/unit/Button.test.js: -------------------------------------------------------------------------------- 1 | import { shallowMount } from '@vue/test-utils'; 2 | import Button from '../../src/components/Button'; 3 | import { findTestWrapper } from '../utils/util'; 4 | 5 | 6 | describe('测试button组件', () => { 7 | 8 | it('1: 可以渲染出button组件', () => { 9 | const wrapper = shallowMount(Button); 10 | expect(wrapper.contains('button')).toBe(true); 11 | }); 12 | 13 | it('2: button组件点击时会触发click事件', () => { 14 | const wrapper = shallowMount(Button); 15 | const button = findTestWrapper(wrapper,'button').at(0); 16 | button.trigger('click'); 17 | expect(wrapper.emitted().click).toBeTruthy(); 18 | }); 19 | 20 | it('3: 传入icon参数, 可以显示icon组件', () => { 21 | const wrapper = shallowMount(Button,{ 22 | propsData:{ 23 | icon:'cc-up' 24 | } 25 | }); 26 | const icon = findTestWrapper(wrapper,'icon').at(0); 27 | expect(icon).toBeTruthy(); 28 | }); 29 | 30 | }); 31 | -------------------------------------------------------------------------------- /src/components/Icon/main/icon.vue: -------------------------------------------------------------------------------- 1 | 20 | -------------------------------------------------------------------------------- /src/style/common/reset.scss: -------------------------------------------------------------------------------- 1 | li, 2 | ul { 3 | list-style: none; 4 | margin: 0; 5 | padding: 0; 6 | box-sizing: border-box; 7 | } 8 | 9 | // 干掉input框typr=number时的上下按钮 10 | input::-webkit-outer-spin-button, 11 | input::-webkit-inner-spin-button { 12 | -webkit-appearance: none; 13 | } 14 | 15 | input[type="number"] { 16 | -moz-appearance: textfield; 17 | } 18 | 19 | // toast 20 | .leave-leave-active { 21 | transition: all .8s cubic-bezier(1.0, 0.5, 0.8, 1.0); 22 | } 23 | 24 | .leave-leave-to { 25 | transform: translateX(10px); 26 | opacity: 0; 27 | } 28 | 29 | // 渐隐渐现 30 | .fade-enter-active, 31 | .fade-leave-active { 32 | transition: opacity .5s; 33 | } 34 | 35 | .fade-enter, 36 | .fade-leave-to 37 | { 38 | opacity: 0; 39 | } 40 | 41 | // 弹出框 42 | .cc-alert-fade-enter-active, 43 | .cc-alert-fade-leave-active { 44 | transition: all .4s; 45 | } 46 | 47 | .cc-alert-fade-enter, 48 | .cc-alert-fade-leave-to 49 | { 50 | transform: translateY(10px) 51 | } -------------------------------------------------------------------------------- /src/components/Alert/main/alert.js: -------------------------------------------------------------------------------- 1 | import alert from './alert.vue'; 2 | export default { 3 | install(Vue) { 4 | // 显示弹框 5 | Vue.prototype.$ccAlert = function(options) { 6 | let Constructor = Vue.extend(alert), node; 7 | if (typeof options === 'object' && options instanceof Object) { 8 | node = new Constructor({ 9 | propsData: options 10 | }); 11 | node.$slots.default = [options.message]; 12 | } else if (typeof options === 'string') { 13 | node = new Constructor(); 14 | node.$slots.default = [options]; 15 | } 16 | node.vm = node.$mount(); 17 | node.vm.visible = true 18 | document.body.appendChild(node.$el); 19 | }; 20 | // 移除弹框 21 | Vue.prototype.$ccHiddenAlert = function() { 22 | document.body.childNodes.forEach(item => { 23 | if (item.className === 'cc-alert') { 24 | document.body.removeChild(item); 25 | } 26 | }); 27 | }; 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /src/(不会被打包)App/分页器.vue: -------------------------------------------------------------------------------- 1 | 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-cc-ui", 3 | "version": "0.0.17", 4 | "main": "./dist/cc-ui.umd.min", 5 | "files": [ 6 | "dist/*", 7 | "src/*", 8 | "*.json", 9 | "*.js" 10 | ], 11 | "scripts": { 12 | "serve": "vue-cli-service serve", 13 | "build": "vue-cli-service build", 14 | "lint": "vue-cli-service lint", 15 | "test:unit": "vue-cli-service test:unit", 16 | "build-bundle": "vue-cli-service build --target lib --name cc-ui ./src/components/index.js" 17 | }, 18 | "dependencies": { 19 | "vue": "^2.6.6" 20 | }, 21 | "devDependencies": { 22 | "@vue/cli-plugin-babel": "^3.5.0", 23 | "@vue/cli-plugin-eslint": "^3.5.0", 24 | "@vue/cli-plugin-unit-jest": "^3.10.0", 25 | "@vue/cli-service": "^3.5.0", 26 | "@vue/test-utils": "1.0.0-beta.29", 27 | "babel-core": "7.0.0-bridge.0", 28 | "babel-eslint": "^10.0.1", 29 | "babel-jest": "^23.6.0", 30 | "eslint": "^5.8.0", 31 | "eslint-plugin-vue": "^5.0.0", 32 | "node-sass": "^4.12.0", 33 | "sass-loader": "^7.1.0", 34 | "speed-measure-webpack-plugin": "^1.3.1", 35 | "vue-template-compiler": "^2.5.21" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/style/Toast.scss: -------------------------------------------------------------------------------- 1 | @import './common/mixin.scss'; 2 | @import "./common/animation.scss"; 3 | 4 | @include b(toast) { 5 | position: fixed; 6 | display: inline-block; 7 | background-color: white; 8 | top: 6px; 9 | left: 50%; 10 | padding: 6px 20px; 11 | transition: all .3s; 12 | transform: translateX(-50%); 13 | @include commonShadow($--color-black); 14 | .#{$namespace}-toast__closeButton { 15 | display: flex; 16 | cursor: pointer; 17 | position: absolute; 18 | align-items: center; 19 | justify-content: center; 20 | top: 0; 21 | right: 0; 22 | bottom: 0; 23 | padding: 0 5px; 24 | &:hover { 25 | animation: rotating .5s infinite linear; 26 | } 27 | } 28 | @at-root { 29 | @include commonType(cc-toast--); 30 | .#{$namespace}-toast--big { 31 | padding: 6px 35px 6px 20px; 32 | } 33 | .#{$namespace}-toast-fade-enter, 34 | .#{$namespace}-toast-fade-leave-active { 35 | opacity: 0; 36 | transform: translate(-50%, -100%); 37 | } 38 | }; 39 | } -------------------------------------------------------------------------------- /src/(不会被打包)App/弹出框.vue: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | -------------------------------------------------------------------------------- /src/assets/js/prevent.js: -------------------------------------------------------------------------------- 1 | // 数据节流函数 2 | // id: 唯一标识 3 | // obj: 要执行的函数 4 | // time: 多久之内节流 5 | // model: 哪种模式 6 | // 需要节流的对象 7 | const preventList = {}; 8 | const config = ['', throttle, shakeBefour, shakeAfter]; 9 | const prevent = function(id, obj, time, model = 1) { 10 | config[model](id, obj, time); 11 | }; 12 | 13 | // 模式1 不管点多少下每隔time秒,触发一次 14 | function throttle(id, obj, time) { 15 | if (preventList['can' + id]) return; 16 | obj(); 17 | preventList['can' + id] = true; 18 | preventList['time' + id] = setTimeout(() => { 19 | preventList['can' + id] = false; 20 | }, time); 21 | } 22 | 23 | // 模式2 每次动作都有time的延时再执行,也就是所有连续点击完事的时候执行一个 24 | function shakeBefour(id, obj, time) { 25 | clearTimeout(preventList['time' + id]); 26 | preventList['time' + id] = setTimeout(() => obj(), time); 27 | } 28 | 29 | // 默认的模式, 模式3, 第一下点击触发, 之后时间内不触发 30 | function shakeAfter(id, obj, time) { 31 | if (preventList['can' + id]) { 32 | clearTimeout(preventList['time' + id]); 33 | } else { 34 | obj(); 35 | preventList['can' + id] = true; 36 | } 37 | preventList['time' + id] = setTimeout(() => { 38 | preventList['can' + id] = false; 39 | }, time); 40 | } 41 | 42 | export default prevent; 43 | -------------------------------------------------------------------------------- /src/(不会被打包)App/popover.vue: -------------------------------------------------------------------------------- 1 | 27 | -------------------------------------------------------------------------------- /tests/unit/Pagination.test.js: -------------------------------------------------------------------------------- 1 | import { shallowMount } from '@vue/test-utils'; 2 | import Pagination from '../../src/components/Pagination'; 3 | import { findTestWrapper } from '../utils/util'; 4 | 5 | describe('测试分页器组件', () => { 6 | it('1: 可以渲染出分页器组件', () => { 7 | const wrapper = shallowMount(Pagination,{ 8 | propsData:{ 9 | pageTotal:5, 10 | value:1 11 | } 12 | }); 13 | expect(wrapper.classes()).toContain('cc-pagination'); 14 | }); 15 | 16 | it('2: 传入1000页是否显示1000页', () => { 17 | const wrapper = shallowMount(Pagination, { 18 | propsData:{ 19 | pageTotal:1000, 20 | pageSize:1000, 21 | value:1 22 | } 23 | }); 24 | const li = findTestWrapper(wrapper, 'item'); 25 | expect(li.length).toBe(1000); 26 | }); 27 | 28 | it('3: 点击第三页是否跳转到第三页', () => { 29 | const wrapper = shallowMount(Pagination, { 30 | propsData:{ 31 | pageTotal:10, 32 | pageSize:10, 33 | value:1 34 | } 35 | }); 36 | wrapper.vm.handlClick(3) 37 | // 发送事件 38 | expect(wrapper.emitted().input).toBeTruthy(); 39 | // 发送事件的参数, 注意,是数组的形式 40 | expect(wrapper.emitted().input[0]).toEqual([3]) 41 | }); 42 | }); -------------------------------------------------------------------------------- /src/style/Alert.scss: -------------------------------------------------------------------------------- 1 | @import './common/var.scss'; 2 | @import './common/mixin.scss'; 3 | @import './common/extend.scss'; 4 | 5 | @include b(alert) { 6 | &::after { 7 | content: ''; 8 | @include position(fixed); 9 | background-color: $--color-black; 10 | opacity: 0.7; 11 | } 12 | 13 | @include e(context) { 14 | position: absolute; 15 | background-color: white; 16 | @include commonShadow($--color-black, 10px); 17 | top: 50%; 18 | left: 50%; 19 | z-index: 2; 20 | min-width: 400px; 21 | border-radius: 5px; 22 | padding: 15px 20px; 23 | transform: translate(-50%, -50%); 24 | 25 | &>header { 26 | display: flex; 27 | align-items: center; 28 | justify-content: space-between; 29 | font-size: 21px; 30 | font-weight: 200; 31 | } 32 | 33 | &>article { 34 | padding: 15px 0; 35 | min-height: 40px; 36 | } 37 | 38 | &>footer { 39 | display: flex; 40 | justify-content: flex-end; 41 | 42 | button:nth-of-type(1) { 43 | margin-right: 15px; 44 | } 45 | } 46 | } 47 | 48 | @include e(icon) { 49 | cursor: pointer; 50 | transition: all .2s; 51 | &:hover{ 52 | transform: scale(20) 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | import tab from './Tab/index'; 2 | import ske from './Ske/index'; 3 | import lazy from './Lazy/index'; 4 | import rate from './Rate/index'; 5 | import tree from './Tree/index'; 6 | import icon from './Icon/index'; 7 | import alert from './Alert/index'; 8 | import toast from './Toast/index'; 9 | import input from './Input/index'; 10 | import button from './Button/index'; 11 | import loading from './loading/index'; 12 | import popover from './Popover/index'; 13 | import pagination from './Pagination/index'; 14 | import datePicker from './DatePicker/index'; 15 | import inputNumber from './InputNumber/index'; 16 | // 总的样式 17 | import '../style/index.scss'; 18 | // 字体样式 19 | import '../style/fonts/iconfont'; 20 | 21 | const components = [ 22 | ske, 23 | tab, 24 | // lazy, 因为他要很多配置, 所以需要单独玩一下 25 | icon, 26 | rate, 27 | tree, 28 | alert, 29 | input, 30 | toast, 31 | button, 32 | popover, 33 | loading, 34 | datePicker, 35 | pagination, 36 | inputNumber 37 | ]; 38 | 39 | let CC = { 40 | install(Vue) { 41 | components.forEach(component => { 42 | Vue.component(component.name, component); 43 | }); 44 | } 45 | }; 46 | 47 | export default CC; 48 | 49 | 50 | export { 51 | ske, 52 | tab, 53 | icon, 54 | rate, 55 | tree, 56 | lazy, 57 | alert, 58 | input, 59 | toast, 60 | button, 61 | popover, 62 | loading, 63 | datePicker, 64 | pagination, 65 | inputNumber, 66 | }; 67 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | // import App from './(不会被打包)App/tab.vue'; 3 | import App from './(不会被打包)App/按钮输入.vue'; 4 | // import App from './(不会被打包)App/计数器.vue'; 5 | // import App from './(不会被打包)App/小星星.vue'; 6 | // import App from './(不会被打包)App/骨架.vue'; 7 | // import App from './(不会被打包)App/懒加载.vue'; 8 | // import App from './(不会被打包)App/分页器.vue'; 9 | // import App from './(不会被打包)App/popover.vue' 10 | // import App from './(不会被打包)App/日期选择器.vue'; 11 | // import App from './(不会被打包)App/tree.vue' 12 | // import App from './(不会被打包)App/弹出框.vue'; 13 | 14 | import { 15 | tab, 16 | ske, 17 | rate, 18 | lazy, 19 | tree, 20 | icon, 21 | input, 22 | toast, 23 | alert, 24 | button, 25 | popover, 26 | loading, 27 | datePicker, 28 | pagination, 29 | inputNumber, 30 | } from './components/index'; 31 | Vue.use(button) 32 | .use(tab) 33 | .use(ske) 34 | .use(icon) 35 | .use(tree) 36 | .use(rate) 37 | .use(alert) 38 | .use(input) 39 | .use(toast) 40 | .use(popover) 41 | .use(loading) 42 | .use(datePicker) 43 | .use(pagination) 44 | .use(inputNumber) 45 | .use(lazy, { 46 | time: 200, 47 | loadingImg:'http://img.mp.sohu.com/upload/20170715/7b3ce9cd49604308bdd2b7755f52cab9_th.png', 48 | error:'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1564597486512&di=9f7ccdadddbd47791ed80eac0890c5e5&imgtype=0&src=http%3A%2F%2Fb-ssl.duitang.com%2Fuploads%2Fblog%2F201501%2F03%2F20150103183647_ZxLtT.jpeg' 49 | }); 50 | 51 | new Vue({ 52 | render: h => h(App) 53 | }).$mount('#app'); 54 | -------------------------------------------------------------------------------- /src/style/Pagination.scss: -------------------------------------------------------------------------------- 1 | @import './common/var.scss'; 2 | @import './common/extend.scss'; 3 | @import './common/mixin.scss'; 4 | @import './config/index.scss'; 5 | 6 | @include b(pagination) { 7 | cursor: pointer; 8 | color: #606266; 9 | align-items: center; 10 | display: inline-flex; 11 | justify-content: center; 12 | .btn-prev { 13 | border: none; 14 | outline: none; 15 | background-color: transparent; 16 | &:hover { 17 | color: $--color-nomal 18 | } 19 | } 20 | 21 | .total-number { 22 | font-size: 13px; 23 | margin-right: 5px; 24 | } 25 | 26 | @include e(box) { 27 | flex-wrap: wrap; 28 | display: inline-flex; 29 | &>li { 30 | min-width: 30px; 31 | margin: 3px 5px; 32 | padding: 4px 8px; 33 | @include flexCenter(); 34 | @include when(active) { 35 | color: $--color-nomal 36 | } 37 | &:hover { 38 | color: $--color-nomal 39 | } 40 | } 41 | } 42 | .ground { 43 | background-color: #f4f4f5; 44 | border-radius: 4px; 45 | &:hover { 46 | color: white; 47 | background-color: $--color-nomal; 48 | } 49 | } 50 | .ground-box { // 背景色是关键字 51 | &>li { 52 | @extend .ground; 53 | } 54 | &>.is-active{ 55 | color: white; 56 | background-color: $--color-nomal; 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /src/style/Tree.scss: -------------------------------------------------------------------------------- 1 | @import './common/var.scss'; 2 | @import './common/mixin.scss'; 3 | @import './common/extend.scss'; 4 | @import "./common/animation.scss"; 5 | 6 | @include b(tree) { 7 | color: $--color-weak; 8 | font-size: 14px; 9 | font-weight: 400; 10 | text-indent: 1em; 11 | @include e(item) { 12 | margin: 2px 0; 13 | } 14 | @include e(content) { 15 | position: relative; 16 | padding: 3px 0; 17 | &:hover { 18 | background-color: $--color-difference; 19 | } 20 | } 21 | @include e(icon) { 22 | overflow: hidden; 23 | position: relative; 24 | display: inline-block; 25 | border-bottom: 3px solid $--color-danger; 26 | width: .5em; 27 | height: .5em; 28 | margin-right:5px; 29 | transform: scale(.8); 30 | transition: all 0.3s; 31 | &::after { 32 | content: ''; 33 | display: block; 34 | position: absolute; 35 | background-color: $--color-danger; 36 | left: 50%; 37 | width: 80%; 38 | height: 100%; 39 | } 40 | } 41 | @include e(checkbox){ 42 | margin-right: 5px; 43 | transform: translateY(-1px) 44 | } 45 | .is-open { 46 | transform: rotate(90deg) scale(1); 47 | } 48 | .is-active{ 49 | background-color: #F5F7FA; 50 | } 51 | .is-all__check{ 52 | @extend .check-ball; 53 | background-color: $--color-warning; 54 | } 55 | .is-all__nocheck{ 56 | @extend .check-ball; 57 | background-color: $--color-nomal; 58 | } 59 | } -------------------------------------------------------------------------------- /src/style/Input.scss: -------------------------------------------------------------------------------- 1 | @import './common/var.scss'; 2 | @import './common/extend.scss'; 3 | @import './common/mixin.scss'; 4 | @import './config/index.scss'; 5 | 6 | @include b(input) { 7 | cursor: pointer; 8 | position: relative; 9 | align-items: center; 10 | display: inline-flex; 11 | background-color: white; 12 | transition: all .3s; 13 | @include b(input__inner) { 14 | border: none; 15 | flex: 1; 16 | width: 100%; 17 | font-size: 1em; 18 | padding: 9px 16px; 19 | &:focus { outline: 0; } 20 | @include placeholder{ 21 | color: $--color-input-placeholder; 22 | } 23 | }; 24 | @include b(input__prefix) { 25 | align-items: center; 26 | display: inline-flex; 27 | &:hover{transform: scale(1.1)} 28 | @include when(left) { 29 | padding-left:6px; 30 | } 31 | @include when(right) { 32 | padding-right:6px; 33 | } 34 | }; 35 | @include b(input__clear){ 36 | position: absolute; 37 | right: 24px; 38 | &:hover{ animation: size .5s infinite linear;} 39 | }; 40 | @include b(input--input__disabled){ 41 | @include commonShadow(disabled); 42 | }; 43 | @at-root { 44 | @include b(input__normal){ 45 | @include commonShadow($--color-black); 46 | &:hover { 47 | z-index: 6; 48 | transform: scale(1.2); 49 | } 50 | } 51 | @include b(input__error){ 52 | @include commonShadow(danger); 53 | } 54 | @include b(input__abnormal){ 55 | @include commonShadow($--color-black); 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /src/style/Button.scss: -------------------------------------------------------------------------------- 1 | @import './common/var.scss'; 2 | @import './common/mixin.scss'; 3 | @import './common/extend.scss'; 4 | 5 | @include b(button) { 6 | cursor: pointer; 7 | overflow: hidden; 8 | position: relative; 9 | align-items: center; 10 | display: inline-flex; 11 | vertical-align: middle; 12 | justify-content: center; 13 | background-color: white; 14 | outline: 0; 15 | border-radius: 6px; 16 | transition: all 0.1s; 17 | 18 | &.size-big { 19 | font-size: $--size-big; 20 | padding: 6px 11px; 21 | } 22 | 23 | &.size-normal { 24 | font-size: $--size-nomal; 25 | padding: 4px 8px; 26 | } 27 | 28 | &.size-small { 29 | font-size: $--size-small; 30 | padding: 2px 6px; 31 | } 32 | 33 | &:not(.is-disabled) { 34 | &:active { 35 | box-shadow: none; 36 | opacity: 0.7; 37 | transform: translateX(2px) translateY(2px) scale(0.9); 38 | } 39 | 40 | &:hover { 41 | background-color:rgba(0, 0, 0, 0.01) 42 | } 43 | } 44 | 45 | @include commonShadow($--color-black); 46 | 47 | @include when(disabled) { 48 | @include commonShadow(disabled); 49 | } 50 | 51 | @include when(left) { 52 | border-radius: 16px 0 0 16px; 53 | } 54 | 55 | ; 56 | 57 | @include when(right) { 58 | border-radius: 0 16px 16px 0; 59 | } 60 | 61 | ; 62 | 63 | @include when(centre) { 64 | border-radius: 0; 65 | } 66 | 67 | @at-root { 68 | @include commonType(cc-button--); 69 | .is-bling { 70 | &:hover { 71 | @extend .cc-bling; 72 | } 73 | } 74 | }; 75 | } -------------------------------------------------------------------------------- /src/style/Ske.scss: -------------------------------------------------------------------------------- 1 | @import './common/var.scss'; 2 | @import './common/mixin.scss'; 3 | @import './common/extend.scss'; 4 | 5 | @include b(ske) { 6 | background-color: white; 7 | @include position(fixed); 8 | 9 | @include e(box) { 10 | overflow: hidden; 11 | @include position(absolute, 30px); 12 | } 13 | 14 | @include e(base) { 15 | background-color: #F6F6F6; 16 | width: 100%; 17 | z-index: -1; // 为了伪类能够被挡住 18 | } 19 | 20 | @include e(round) { 21 | display: flex; 22 | position: absolute; 23 | align-items: center; 24 | justify-content: center; 25 | background-color: white; 26 | left: 0px; 27 | width: 180px; 28 | height: 180px; 29 | 30 | &::after { 31 | content: ''; 32 | position: absolute; 33 | background-color: #F6F6F6; 34 | width: 70%; 35 | height: 70%; 36 | border-radius: 50%; 37 | } 38 | } 39 | 40 | @include e(rec) { 41 | position: absolute; 42 | background-color: #F6F6F6; 43 | left: 0px; 44 | bottom: 0; 45 | right: 0px; 46 | height: 300px; 47 | @at-root { 48 | @include m(big) { 49 | position: absolute; 50 | background-color: #F6F6F6; 51 | border-right: 20px solid white; 52 | top: 0px; 53 | left: 0px; 54 | width: 260px; 55 | height: 100%; 56 | } 57 | } 58 | } 59 | .across { 60 | // 透明的白色, 惊艳了 61 | background-color: white; 62 | animation: pass 2s infinite linear; 63 | width: 30px; 64 | opacity: 0.8; 65 | height: 2000px; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/(不会被打包)App/计数器.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 49 | -------------------------------------------------------------------------------- /src/components/Tab/main/tab.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | -------------------------------------------------------------------------------- /tests/unit/Input.test.js: -------------------------------------------------------------------------------- 1 | import { shallowMount } from '@vue/test-utils'; 2 | import Input from '../../src/components/Input'; 3 | import { findTestWrapper } from '../utils/util'; 4 | 5 | 6 | describe('测试Input组件', () => { 7 | 8 | it('1: 可以渲染出Input组件', () => { 9 | const wrapper = shallowMount(Input); 10 | expect(wrapper.contains('input')).toBe(true); 11 | }); 12 | 13 | it('2: 输入value与显示的内容相同, 并且修改联动', () => { 14 | const wrapper = shallowMount(Input,{ 15 | propsData:{ 16 | value:'内容1' 17 | } 18 | }); 19 | const input = findTestWrapper(wrapper,'input').at(0); 20 | expect(input.element.value).toBe('内容1') 21 | // 改变也随之改变 22 | wrapper.setProps({ value: '内容2' }) 23 | expect(input.element.value).toBe('内容2') 24 | }); 25 | 26 | it('3: 清除内容按钮有效', () => { 27 | const wrapper = shallowMount(Input,{ 28 | propsData:{ 29 | value:'内容1', 30 | clear:true 31 | } 32 | }); 33 | // hover 时候才会出现!! 34 | wrapper.setData({ 35 | hovering:true 36 | }) 37 | const clear = findTestWrapper(wrapper,'clear').at(0); 38 | expect(clear).toBeTruthy(); 39 | 40 | clear.trigger('click'); 41 | 42 | expect(wrapper.emitted().input).toBeTruthy(); 43 | }); 44 | 45 | it('4: 传入icon参数, 可以显示icon组件', () => { 46 | const wrapper = shallowMount(Input,{ 47 | propsData:{ 48 | icon:'cc-up' 49 | } 50 | }); 51 | const icon = findTestWrapper(wrapper,'icon').at(0); 52 | expect(icon).toBeTruthy(); 53 | }); 54 | 55 | it('5: 切换type, 出现文本框', () => { 56 | const wrapper = shallowMount(Input,{ 57 | propsData:{ 58 | type:'textarea' 59 | } 60 | }); 61 | const textarea = findTestWrapper(wrapper,'textarea').at(0); 62 | expect(textarea).toBeTruthy(); 63 | }); 64 | 65 | }); 66 | -------------------------------------------------------------------------------- /src/components/loading/main/loading.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | -------------------------------------------------------------------------------- /src/components/Ske/main/ske.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 38 | 39 | 86 | -------------------------------------------------------------------------------- /src/style/DatePicker.scss: -------------------------------------------------------------------------------- 1 | @import './common/var.scss'; 2 | @import './common/mixin.scss'; 3 | @import './common/extend.scss'; 4 | 5 | @include b(date) { 6 | position: relative; 7 | display: inline-block; 8 | 9 | @include b(date-input) { 10 | border: 1px solid $--color-disabled; 11 | outline: 0px; 12 | padding: 8px; 13 | font-size: 16px; 14 | border-radius: 7px; 15 | } 16 | 17 | @include b(date-pannel) { 18 | position: fixed; 19 | background-color: $--color-white; 20 | border: 1px solid $--color-disabled; 21 | width: 280px; 22 | padding: 8px; 23 | border-radius: 7px; 24 | 25 | .pannel-nav { 26 | display: flex; 27 | align-items: center; 28 | justify-content: space-around; 29 | box-shadow: 0px 2px 2px 2px $--color-difference; 30 | padding: 6px 0; 31 | margin-bottom: 10px; 32 | 33 | .pannel-selected { 34 | text-align: center; 35 | width: 160px; 36 | } 37 | 38 | &>span { 39 | &:hover { 40 | cursor: pointer; 41 | color: $--color-nomal 42 | } 43 | } 44 | } 45 | 46 | .pannel-content { 47 | box-shadow: 0px 2px 2px 2px $--color-difference; 48 | 49 | ul { 50 | display: flex; 51 | } 52 | 53 | li { 54 | text-align: center; 55 | flex: 1; 56 | height: 35px; 57 | line-height: 35px; 58 | } 59 | 60 | .read-only { 61 | color: $--color-disabled; 62 | } 63 | 64 | .active-date { 65 | @extend .active-item; 66 | } 67 | 68 | .pannel-content__item { 69 | cursor: pointer; 70 | border: 1px solid $--color-difference; 71 | 72 | li:not(.read-only) { 73 | transition: all .2s; 74 | transform: scale(.8); 75 | 76 | &:hover { 77 | transform: scale(1.3); 78 | @extend .active-item; 79 | } 80 | } 81 | } 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /src/(不会被打包)App/tree.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | -------------------------------------------------------------------------------- /src/style/Tab.scss: -------------------------------------------------------------------------------- 1 | @import './common/var.scss'; 2 | @import './common/mixin.scss'; 3 | @import './common/extend.scss'; 4 | 5 | @include b(tab) { 6 | @include brother(nav) { 7 | display: flex; 8 | flex-wrap: nowrap; 9 | text-align: center; 10 | border-bottom: 1px solid #eee; 11 | margin-bottom: 10px; 12 | &>li { 13 | cursor: pointer; 14 | display: flex; 15 | position: relative; 16 | align-items: center; 17 | border-bottom: none; 18 | background-color: white; 19 | padding: 10px 20px; 20 | transition: all 0.2s; 21 | &:hover { 22 | transform: scale(0.8) 23 | }; 24 | &::after { 25 | content: ''; 26 | position: absolute; 27 | left: 6px; 28 | bottom: 0; 29 | right: 6px; 30 | transform: scale(0); 31 | transition: all 0.2s; 32 | } 33 | @include when(active) { 34 | color: $--color-nomal; 35 | &::after { 36 | border-bottom: 2px solid $--color-nomal; 37 | transform: scale(1); 38 | } 39 | } 40 | } 41 | @include when(card) { 42 | &::after { 43 | display: none 44 | } 45 | &>li { 46 | border-bottom: none; 47 | border: 1px solid #eee; 48 | &:hover { 49 | transform: scale(1) 50 | } 51 | }; 52 | &>li+li { 53 | border-left: none 54 | }; 55 | &>.is-active { 56 | border-bottom: none; 57 | &::after { 58 | content: ''; 59 | position: absolute; 60 | border-bottom: 2px solid white; 61 | left: 0; 62 | right: 0; 63 | bottom: -1px; 64 | } 65 | }; 66 | &>:nth-last-child(1) { 67 | border-top-right-radius: 7px; 68 | }; 69 | &>:nth-child(1) { 70 | border-top-left-radius: 7px; 71 | }; 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/components/Lazy/main/lazy.js: -------------------------------------------------------------------------------- 1 | import { 2 | getScrollOffset, 3 | getViewportSize, 4 | getHTMLScroll 5 | } from '@/assets/js/utils'; 6 | class Lazy { 7 | install(Vue, options) { 8 | this.vm = Vue; 9 | this.timeEl = ''; 10 | this.list = new Set(); 11 | this.time = options.time; 12 | this.error = options.error; 13 | this.loadingImg = options.loadingImg; 14 | this.initDirective(); 15 | this.initScroll(); 16 | } 17 | initDirective() { 18 | this.vm.directive('lazy', { 19 | bind: (el, data) => { 20 | if(this.loadingImg){ 21 | el.setAttribute('src', this.loadingImg); 22 | } 23 | this.list.add({ oImg: el, path: data.value }); 24 | } 25 | }); 26 | this.vm.directive('lazy-box', { 27 | bind: el => { 28 | this.whetherHandle(); 29 | el.addEventListener('scroll', this.whetherHandle.bind(this), false); 30 | } 31 | }); 32 | } 33 | initScroll() { 34 | // 不管怎么样, 默认先把body监控起来把 35 | // 先触发一次, 第一屏 36 | this.whetherHandle(); 37 | window.addEventListener('scroll', this.whetherHandle.bind(this), false); 38 | } 39 | whetherHandle() { 40 | // 完全停下的一瞬间开始计时 41 | clearTimeout(this.timeEl); 42 | this.timeEl = setTimeout(() => { 43 | this.handleScroll(); 44 | }, this.time); 45 | } 46 | handleScroll() { 47 | for (let item of this.list) { 48 | if (this.isNoLoading(item.oImg)) { 49 | this.list.delete(item); 50 | } else { 51 | this.handleSrc(item); 52 | } 53 | } 54 | } 55 | // 处理该不该显示的问题 56 | handleSrc(item) { 57 | let { oImg, path } = item, 58 | { top: top1, left: left1 } = getHTMLScroll(oImg), 59 | { top: top2, left: left2 } = getScrollOffset(), 60 | { width, height } = getViewportSize(), 61 | height2 = oImg.offsetHeight / 2, 62 | width2 = oImg.offsetWidth / 2; 63 | if (top1 - top2 + height2 > 0 && top1 - top2 + height2 < height) { 64 | if (left1 - left2 + width2 > 0 && left1 - left2 + width2 < width) { 65 | oImg.onerror = ()=>{ 66 | oImg.setAttribute('src', this.error); 67 | 68 | } 69 | oImg.setAttribute('src', path); 70 | } 71 | } 72 | } 73 | 74 | // 工具类 75 | isNoLoading(item){ 76 | if(!item)return false 77 | if(item && item.src === this.loadingImg) return false 78 | return true 79 | } 80 | } 81 | 82 | export default new Lazy(); 83 | -------------------------------------------------------------------------------- /src/components/Button/main/button.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 90 | -------------------------------------------------------------------------------- /src/(不会被打包)App/tab.vue: -------------------------------------------------------------------------------- 1 | 59 | 60 | 81 | -------------------------------------------------------------------------------- /src/components/Toast/main/toast.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | -------------------------------------------------------------------------------- /src/(不会被打包)App/懒加载.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 46 | 63 | -------------------------------------------------------------------------------- /src/components/Alert/main/alert.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 103 | -------------------------------------------------------------------------------- /src/components/InputNumber/main/input-number.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 28 | 29 | 111 | -------------------------------------------------------------------------------- /src/assets/js/utils.js: -------------------------------------------------------------------------------- 1 | // 加.0 toFixed会四舍五入的 2 | export const myToFixed = value => { 3 | value = value + ''; 4 | if (value.includes('.')) { 5 | let sp = value.split('.'); 6 | return sp[0] + '.' + sp[1].slice(0, 1); 7 | } else { 8 | return value + '.0'; 9 | } 10 | }; 11 | 12 | // 算元素距离body的距离 13 | export function getHTMLScroll(node) { 14 | // node.getBoundingClientRect() 其实这一个函数就行了 15 | if (!node) return; 16 | let result = { top: 0, left: 0 }, 17 | parent = node.offsetParent || node.parentNode, 18 | children = node; // 获取定位父级 19 | let task = son => { 20 | let dom = son.parentNode; 21 | if (!dom) return; 22 | if (parent === dom) { 23 | let domScrollTop = dom.scrollTop || 0, 24 | domScrollLeft = dom.scrollLeft || 0; 25 | result.top += children.offsetTop - domScrollTop; 26 | result.left += children.offsetLeft - domScrollLeft; 27 | children = parent; 28 | parent = dom.offsetParent; // 下一个父级 29 | } else { 30 | let domScrollTop = dom.scrollTop || 0, 31 | domScrollLeft = dom.scrollLeft || 0; 32 | result.top -= domScrollTop; 33 | result.left -= domScrollLeft; 34 | } 35 | 36 | let pos = window.getComputedStyle(dom, null).position; 37 | if (pos === 'fixed') { 38 | result.top += dom.offsetTop; 39 | result.left += dom.offsetLeft; 40 | return; 41 | } 42 | if (dom.nodeName !== 'BODY') { 43 | task(dom); 44 | } 45 | }; 46 | task(node); 47 | return result; 48 | } 49 | 50 | export function getViewportSize() { 51 | if (window.innerHeight) { 52 | return { 53 | width: window.innerWidth, 54 | height: window.innerHeight 55 | }; 56 | } else { 57 | if (document.compatMode === 'BackCompat') { 58 | return { 59 | width: document.body.clientWidth, 60 | height: document.body.clientHeight 61 | }; 62 | } else { 63 | return { 64 | width: document.documentElement.clientWidth, 65 | height: document.documentElement.clientHeight 66 | }; 67 | } 68 | } 69 | } 70 | 71 | export function getScrollOffset() { 72 | if (window.pageXOffset) { 73 | return { 74 | left: window.pageXOffset, 75 | top: window.pageYOffset 76 | }; 77 | } else { 78 | // 问题: 为什么要相加 79 | // 因为这两个属性只有一个有用, 另一个肯定是0, 索性直接相加 80 | return { 81 | left: document.body.scrollLeft + document.documentElement.scrollLeft, 82 | top: document.body.scrollTop + document.documentElement.scrollTop 83 | }; 84 | } 85 | } 86 | 87 | export function inspect(min) { 88 | return function(value) { 89 | if (value < min || value !== ~~value) { 90 | throw new Error(`最小为${min}的整数`); 91 | } 92 | return true; 93 | }; 94 | } 95 | 96 | // 添加事件, element-ui判断是不是服务器环境 97 | export function on(element, event, handler) { 98 | if (element && event && handler) { 99 | element.addEventListener(event, handler, false); 100 | } 101 | } 102 | // 移除事件 103 | export function off(element, event, handler) { 104 | if (element && event) { 105 | element.removeEventListener(event, handler, false); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/assets/js/vuePopper.js: -------------------------------------------------------------------------------- 1 | // 流体bug未解决, 需要多次获取dom, 感觉完全没必要 2 | import { getScrollOffset } from './utils'; 3 | export function getPopoverPosition(popover, content, direction,CONTANT ) { 4 | // 只负责启动, 自己去检测 5 | // 优化一些, 参数获取函数 6 | let result = { show: true }; 7 | getOptions(result, popover, content, direction,CONTANT ); 8 | let { left, top } = getScrollOffset(); 9 | result.left += left; 10 | result.top += top; 11 | return result; 12 | } 13 | // 手打比 循环省性能 14 | const list = [ 15 | 'top-end', 16 | 'left-end', 17 | 'top-start', 18 | 'right-end', 19 | 'top-middle', 20 | 'bottom-end', 21 | 'left-start', 22 | 'right-start', 23 | 'left-middle', 24 | 'right-middle', 25 | 'bottom-start', 26 | 'bottom-middle' 27 | ]; 28 | // 只要获取一次就行, 不要浪费性能 29 | function getOptions(result, popover, content, direction,CONTANT = 10) { 30 | let myList = list.concat(), 31 | client = popover.getBoundingClientRect(); 32 | myList.splice(list.indexOf(direction), 1); 33 | getDirection(result, { 34 | myList, 35 | direction, 36 | CONTANT, 37 | top: client.top, 38 | left: client.left, 39 | popoverWidth: popover.offsetWidth, 40 | contentWidth: content.offsetWidth, 41 | popoverHeight: popover.offsetHeight, 42 | contentHeight: content.offsetHeight 43 | }); 44 | } 45 | function getDirection(result, options) { 46 | let { 47 | top, 48 | left, 49 | CONTANT, 50 | direction, 51 | contentWidth, 52 | popoverWidth, 53 | contentHeight, 54 | popoverHeight 55 | } = options; 56 | result.options = options; 57 | let main = direction.split('-')[0], 58 | around = direction.split('-')[1]; 59 | if (main === 'top' || main === 'bottom') { 60 | if (around === 'start') { 61 | result.left = left; 62 | } else if (around === 'end') { 63 | result.left = left + popoverWidth - contentWidth; 64 | } else if (around === 'middle') { 65 | result.left = left + popoverWidth / 2 - contentWidth / 2; 66 | } 67 | if (main === 'top') { 68 | result.top = top - contentHeight - CONTANT; 69 | } else { 70 | result.top = top + popoverHeight + CONTANT; 71 | } 72 | } else if (main === 'left' || main === 'right') { 73 | if (around === 'start') { 74 | result.top = top; 75 | } else if (around === 'end') { 76 | result.top = top + popoverHeight - contentHeight; 77 | } else if (around === 'middle') { 78 | result.top = top + popoverHeight / 2 - contentHeight / 2; 79 | } 80 | if (main === 'left') { 81 | result.left = left - contentWidth - CONTANT; 82 | } else { 83 | result.left = left + popoverWidth + CONTANT; 84 | } 85 | } 86 | 87 | testDirection(result, options); 88 | } 89 | 90 | function testDirection(result, options) { 91 | let { left, top } = result, 92 | width = document.documentElement.clientWidth, 93 | height = document.documentElement.clientHeight; 94 | if ( 95 | top < 0 || 96 | left < 0 || 97 | top + options.contentHeight > height || 98 | left + options.contentWidth > width 99 | ) { 100 | // 还有可以循环的 101 | if (options.myList.length) { 102 | options.direction = options.myList.shift(); 103 | getDirection(result, options); 104 | } else { 105 | // 实在不行就在父级身上 106 | result.left = options.left; 107 | result.right = options.right; 108 | } 109 | } else { 110 | result.show = true; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/style/common/mixin.scss: -------------------------------------------------------------------------------- 1 | @import '../config/index.scss'; 2 | @import '../common/var.scss'; 3 | 4 | @mixin commonShadow($color,$size:2px) { 5 | @if $color== 'success' { 6 | $color: $--color-success; 7 | } 8 | 9 | @if $color== 'warning' { 10 | $color: $--color-warning; 11 | } 12 | 13 | @if $color== 'danger' { 14 | $color: $--color-danger; 15 | } 16 | 17 | @if $color== 'disabled' { 18 | cursor: not-allowed; 19 | $color: $--color-disabled; 20 | } 21 | 22 | color: $color; 23 | border: 1px solid $color; 24 | box-shadow: $size $size $color; 25 | } 26 | 27 | @mixin commonType($name) { 28 | @each $type in (success, warning, danger) { 29 | .#{$name}#{$type} { 30 | @include commonShadow($type); 31 | } 32 | } 33 | } 34 | 35 | @mixin b($block) { 36 | // !global与!defult相对立, 优先与默认编译 37 | $B: $namespace + '-' + $block !global; 38 | 39 | .#{$B} { 40 | @content; 41 | } 42 | } 43 | 44 | @mixin when($state) { 45 | @at-root { 46 | &.#{$state-prefix + $state} { 47 | @content; 48 | } 49 | } 50 | } 51 | 52 | @mixin brother($name) { 53 | &>&#{-$name} { 54 | @content; 55 | } 56 | } 57 | 58 | @mixin e($name) { 59 | &#{$cc-separator}#{$name} { 60 | @content; 61 | } 62 | } 63 | 64 | @mixin m($name) { 65 | &#{$cc-modifier-separator}#{$name} { 66 | @content; 67 | } 68 | } 69 | 70 | 71 | 72 | @mixin placeholder { 73 | &::-webkit-input-placeholder { 74 | @content; 75 | } 76 | 77 | &::-moz-placeholder { 78 | @content; 79 | } 80 | 81 | &:-ms-input-placeholder { 82 | @content; 83 | } 84 | } 85 | 86 | @mixin position($position: absolute, $long: 0) { 87 | margin: auto; 88 | position: $position; 89 | top: $long; 90 | left: $long; 91 | right: $long; 92 | bottom: $long; 93 | } 94 | 95 | @mixin flexCenter ($direction:column) { 96 | display: flex; 97 | align-items: center; 98 | justify-content: center; 99 | flex-direction: $direction; 100 | } 101 | 102 | 103 | @mixin triangleAfter ($where){ 104 | &::after{ 105 | content: ''; 106 | position: absolute; 107 | border: 6px solid white; 108 | border-top-color: transparent; 109 | border-left-color: transparent; 110 | border-right-color: transparent; 111 | border-bottom-color: transparent; 112 | @if $where== 'top' { 113 | bottom: -11px; 114 | border-top-color: white; 115 | } 116 | @if $where== 'right' { 117 | left: -11px; 118 | border-right-color:white; 119 | } 120 | @if $where== 'left' { 121 | right: -11px; 122 | border-left-color:white; 123 | } 124 | @if $where== 'bottom' { 125 | top: -11px; 126 | border-bottom-color: white; 127 | } 128 | @content; 129 | }; 130 | } 131 | @mixin triangleBefore ($where){ 132 | &::before{ 133 | content: ''; 134 | position: absolute; 135 | border: 6px solid rgb(172, 171, 171); 136 | border-top-color: transparent; 137 | border-left-color: transparent; 138 | border-right-color: transparent; 139 | border-bottom-color: transparent; 140 | @if $where== 'top' { 141 | bottom: -12px; 142 | border-top-color: rgb(172, 171, 171); 143 | } 144 | @if $where== 'right' { 145 | left:-12px; 146 | border-right-color: rgb(172, 171, 171); 147 | } 148 | @if $where== 'left' { 149 | right: -12px; 150 | border-left-color: rgb(172, 171, 171); 151 | } 152 | @if $where== 'bottom' { 153 | top: -12px; 154 | border-bottom-color: rgb(172, 171, 171); 155 | } 156 | @content; 157 | }; 158 | } -------------------------------------------------------------------------------- /src/(不会被打包)App/小星星.vue: -------------------------------------------------------------------------------- 1 | 108 | 109 | 129 | -------------------------------------------------------------------------------- /src/style/Popover.scss: -------------------------------------------------------------------------------- 1 | @import './common/var.scss'; 2 | @import './common/extend.scss'; 3 | @import './common/mixin.scss'; 4 | @import './config/index.scss'; 5 | 6 | 7 | @include b(popover) { 8 | position: relative; 9 | display: inline-block; 10 | @include e(box) { 11 | overflow: auto; 12 | } 13 | @include e(content) { 14 | position: absolute; 15 | color: $--color-weak; 16 | background-color: white; 17 | box-shadow: 0px 0px 3px rgb(172, 171, 171); 18 | top: 100px; 19 | left: 100px; 20 | padding: 6px; 21 | @at-root { 22 | .top-end { 23 | @include triangleAfter(top){ 24 | right:5px; 25 | }; 26 | @include triangleBefore(top){ 27 | right:5px; 28 | }; 29 | } 30 | .top-middle { 31 | @include triangleAfter(top){ 32 | left: calc(50% - 6px); 33 | }; 34 | @include triangleBefore(top){ 35 | left: calc(50% - 6px); 36 | }; 37 | } 38 | .top-start { 39 | @include triangleAfter(top){ 40 | left: 5px 41 | }; 42 | @include triangleBefore(top){ 43 | left: 5px 44 | }; 45 | } 46 | 47 | .bottom-end { 48 | @include triangleAfter(bottom){ 49 | right:5px; 50 | }; 51 | @include triangleBefore(bottom){ 52 | right:5px; 53 | }; 54 | } 55 | .bottom-middle { 56 | @include triangleAfter(bottom){ 57 | left: calc(50% - 6px); 58 | }; 59 | @include triangleBefore(bottom){ 60 | left: calc(50% - 6px); 61 | }; 62 | } 63 | .bottom-start { 64 | @include triangleAfter(bottom){ 65 | left: 5px 66 | }; 67 | @include triangleBefore(bottom){ 68 | left: 5px 69 | }; 70 | } 71 | 72 | .right-end { 73 | @include triangleAfter(right){ 74 | bottom: 5px; 75 | }; 76 | @include triangleBefore(right){ 77 | bottom: 5px; 78 | }; 79 | } 80 | .right-middle { 81 | @include triangleAfter(right){ 82 | top: calc(50% - 6px); 83 | }; 84 | @include triangleBefore(right){ 85 | top: calc(50% - 6px); 86 | }; 87 | } 88 | .right-start { 89 | @include triangleAfter(right){ 90 | top: 5px 91 | }; 92 | @include triangleBefore(right){ 93 | top: 5px 94 | }; 95 | } 96 | 97 | 98 | .left-end { 99 | @include triangleAfter(left){ 100 | bottom: 5px; 101 | }; 102 | @include triangleBefore(left){ 103 | bottom: 5px; 104 | }; 105 | } 106 | .left-middle { 107 | @include triangleAfter(left){ 108 | top: calc(50% - 6px); 109 | }; 110 | @include triangleBefore(left){ 111 | top: calc(50% - 6px); 112 | }; 113 | } 114 | .left-start { 115 | @include triangleAfter(left){ 116 | top: 5px 117 | }; 118 | @include triangleBefore(left){ 119 | top: 5px 120 | }; 121 | } 122 | } 123 | } 124 | } -------------------------------------------------------------------------------- /src/components/Pagination/main/pagination.vue: -------------------------------------------------------------------------------- 1 | 2 | 38 | 39 | 129 | -------------------------------------------------------------------------------- /src/components/Rate/main/rate.vue: -------------------------------------------------------------------------------- 1 | 2 | 36 | 37 | 156 | -------------------------------------------------------------------------------- /src/components/Tree/main/tree.vue: -------------------------------------------------------------------------------- 1 | 2 | 40 | 41 | -------------------------------------------------------------------------------- /src/components/Popover/main/popover.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 155 | -------------------------------------------------------------------------------- /src/components/DatePicker/main/datePicker.vue: -------------------------------------------------------------------------------- 1 | 2 | 50 | 51 | 182 | -------------------------------------------------------------------------------- /src/(不会被打包)App/按钮输入.vue: -------------------------------------------------------------------------------- 1 | 137 | 138 | 179 | -------------------------------------------------------------------------------- /src/components/Input/main/input.vue: -------------------------------------------------------------------------------- 1 |