├── .browserslistrc ├── .eslintrc.js ├── .gitignore ├── README.md ├── babel.config.js ├── jest.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── public └── index.html ├── src ├── (不会被打包)App │ ├── popover.vue │ ├── tab.vue │ ├── tree.vue │ ├── 分页器.vue │ ├── 啊!声明.md │ ├── 小星星.vue │ ├── 弹出框.vue │ ├── 懒加载.vue │ ├── 按钮输入.vue │ ├── 日期选择器.vue │ ├── 计数器.vue │ └── 骨架.vue ├── assets │ └── js │ │ ├── Clickoutside.js │ │ ├── handelDate.js │ │ ├── prevent.js │ │ ├── utils.js │ │ └── vuePopper.js ├── components │ ├── Alert │ │ ├── index.js │ │ └── main │ │ │ ├── alert.js │ │ │ └── alert.vue │ ├── Button │ │ ├── index.js │ │ └── main │ │ │ └── button.vue │ ├── DatePicker │ │ ├── index.js │ │ └── main │ │ │ └── datePicker.vue │ ├── Icon │ │ ├── index.js │ │ └── main │ │ │ └── icon.vue │ ├── Input │ │ ├── index.js │ │ └── main │ │ │ └── input.vue │ ├── InputNumber │ │ ├── index.js │ │ └── main │ │ │ └── input-number.vue │ ├── Lazy │ │ ├── index.js │ │ └── main │ │ │ └── lazy.js │ ├── Pagination │ │ ├── index.js │ │ └── main │ │ │ └── pagination.vue │ ├── Popover │ │ ├── index.js │ │ └── main │ │ │ ├── index.js │ │ │ └── popover.vue │ ├── Rate │ │ ├── index.js │ │ └── main │ │ │ └── rate.vue │ ├── Ske │ │ ├── index.js │ │ └── main │ │ │ └── ske.vue │ ├── Tab │ │ ├── index.js │ │ └── main │ │ │ ├── tab-pane.vue │ │ │ └── tab.vue │ ├── Toast │ │ ├── index.js │ │ └── main │ │ │ ├── toast.js │ │ │ └── toast.vue │ ├── Tree │ │ ├── index.js │ │ └── main │ │ │ └── tree.vue │ ├── index.js │ └── loading │ │ ├── index.js │ │ └── main │ │ ├── loading.js │ │ └── loading.vue ├── main.js └── style │ ├── Alert.scss │ ├── Button.scss │ ├── DatePicker.scss │ ├── Icon.scss │ ├── Input.scss │ ├── Loading.scss │ ├── Pagination.scss │ ├── Popover.scss │ ├── Rate.scss │ ├── Ske.scss │ ├── Tab.scss │ ├── Toast.scss │ ├── Tree.scss │ ├── common │ ├── animation.scss │ ├── extend.scss │ ├── mixin.scss │ ├── reset.scss │ └── var.scss │ ├── config │ └── index.scss │ ├── fonts │ └── iconfont.js │ ├── index.scss │ └── inputNumber.scss └── tests ├── unit ├── .eslintrc.js ├── Button.test.js ├── Input.test.js ├── Loading.test.js ├── Pagination.test.js └── Toast.test.js └── utils └── util.js /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not ie <= 8 4 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | cc_ui 9 | 10 | 11 | 14 |
15 | 16 | 17 | -------------------------------------------------------------------------------- /src/(不会被打包)App/popover.vue: -------------------------------------------------------------------------------- 1 | 27 | -------------------------------------------------------------------------------- /src/(不会被打包)App/tab.vue: -------------------------------------------------------------------------------- 1 | 59 | 60 | 81 | -------------------------------------------------------------------------------- /src/(不会被打包)App/tree.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | -------------------------------------------------------------------------------- /src/(不会被打包)App/分页器.vue: -------------------------------------------------------------------------------- 1 | 28 | -------------------------------------------------------------------------------- /src/(不会被打包)App/啊!声明.md: -------------------------------------------------------------------------------- 1 | ### 特别声明 2 | 1. 此处为示例文件夹, 不会被打包到项目中, 所以中文命名也无妨. -------------------------------------------------------------------------------- /src/(不会被打包)App/小星星.vue: -------------------------------------------------------------------------------- 1 | 108 | 109 | 129 | -------------------------------------------------------------------------------- /src/(不会被打包)App/弹出框.vue: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | -------------------------------------------------------------------------------- /src/(不会被打包)App/懒加载.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 46 | 63 | -------------------------------------------------------------------------------- /src/(不会被打包)App/按钮输入.vue: -------------------------------------------------------------------------------- 1 | 137 | 138 | 179 | -------------------------------------------------------------------------------- /src/(不会被打包)App/日期选择器.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | -------------------------------------------------------------------------------- /src/(不会被打包)App/计数器.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 49 | -------------------------------------------------------------------------------- /src/(不会被打包)App/骨架.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | -------------------------------------------------------------------------------- /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/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/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/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/components/Alert/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './main/alert'; 2 | -------------------------------------------------------------------------------- /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/components/Alert/main/alert.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 103 | -------------------------------------------------------------------------------- /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/Button/main/button.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 90 | -------------------------------------------------------------------------------- /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/DatePicker/main/datePicker.vue: -------------------------------------------------------------------------------- 1 | 2 | 50 | 51 | 182 | -------------------------------------------------------------------------------- /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/Icon/main/icon.vue: -------------------------------------------------------------------------------- 1 | 20 | -------------------------------------------------------------------------------- /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/Input/main/input.vue: -------------------------------------------------------------------------------- 1 |