├── .browserslistrc ├── .gitignore ├── README.md ├── babel.config.js ├── doc ├── dragReport.md └── palette.md ├── package.json ├── postcss.config.js ├── public ├── favicon.ico └── index.html ├── src ├── App.vue ├── api │ ├── index.js │ └── service.js ├── assets │ ├── icons │ │ ├── iconfont.css │ │ ├── iconfont.eot │ │ ├── iconfont.js │ │ ├── iconfont.svg │ │ ├── iconfont.ttf │ │ ├── iconfont.woff │ │ ├── iconfont.woff2 │ │ └── index.css │ └── img │ │ ├── dragReport │ │ ├── col-1-0.png │ │ ├── col-12-0.png │ │ ├── col-12-1.png │ │ ├── col-12-2.png │ │ ├── col-16-1.png │ │ ├── col-2-0.png │ │ ├── col-24-0.png │ │ ├── col-24-1.png │ │ ├── col-3-0.png │ │ ├── col-4-0.png │ │ ├── col-4.8-1.png │ │ ├── col-4.8-2.png │ │ ├── col-4.8-3.png │ │ ├── col-4.8-4.png │ │ ├── col-4.8-5.png │ │ ├── col-6-0.png │ │ ├── col-8-0.png │ │ └── col-8-1.png │ │ └── palette │ │ └── lucency.png ├── components │ ├── color-picker │ │ ├── color.js │ │ ├── convert.js │ │ └── index.vue │ ├── custom-loading │ │ └── index.vue │ ├── custom-report │ │ ├── base │ │ │ ├── README.md │ │ │ ├── base-chart-bar.vue │ │ │ ├── base-chart-line.vue │ │ │ ├── base-chart-pie.vue │ │ │ ├── base-circle.vue │ │ │ ├── base-table.vue │ │ │ ├── circle-icon.vue │ │ │ ├── circle-three-text.vue │ │ │ └── three-line-text.vue │ │ ├── custom-report-component │ │ │ ├── chart │ │ │ │ ├── alarm-event-distribution.vue │ │ │ │ ├── alarm-interval-distribution.vue │ │ │ │ ├── bar-pie.vue │ │ │ │ ├── bar-stack.vue │ │ │ │ ├── driving-score-bar.vue │ │ │ │ ├── driving-score-line.vue │ │ │ │ ├── event-processing-report.vue │ │ │ │ ├── line-area.vue │ │ │ │ ├── line.vue │ │ │ │ └── pie.vue │ │ │ ├── group │ │ │ │ ├── table-pie.vue │ │ │ │ └── vehicle-score.vue │ │ │ ├── other │ │ │ │ ├── block.vue │ │ │ │ ├── circle-icon.vue │ │ │ │ └── circle-security-rate.vue │ │ │ ├── table │ │ │ │ └── block.vue │ │ │ └── text │ │ │ │ ├── text.vue │ │ │ │ └── three-line-text.vue │ │ ├── default-container.vue │ │ ├── js │ │ │ ├── chart-variable.js │ │ │ └── variable.js │ │ ├── mixins │ │ │ └── chart-default.js │ │ └── report-tool-select-query.vue │ ├── custom-scrollbar │ │ ├── bar.vue │ │ └── index.vue │ ├── default-framework │ │ └── index.vue │ ├── default-layout-editor │ │ └── index.vue │ ├── drag-group │ │ └── index.vue │ ├── drag-item │ │ └── index.vue │ └── te-icon │ │ └── index.vue ├── directive │ ├── clickoutside │ │ ├── clickoutside.js │ │ └── index.js │ ├── customLoading │ │ ├── customLoading.js │ │ ├── index.js │ │ └── service.js │ ├── dragDialog │ │ ├── dragDialog.js │ │ └── index.js │ └── waves │ │ ├── index.js │ │ ├── waves.css │ │ └── waves.js ├── layout │ └── home │ │ └── index.vue ├── main.js ├── mixins │ └── methods │ │ └── col-style.js ├── mock │ ├── index.js │ ├── modules │ │ └── getcustomccdeptreport.js │ └── variable.js ├── mount │ └── index.js ├── router │ └── index.js ├── styles │ ├── global.scss │ ├── mixin.scss │ └── variable.scss ├── utils │ ├── dom.js │ ├── drag.js │ ├── dragReport.js │ ├── exportPDF.js │ ├── index.js │ ├── paste.js │ ├── ramdaUtil.js │ ├── resize-event.js │ └── resize.js └── views │ ├── customLoading │ └── index.vue │ ├── customReportList │ ├── index.vue │ └── layout.scss │ ├── customScrollbar │ └── index.vue │ ├── dragDialog │ └── index.vue │ ├── dragList │ ├── index.vue │ └── layout.scss │ ├── dragReport │ ├── index.vue │ └── layout.scss │ ├── editComponent │ ├── index.vue │ └── layout.scss │ ├── palette │ └── index.vue │ ├── previewComponent │ └── index.vue │ ├── previewReport │ ├── index.js │ ├── index.vue │ └── layout.scss │ └── waves │ └── index.vue ├── static └── css │ └── reset.css ├── vue.config.js └── yarn.lock /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not ie <= 8 4 | -------------------------------------------------------------------------------- /.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 | 23 | # lock files 24 | yarn.lock -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-tiny-code 2 | 3 | 这里会有不少基于 `Vue` 这个棒棒的框架写的小玩意儿。 4 | 5 | 现有项目:[拖拽自定义报表](/doc/dragReport.md)、[仿 `chrome` 调色板](/doc/palette.md)。 6 | 7 | 具体可以查看 `src/router/index.js` 目录,当然,以后会做一个首页导航的啦。 8 | 9 | 项目目录结构取自于 [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin) 项目。 10 | 11 | 当然,不仅仅是自己的项目,也有很多日常看其他开源项目源码时候看到的有意思的代码,具体可以查阅 `src\directive`、`src\utils` 这两个目录,相信我,里面有不少好东西。 12 | 13 | ## 各项目文档说明 14 | 15 | [拖拽自定义报表 --- 文档跳转](/doc/dragReport.md) 16 | 17 | ```html 18 | http://localhost:8080/dragReport 19 | ``` 20 | 21 | [仿 `chrome` 调色板 --- 文档跳转](/doc/palette.md) 22 | 23 | ```html 24 | http://localhost:8080/palette 25 | ``` 26 | 27 | ## 拷贝项目 28 | 29 | ```cmd 30 | git clone https://github.com/jsjzh/vue-tiny-code.git 31 | ``` 32 | 33 | ## 开始项目 34 | 35 | ```cmd 36 | yarn 37 | or 38 | npm install 39 | ``` 40 | 41 | ## 本地环境 42 | 43 | ```cmd 44 | yarn start 45 | or 46 | npm start 47 | ``` 48 | 49 | ## 开发环境 50 | 51 | ```cmd 52 | yarn run build 53 | or 54 | npm run build 55 | ``` 56 | 57 | ## 其他 58 | 59 | `npm install` 的速度在不可抗力下会比较慢(该项目中,应该是 `node-sass` 比较慢),即使有 `npm.taobao.org` 也不能拯救我们,这个时候,我就推荐一个国内镜像的地址。 60 | 61 | ```cmd 62 | npm install -g mirror-config-china --registry=http://registry.npm.taobao.org 63 | ``` 64 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['@vue/app'], 3 | plugins: ['transform-vue-jsx'] 4 | } 5 | -------------------------------------------------------------------------------- /doc/dragReport.md: -------------------------------------------------------------------------------- 1 | # 拖拽自定义报表 2 | 3 | ## 介绍 4 | 5 | 该项目起源于公司内部的需求,本人在司维护开发的项目具有大量相似的报表页面,而最近又接到三个雷同页面,该项目应运而生。 6 | 7 | 项目在电脑端,遂工具制作成了拖动完成排版的形式,方便可爱的运维小姐姐操作。 8 | 9 | 推荐使用 `chrome` 浏览器食用更佳。 10 | 11 | ## 演示 12 | 13 | gif 图稍大,若加载不出来请稍等片刻 (..•˘_˘•..) 14 | 15 | ![演示1](https://i.loli.net/2019/03/12/5c8719e48a970.gif) 16 | 17 | ![演示2](https://i.loli.net/2019/03/12/5c8719e30304c.gif) 18 | 19 | ## 说明 20 | 21 | 在拖动页面实际拖动的就是一个拥有背景的 `div`,在预览页面才会生成其对应的组件。 22 | 23 | 项目主要由两个页面构成,第一个页面主要为拖动编辑排版,第二个页面为预览编辑的页面。 24 | 25 | ### 插件说明 26 | 27 | - `echarts` 百度可视化图表库 28 | - `element-ui` 饿了么组件库 29 | - `ramda` 函数式编程顶好的库之一 30 | - `jspdf` 结合 `html2canvas` 实现 pdf 导出功能 31 | - `html2canvas` 结合 `jspdf` 实现 pdf 导出功能 32 | - `lodash` 暂且只使用了 debounce 函数 33 | - `mockjs` 前端随机数据生成工具 34 | 35 | ### 结构说明 36 | 37 | #### 拖动页面 38 | 39 | 通识 40 | 41 | - 页面由行(`row`)组成,每一行又由列又称组件(`col`)组成 42 | - 将 `row` 栅格化,可以理解成 `24` 个块,组合可以随意,`12 + 12`、`16 + 8` 等,但其实 `8 + 8` 也可,行内排序改为两侧留白或者两侧对齐即可 43 | - `row` 间可以进行拖动更改位置,`col` 间可以进行拖动更改位置 44 | - `v-if` 和 `v-show` 的区别,在于是否会销毁某标签,使用 `v-show` 可以保存组件更改 45 | 46 | 使用的 `API` 说明 47 | 48 | - `draggable: true` 标识该标签内容是否可被拖动 49 | - `dragstart` 某一可拖动组件被拖动时,该事件会触发 50 | - `drop` 当某一可拖动组件被拖动至某一标签上时,该事件会被触发 51 | - `dragover` 由于拖动需要阻止事件的默认事件,遂一般会配合 `drop` 食用 52 | 53 | 行 --- 添加、修改、删除 54 | 55 | - 添加一行,点击左侧的 `+`,拖动某一行排序至中间的行即可 56 | - 修改某一行,鼠标放置某一行上,顶部浮现出行-控制条,拖动该控制条至其他行即可交换两行位置 57 | - 删除某一行,鼠标放置某一行,顶部浮现出行-控制条,点击 `remove` 即可 58 | 59 | 列 --- 添加、修改、删除 60 | 61 | - 添加一组件,点击右侧的 `+`,拖动某一组件至中间的位置即可 62 | - 修改某一组件,鼠标直接拖动某一组件至想要的位置即可 63 | - 删除某一组件,鼠标放置某一组件上,顶部浮现出列-控制条,点击 `remove` 即可 64 | 65 | #### 预览页面 66 | 67 | 通识 68 | 69 | - Vue 动态组件 70 | 71 | 使用的 `API` 说明 72 | 73 | - `` [`Vue` 动态组件](https://cn.vuejs.org/v2/guide/components.html#%E5%8A%A8%E6%80%81%E7%BB%84%E4%BB%B6) 74 | 75 | 组件集(`src\components\custom-report`) 76 | 77 | - 带 `custom-report-` 前缀的 `vue` 文件都会被自动引入到预览页面成为一个可供使用的组件 78 | - 带 `default-` 的组件需要手动引入 79 | - 基础 `echarts` 组件,在同级目录下的 `mixins` 中有定义其需要混入的方法 80 | - 基础 `element-table` 组件,接受一个 `table-option` 来定义表格的表头和 `key` 内容,由于该类型页面多为展示,遂该组件没有监听事件,若需要监听多个 `element-table` 的事件也简单,`element-table` 加上 `v-on="$listeners"` 即可 81 | - `echarts` 组件简单说明 82 | - `echarts` 在 `src\prototype` 中被挂载到 `Vue` 的实例原型上方便调用 83 | - 为什么会有这么多次的 `setOption` 84 | - 第一次 `defaultOption` 设置默认 `option`(在该目录下的 `js\chart-variable`),同时想要修改图表颜色集也可以在这里修改 85 | - 第二次 `customOption` 某些页面的图表使用默认的 `option` 会导致样式错位,这时候就需要增加 `js\variable` 变量,用于设置一些特殊的排版,比如 `center: [50%, 50%]`,等 86 | - 第三次 `reportData`,这个时候才是真实数据应该渲染的时候,建议在此处不要修改 `echarts` 样式相关的内容,而是只注入 `data` 87 | 88 | ### 数据格式说明 89 | 90 | 这里会详细说一下数据格式,具体可以参阅(`src\mock\modules\variable`)文件。 91 | 92 | #### 行数据 93 | 94 | ```js 95 | { 96 | // 表格 title 97 | title: 'drag-report', 98 | // 表格标识 key 通常唯一 99 | reportKey: 'first-report', 100 | // 行数据 101 | children: [{ 102 | // 行的水平排序方式 103 | align: 'flex-start', 104 | // 行高,暂时只有 250 和 100 两种 105 | height: 250, 106 | // 行 index 用于排序 107 | index: 1, 108 | // 行内组件数据 109 | children: [{ 110 | // 组件标题 111 | title: '评分图', 112 | // 组件行占比 113 | col: 24, 114 | // 组件 key 用于查询组件 115 | componentKey: 15, 116 | // 推荐行占比 117 | initCol: 24 118 | }] 119 | }] 120 | } 121 | ``` 122 | 123 | #### 组件数据 124 | 125 | ```js 126 | { 127 | // 组建默认名 128 | label: '占位', 129 | // 组件 key 通常唯一 130 | componentKey: 1, 131 | // 该组件对应的 vue 组件名称,在动态组件渲染的时候会自动加上 custom-report- 前缀 132 | componentName: 'block-module', 133 | // 该组件对应的接口,接口相同也没事,相同的请求不会发起两次 134 | api: '', 135 | // 该接口的类型 136 | method: 'get', 137 | // 返回的数据中,需要的那个数据的 key,若为空组件内会直接接受全量数据 138 | // 比如返回的 responseData 为 { names: [{name: 'king'}, {name: 'kimi'}] } 139 | // dataKey 就可以设置为 name,所对应的 vue 组件的 prop reportData 就会接受到 name 的数据 140 | dataKey: '', 141 | // 组件的行占比 142 | col: 1, 143 | // 组件的高度,暂时没用,可用于筛选 144 | height: 250, 145 | // 组件的预览图,用于拖动页面显示 146 | previewImage: 'https://i.loli.net/2019/03/11/5c8663be38f28.png' 147 | } 148 | ``` 149 | 150 | ### 小部件说明 151 | 152 | `clickoutside` 来自于 `element-ui` 的源码,用于判断用户是否点击某一元素。 153 | 154 | ### 优化说明 155 | 156 | 本来的想法是一个组件对应一个接口,现在对相同接口的组件进行了优化,如果接口相同则只会调用一遍接口。 157 | 158 | ## 代办事项 159 | 160 | - 调用接口所传输的数据需要相同,拟改为可以自定义传输的数据 161 | - 拟把行的高度也改为可以自定义的,这时候就需要一个行内垂直的排序方式 162 | - `pdf` 导出的样式有点丑,后面稍微改一下 163 | - 拟将 `drag` 改为 `mousedown` 等,进行低版本浏览器的兼容 164 | - 拟增加一个预览页面切换报表颜色系的功能 165 | 166 | ## 后语 167 | 168 | - 该项目中困难的并非是该如何实现拖拽功能,真正困难的是数据格式的设计,由于该工具为前端主导后端为辅,数据格式大的调整有过三次,一次是格式问题,不能实现我想要的功能,两次是格式优化,考虑到要存储到数据库,遂对所需格式又进行了两次优化 169 | - 考虑到数据应该都由后台接口获取过来,所以加入了 `mockjs` 用以模拟 `ajax` 请求,引入到正式项目中的时候可以减少更改 170 | - 样式整理也是一个比较令人头秃的一个点,在初版完成之后的改版优化中,我一边改一边骂自己是个傻 X,还好有 `sass` 的存在,让我减少了不少工作量,但是不能否认的是项目中还是有许多辣鸡代码(何谓辣鸡代码,就是让我看一遍也会想拍死作者,即使是自己写的),还是需要做优化 171 | - 在预览报表的页面,由于了解过 `Vue` 的 `component` 组件,所以实现起来也不是什么难事,关键点在于每个组件对外接收的 `prop` 要做到统一,这不仅是为了方便,任何一个框架都需要有人来维护,这也是为了减少后面接手的人的理解成本 172 | - 在框架搭建完成之后,组件式报表的业务重点就在于大量的组件,本来的写法是写一个组件在预览页面引用一个,但是在看 [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin) 的源码的时候,发现了 `webpack` 的 `require.context` 这个令人着迷的 `API`,那还能忍?盘他,于是,就有了 `src\views\previewReport\index.js` 这个文件的诞生,只要组件命名规范(前缀 `custom-report-`),该文件就会自动引入该组件,方便省心,比充 X 娃娃还好使 173 | - 由于这样的页面大都不仅是用来看看,都会结合一下 `pdf` 导出的功能,遂查阅相关资料做了一个 `pdf` 导出的功能,前端实现 `pdf` 导出功能的思想就是通过遍历 `dom` 来生成 `canvas`,然后又借助于 `canvas` 的 `API` 获取图片的 `base64` 码,最后通过 `jspdf` 生成 `pdf` 174 | -------------------------------------------------------------------------------- /doc/palette.md: -------------------------------------------------------------------------------- 1 | # 仿 `chrome` 调色板 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-tiny-code", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "start": "npm run serve" 9 | }, 10 | "dependencies": { 11 | "axios": "^0.18.0", 12 | "babel-polyfill": "^6.26.0", 13 | "echarts": "^4.1.0", 14 | "element-ui": "^2.5.4", 15 | "html2canvas": "^1.0.0-alpha.12", 16 | "jspdf": "^1.5.3", 17 | "lodash": "^4.17.11", 18 | "mockjs": "^1.0.1-beta3", 19 | "moment": "^2.24.0", 20 | "ramda": "^0.26.1", 21 | "resize-observer-polyfill": "^1.5.0", 22 | "vue": "^2.5.17", 23 | "vue-router": "^3.0.1" 24 | }, 25 | "devDependencies": { 26 | "@vue/cli-plugin-babel": "^3.0.3", 27 | "@vue/cli-service": "^3.0.3", 28 | "babel-helper-vue-jsx-merge-props": "^2.0.3", 29 | "babel-plugin-syntax-jsx": "^6.18.0", 30 | "babel-plugin-transform-vue-jsx": "^3.7.0", 31 | "babel-preset-env": "^1.7.0", 32 | "node-sass": "^4.9.0", 33 | "sass-loader": "^7.0.1", 34 | "vue-template-compiler": "^2.5.17" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsjzh/vue-tiny-code/78508f3ee9a16875c17cf66b209d85dd1e5a7bd6/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | new-vue-cli-demo 9 | 10 | 11 | 12 | 18 |
19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 21 | -------------------------------------------------------------------------------- /src/api/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: jsjzh 3 | * @Email: kimimi_king@163.com 4 | * @LastEditors: jsjzh 5 | * @Date: 2019-03-06 14:39:11 6 | * @LastEditTime: 2019-05-13 11:00:58 7 | * @Description: 8 | * 数据为何要解构再变为一个 obj,因为需要知道该接口所接收的数据 9 | * 若有默认参数则可以直接加入 default: 'default' 10 | */ 11 | import { post, get } from './service' 12 | 13 | /** 14 | * 获取自定义报表的组件列表 15 | */ 16 | export function getcomponentinfo() { 17 | return get('/getcomponentinfo', {}) 18 | } 19 | /** 20 | * 获取自定义报表列表 21 | */ 22 | export function getreportstructurelist() { 23 | return get('/getreportstructurelist', {}) 24 | } 25 | /** 26 | * 新增自定义报表 27 | * @param {string} title 28 | * @param {array} children 29 | */ 30 | export function operatestructureinfo({ title, children }) { 31 | return post('/operatestructureinfo', { title, children }) 32 | } 33 | /** 34 | * 删除自定义报表 35 | * @param {string} reportUnionKey 36 | */ 37 | export function delstructureinfo({ reportUnionKey }) { 38 | return post('/delstructureinfo', { reportUnionKey }) 39 | } 40 | /** 41 | * 获取某一报表详细布局信息 42 | * @param {string} reportUnionKey 43 | */ 44 | export function getreportcomponentinfo({ reportUnionKey }) { 45 | return get('/getreportcomponentinfo', { reportUnionKey }) 46 | } 47 | 48 | /** 49 | * 更新一条布局信息 50 | * @param {object} layoutData 51 | */ 52 | export function updatestructureinfo(layoutData) { 53 | return post('/updatestructureinfo', layoutData) 54 | } 55 | -------------------------------------------------------------------------------- /src/api/service.js: -------------------------------------------------------------------------------- 1 | import { Message } from 'element-ui' 2 | import axios from 'axios' 3 | import * as R from 'ramda' 4 | 5 | const service = axios.create({ 6 | baseURL: 'http://localhost', 7 | timeout: 5000 8 | }) 9 | 10 | service.interceptors.request.use( 11 | config => { 12 | config.params = { ...config.params, _t: +Date.now() } 13 | return config 14 | }, 15 | err => { 16 | console.log('request err', err) 17 | return Promise.reject(err) 18 | } 19 | ) 20 | 21 | service.interceptors.response.use( 22 | response => { 23 | if (response.status !== 200) { 24 | Message({ 25 | message: response.status, 26 | type: 'error', 27 | duration: 5 * 1000 28 | }) 29 | console.log('response err', response) 30 | return Promise.reject('err') 31 | } else { 32 | return response.data 33 | } 34 | }, 35 | err => { 36 | Message({ 37 | message: err.messages, 38 | type: 'error', 39 | duration: 5 * 1000 40 | }) 41 | console.log('response err', err) 42 | return Promise.reject(err) 43 | } 44 | ) 45 | 46 | export const get = R.curry(function(url, params) { 47 | return service.get(url, { params }) 48 | }) 49 | 50 | export const post = R.curry(function(url, params) { 51 | return service.post(url, { params }) 52 | }) 53 | 54 | export default service 55 | -------------------------------------------------------------------------------- /src/assets/icons/iconfont.css: -------------------------------------------------------------------------------- 1 | @font-face {font-family: "iconfont"; 2 | src: url('iconfont.eot?t=1557223024822'); /* IE9 */ 3 | src: url('iconfont.eot?t=1557223024822#iefix') format('embedded-opentype'), /* IE6-IE8 */ 4 | url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAA2AAAsAAAAAFrAAAA0wAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCFBgqdNJcfATYCJAM8CyAABCAFhG0HgVQbsxIzkpFWDdl/lWDKfUOhQzGIO4aFuKaj2+ACSYfyRtcIYBeNYVm2PSo0h6HxbX4bHJPB05z9vLzNJtlsxDGRRbwS/ODoJVVDrAZUgbpTEzsRo70veNNzAqDNB87/z/vOVG5ZumNDoAggOKD0BgTx25+1sWzHOcA4NOAUCAyFv+E2U51ebmdYvo5e0/a65vRYgL6JCOHGwAnDvXJqrdXXjljUxTREqOHs5fRFF1NLDJESCekWE8mqoZAisZMKFmOTY1XgW8WsXOXqQABwcEEQSGb2iBJIwUAwBFVmTJ08FtKACUyCI4FUYBWzMuPJWoghFbWLXgBYY30/+S8iiBQQQUwh1mSelDUBQjf+01XLcEsV7cC66iCAOQqgAIIAMHP+PaX+CcCSrTMFV5vBTsAJar5TvhvdFMsonsCsRJvHLy3r+U/XhxNiJe6YiCdwkjG5LAGQgocCSrCgkEOC/qZDBu5U/fFAWExYniuvFCiBblAISIFuGgI80C0OAQXQzZJZOSMYRLl4MCjCCTBADs4MAyTgSmCACFwbGFTLPUYIyICXFgM4i+sBBMGtSEH1ELAqPq4LgByAOQ2wt7JyAi3aoUTQgYEYErCcpg5NaCaxh5tJJpd4aJ3UPE8YcUQa4iMjUmLgA+Q6jUohi5HFcjIZr1QplWp1hEqn5OUMk5iqppBUzTEuPK9GhDC2G8vcqBMRk1kqdaeHzNY0U3EiKXcSwk2AbdPKDWhYki6nthFHmVg6bmu2qZDMFJz9FVtS94eA7vSMz8ZhSQ/XB0+YpviP8Cn/mWJ4P7jr3fPrOz3ZCmJ6kefZIUbZ0/jVQxtmmr2R1IYDdL4VyCKYRe1wasZf+oc/PXtGW9de/2GZIQDKW+9sR5feBaXEsvCfgfHJFJGLYICAw+OSgZJo+ngbs21Uzlv0k6aAZYZFhAwtVbI1ZCoJrwao5RpRw1bMMRLZ9n4Dqm/ZQlADW+b252xbW+zMHCNSMhaDblpjbPIMR2IhNe2wrXAl80NkQqR0fHXIqUFX1YPYPCtgyTwv6gUjB0oTxq80nZRQn41hIeNU39QMMrVsnBW1RanXG0vh+P5g20VvQpdfT2Pj2YAv3Z3254x0ol3HSnE2G3f6RCFEO7kWibKzizGaZulpFBMTZX+KfXgh9MWmvD3+Q+z9RYPHLPybBB72fOL7LAypF7Bh6iU8WOguTmfSOJeJNg7mALqWE5fShTof1c7L+BAaMi1clvHFs8oY1FFSUCYvE7oQ8TxAS06GAMjHmiZCdocq6SritmSyVqn1S/muHLZUZId7wlIYHCgtZLDJVJCZi40mIkWt4xOV0M/UImPWIwRCwp7ima2MHF7IMMWYPLttNBBJE221NaZbrHwzFgGtDXMdPGJE3J+aoJZaI34u8MpH0Bq2c1ulFtwSiVM46SCeiAelYArjQ+uyqv3sbbseTA7+UlFuiqGqrsH03O61w3Bn4o9bUWdhxmPw/Ym+9w7rcKcF3gE5l59KMsWENW6SZhWmUdeN4FT4KkrPvZw5FU2dnPKKezqmn2lEtH0IGg7bGKx7H7KyIXB4JUX1r8AR6P2RaERLMiXLXa8DoIQBIahDSMV0ALbmjEJMoip8A1UrfVphh6YNFikhwAhkBB5BgNAjYDmU0IchLyV/LD3ntYiZR6VTY82bdaqxDvgUpzrlQW9aIYxV3GtH8ruhWt2xbqmRWvGeM0t5h/ZTOD4M2ZUPS3zppa1/jP1loXlL1c5HTXmJE/Vxy8oKQ3V7YmVl4DnF02r3eLbM0jqKU7nNtj25587y7Ht6eEbOj/3QB1q8k5wx7zRLnuP39XuGX643W2CxzjHEz+inoZGTlzEEIC4ifg+0zEm5pPuJ4D8a/s/8f4n3zweeh32fdKVMENCkgM8WYooBmmBsFyC19MHT/CymJeWXzAtN6yeFuA0eTlJ59/rEqoW1EA2bdWhg0bH++fObDjTtX7Shf0Z0NJya2AA2ovp69Wfo5wl7tisFZcP66kL15k0La1HZ6Geqvl10MlNefnJw0reHVg4fApbMSVZfm/HNlpnNW2b1XZlWvWv2K0/g/VWyxLL2N0n/+WhvxL9m88ia40N31e8cIozHWfbIffrWSZOm75gX7ZnRA5IO3vhi7Rf8RSaMznzLPLq561pz5JShva9tM3a3x662Bt/tSrMb9/BsVHn+3iZznOP2a9zdDDNpEP0voQHBNfNP8D3y342baUPrh60N1LTtL66nbE5smqhgNJ3w090Sh10XvD1SokM8BtlJpHh4XXDYNXGfcEdV7bBDqVMe59TV0hl8lnx+xgRub8ReTs/6B+k2m9NbHKui/Ys0xX2ePdllX6pNXdbwjx4E+hdF+9fNj3r8P6f8i+uz33pzmUcuHf313nP6Nq/QnIriwKzysFy/dZcmuWXHiU2WWPds76j6o/3S6i07Kr4MnxZa6R0gL+POjOMcGD6vPtosRIX36lb4yKPXTfjFc8ejmvC5r2ux3+NQxCbLiYlrHp6pkhvM1oOfWVY73nQcWRu860SfkOoKIQ1q0rp6j3/nQMX+fWan6iNxYxYxas9D1TM6ZqR1bvijdef59JUR6tzhgJ0lOsIn29kin+A/xtEgXS9iE+1OVCYPjrOfGOEwfZT/JeXZnmZ4Lnb7Y3noOveJE93XhS7f47Jjgb5j5Mq8oMDqmTOrA4PyVo7s0C+YM3O++rPZCbNj5vr0lFdWlPf6zI1ZncRn6vlZxg63KVPcOmQW3I+iH7QpmCUeG7UNXg3k7zx1g/b4utRx0qBxVU8J+Tsh+e76SDa8YGtSgduvKHTT9ud9LMLDO74kZemn2BArL1V5KbDXwHkvdg44SLI/ysBVLsCDe8E5yN5deMbrw43O8R9sVN6wl9l7rH3B2XPvclHisvanE9qL2EjuXZm97MVaTxde2cU/JXn4MQ3cQiy2er3xM63mAe74IWXpQehX6j9YrPD/YINiEeP14QZlXtlHEv3pDsncuHei5ioXFuZbN0qPF0oLLFjwxxyr2G45k0nvmA06a3HKpeGsUVUlyqJuVrGiimbSoMx058hcjtJoJo81mo7QrN2nXY6IrelMFjUdMbJ5VnZZuxvNEqmqjCyyLoqvMpiJ6XRSWBRjWm4wt9BMRscXaSXI71vQ+n44sVoDFo1NLIvK0vmV0zPZmNYyNmDR2qTKHEmNWh+W1tMeXyUoq/UVNat9a9ur4wf/Gt66jdaFj4/YUn2TT/duvBlrV5H+bt3W8CxXRxivHXqUsPxDS8nIuoskQH+40884VGhfUaRa8TDhaW4Jsw1nshwdMs9gyU4leB1v6zrQSXbZNtk6SddB2+vwtwuV4Zztu4Y2rzbFba/Dfmh/U/EGyyC0yElPL1LZLqoLzQcDJgqls53OaPZSYaJvHeZC9UWbag495neMls92YgRDVPghq4NBYGC5MFvUElRrhDvCotul0XfocODycX2urDVd9rb21fMMrV2W90z/GfFayYU/HgQwphxtmYeenJH/Ej4pke1H9ktx2iLLYsWozdJdv2+5ItL7MGPT1cnhl3qH2P44zrX8uS5vQk6WPB4e5ysm5kTbOSZ8ml/p1cFUbx7vZDc1ioGFWGTjI5a+dZ6PDMuPaM7Qly7JKJOZpGRznMm+ZNt62q4P/uByWekF/rxjkinH5u1Yd/g/EQl2OW+8ws5J/ccUTYwrPVgaLxnZawudHGViWddoF3uXztxGN/2Pqya4Hjvku1RT8Yl70PWw4LXS2JA2DBffp5Rh7jGm9r5OMz4Mkns1+Yzmy3ToNXNZGoaGoGVmpFf6dbOeXcFWEc9Z4wVfyeAzhLRIReTk2QBfS8LlMxQZWPAdxLD0PvkFGG6hObRlncp40ZHz/EUM6AIVdKGtyP+zIeqT1ciKkLteHErbJ6XvtJcKw/9y9lWW3LX0GbWg8Dh9l5b+kY6G7e++J7Kokl5L5T/rWV+8aZhj1Rvu/Wz2OO34z2BFxxII8LdpYI+tJTbNTi3MKmZDUU9Pzw3DvZXEkQB7TGV/Nfwvp0BW5KPCNehyRQA42Q/4u0zW4rvmeENm6p+aN3hABAmcAYUUXlJGGQTEkCMKsJAiFXAIRM7ccuhQDQrCyAAEYCkDBGqcBiIocRtQqPEuBgzKb4EYRvwfsFATCjiUEd0i5fDBELp8EQTFlel2hizPTrPqpV31E30eQfxqy/MXJVGWaco6XPSBM0oVU6S1b1WdccKTeWdrw3FkE4V7tFoG1bisKqeasrQ8LS6RIQhqBF4ZdLvoZHl2g+tl5Os/0ecRJKHNbfRflERDN2hQos4g/WDnTG12pXZa+xbKoTPUWHgyeOcQR5nPBlE9Uo9WS4QCuXGJivflsrJl8/x0vbO4+toVh6siIcmKqumGadmO6/m3zSHDvCWY/QpJ5KQnSPnUBtAgPgSFROi4ItNY/HnE8HeWmMsp0JlKjDZID5o7Ag4gaZ954g7m856qmWxO+eQrQMRnJ/ifWR0tRpixsEsDHtVAH5OhucxPLPOUisUCAAAA') format('woff2'), 5 | url('iconfont.woff?t=1557223024822') format('woff'), 6 | url('iconfont.ttf?t=1557223024822') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */ 7 | url('iconfont.svg?t=1557223024822#iconfont') format('svg'); /* iOS 4.1- */ 8 | } 9 | 10 | .iconfont { 11 | font-family: "iconfont" !important; 12 | font-size: 16px; 13 | font-style: normal; 14 | -webkit-font-smoothing: antialiased; 15 | -moz-osx-font-smoothing: grayscale; 16 | } 17 | 18 | .iconzhuanxiangdeng:before { 19 | content: "\e7ee"; 20 | } 21 | 22 | .iconjiasu:before { 23 | content: "\e654"; 24 | } 25 | 26 | .iconchache:before { 27 | content: "\e639"; 28 | } 29 | 30 | .iconhangrenpengzhuang:before { 31 | content: "\eaa3"; 32 | } 33 | 34 | .iconhangren_:before { 35 | content: "\e682"; 36 | } 37 | 38 | .iconchaoshi:before { 39 | content: "\e616"; 40 | } 41 | 42 | .iconzhuiwei:before { 43 | content: "\e650"; 44 | } 45 | 46 | .icontubiaozhizuomoban:before { 47 | content: "\e600"; 48 | } 49 | 50 | .iconjijiansu:before { 51 | content: "\e6c7"; 52 | } 53 | 54 | .iconShape:before { 55 | content: "\e75f"; 56 | } 57 | 58 | .iconfrequent-lane-change:before { 59 | content: "\e640"; 60 | } 61 | 62 | .iconche:before { 63 | content: "\e603"; 64 | } 65 | 66 | .iconche1:before { 67 | content: "\e605"; 68 | } 69 | 70 | .iconweibiaoti-:before { 71 | content: "\e606"; 72 | } 73 | 74 | -------------------------------------------------------------------------------- /src/assets/icons/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsjzh/vue-tiny-code/78508f3ee9a16875c17cf66b209d85dd1e5a7bd6/src/assets/icons/iconfont.eot -------------------------------------------------------------------------------- /src/assets/icons/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsjzh/vue-tiny-code/78508f3ee9a16875c17cf66b209d85dd1e5a7bd6/src/assets/icons/iconfont.ttf -------------------------------------------------------------------------------- /src/assets/icons/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsjzh/vue-tiny-code/78508f3ee9a16875c17cf66b209d85dd1e5a7bd6/src/assets/icons/iconfont.woff -------------------------------------------------------------------------------- /src/assets/icons/iconfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsjzh/vue-tiny-code/78508f3ee9a16875c17cf66b209d85dd1e5a7bd6/src/assets/icons/iconfont.woff2 -------------------------------------------------------------------------------- /src/assets/icons/index.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'iconfont'; 3 | src: url('iconfont.eot'); 4 | src: url('iconfont.eot?#iefix') format('embedded-opentype'), url('iconfont.woff2') format('woff2'), 5 | url('iconfont.woff') format('woff'), url('iconfont.ttf') format('truetype'), 6 | url('iconfont.svg#iconfont') format('svg'); 7 | } 8 | 9 | .iconfont { 10 | font-family: 'iconfont' !important; 11 | font-size: 16px; 12 | font-style: normal; 13 | -webkit-font-smoothing: antialiased; 14 | -moz-osx-font-smoothing: grayscale; 15 | } 16 | -------------------------------------------------------------------------------- /src/assets/img/dragReport/col-1-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsjzh/vue-tiny-code/78508f3ee9a16875c17cf66b209d85dd1e5a7bd6/src/assets/img/dragReport/col-1-0.png -------------------------------------------------------------------------------- /src/assets/img/dragReport/col-12-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsjzh/vue-tiny-code/78508f3ee9a16875c17cf66b209d85dd1e5a7bd6/src/assets/img/dragReport/col-12-0.png -------------------------------------------------------------------------------- /src/assets/img/dragReport/col-12-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsjzh/vue-tiny-code/78508f3ee9a16875c17cf66b209d85dd1e5a7bd6/src/assets/img/dragReport/col-12-1.png -------------------------------------------------------------------------------- /src/assets/img/dragReport/col-12-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsjzh/vue-tiny-code/78508f3ee9a16875c17cf66b209d85dd1e5a7bd6/src/assets/img/dragReport/col-12-2.png -------------------------------------------------------------------------------- /src/assets/img/dragReport/col-16-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsjzh/vue-tiny-code/78508f3ee9a16875c17cf66b209d85dd1e5a7bd6/src/assets/img/dragReport/col-16-1.png -------------------------------------------------------------------------------- /src/assets/img/dragReport/col-2-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsjzh/vue-tiny-code/78508f3ee9a16875c17cf66b209d85dd1e5a7bd6/src/assets/img/dragReport/col-2-0.png -------------------------------------------------------------------------------- /src/assets/img/dragReport/col-24-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsjzh/vue-tiny-code/78508f3ee9a16875c17cf66b209d85dd1e5a7bd6/src/assets/img/dragReport/col-24-0.png -------------------------------------------------------------------------------- /src/assets/img/dragReport/col-24-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsjzh/vue-tiny-code/78508f3ee9a16875c17cf66b209d85dd1e5a7bd6/src/assets/img/dragReport/col-24-1.png -------------------------------------------------------------------------------- /src/assets/img/dragReport/col-3-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsjzh/vue-tiny-code/78508f3ee9a16875c17cf66b209d85dd1e5a7bd6/src/assets/img/dragReport/col-3-0.png -------------------------------------------------------------------------------- /src/assets/img/dragReport/col-4-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsjzh/vue-tiny-code/78508f3ee9a16875c17cf66b209d85dd1e5a7bd6/src/assets/img/dragReport/col-4-0.png -------------------------------------------------------------------------------- /src/assets/img/dragReport/col-4.8-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsjzh/vue-tiny-code/78508f3ee9a16875c17cf66b209d85dd1e5a7bd6/src/assets/img/dragReport/col-4.8-1.png -------------------------------------------------------------------------------- /src/assets/img/dragReport/col-4.8-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsjzh/vue-tiny-code/78508f3ee9a16875c17cf66b209d85dd1e5a7bd6/src/assets/img/dragReport/col-4.8-2.png -------------------------------------------------------------------------------- /src/assets/img/dragReport/col-4.8-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsjzh/vue-tiny-code/78508f3ee9a16875c17cf66b209d85dd1e5a7bd6/src/assets/img/dragReport/col-4.8-3.png -------------------------------------------------------------------------------- /src/assets/img/dragReport/col-4.8-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsjzh/vue-tiny-code/78508f3ee9a16875c17cf66b209d85dd1e5a7bd6/src/assets/img/dragReport/col-4.8-4.png -------------------------------------------------------------------------------- /src/assets/img/dragReport/col-4.8-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsjzh/vue-tiny-code/78508f3ee9a16875c17cf66b209d85dd1e5a7bd6/src/assets/img/dragReport/col-4.8-5.png -------------------------------------------------------------------------------- /src/assets/img/dragReport/col-6-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsjzh/vue-tiny-code/78508f3ee9a16875c17cf66b209d85dd1e5a7bd6/src/assets/img/dragReport/col-6-0.png -------------------------------------------------------------------------------- /src/assets/img/dragReport/col-8-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsjzh/vue-tiny-code/78508f3ee9a16875c17cf66b209d85dd1e5a7bd6/src/assets/img/dragReport/col-8-0.png -------------------------------------------------------------------------------- /src/assets/img/dragReport/col-8-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsjzh/vue-tiny-code/78508f3ee9a16875c17cf66b209d85dd1e5a7bd6/src/assets/img/dragReport/col-8-1.png -------------------------------------------------------------------------------- /src/assets/img/palette/lucency.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsjzh/vue-tiny-code/78508f3ee9a16875c17cf66b209d85dd1e5a7bd6/src/assets/img/palette/lucency.png -------------------------------------------------------------------------------- /src/components/color-picker/color.js: -------------------------------------------------------------------------------- 1 | import { hsv2rgb, hsv2hsl, rgb2hsv, hsl2hsv, toHex, parseHex } from './convert' 2 | 3 | // 内部使用颜色之前 默认需要转换成 HSVA 模式 4 | export default class Color { 5 | constructor(options) { 6 | this.pure = options.pure || '' 7 | this.value = options.value || '' 8 | this.format = options.format || 'hsva' 9 | this._chunkAngle = 360 / (options.precision || 12 * 5) 10 | 11 | this.recomColors = [ 12 | 'rgba(244, 67, 54, 1)', 13 | 'rgba(233, 30, 99, 1)', 14 | 'rgba(156, 39, 176, 1)', 15 | 'rgba(103, 58, 183, 1)', 16 | 'rgba(63, 81, 181, 1)', 17 | 'rgba(33, 150, 243, 1)', 18 | 'rgba(3, 169, 244, 1)', 19 | 'rgba(0, 188, 212, 1)', 20 | 'rgba(0, 150, 136, 1)', 21 | 'rgba(76, 175, 80, 1)', 22 | 'rgba(139, 195, 74, 1)', 23 | 'rgba(205, 220, 57, 1)', 24 | 'rgba(255, 235, 59, 1)', 25 | 'rgba(255, 193, 7, 1)', 26 | 'rgba(255, 152, 0, 1)', 27 | 'rgba(255, 87, 34, 1)', 28 | 'rgba(121, 85, 72, 1)', 29 | 'rgba(158, 158, 158, 1)', 30 | 'rgba(96, 125, 13, 19)' 31 | ] 32 | 33 | this._hue = 0 34 | this._saturation = 100 35 | this._value = 100 36 | this._trans = 1 37 | 38 | this.output = '' 39 | 40 | this._handleChange() 41 | } 42 | 43 | get(prop) { 44 | return this[prop] 45 | } 46 | 47 | // 传入皆为 0-100 之间的整数 48 | _update(sat, value, hue, trans) { 49 | this._hue = Math.round(360 - (hue / 100) * 360) 50 | this._saturation = Math.round((sat / 100) * 100) 51 | this._value = Math.round(100 - (value / 100) * 100) 52 | this._trans = ((trans / 100) * 1).toFixed(2) 53 | this._handleChange() 54 | } 55 | 56 | // 传入颜色字符串 57 | string2rate(str) { 58 | let arr = str.split(/rgba\(|\s|\,|\)|\%/gi).reduce((sum, item) => (item ? [...sum, +item] : sum), []) 59 | const { h, s, v } = rgb2hsv.apply(null, arr) 60 | 61 | return { 62 | satLeft: (s / 100) * 100, 63 | valueTop: 100 - (v / 100) * 100, 64 | hueLeft: 100 - (h / 360) * 100, 65 | transLeft: 100 66 | } 67 | } 68 | 69 | blendent(type = 'similar', options) { 70 | const arr = this['_' + type].call(this, options) 71 | return arr 72 | .reduce((sum, hue) => [...sum, hsv2rgb.call(null, hue, this._saturation, this._value)], []) 73 | .map(item => 74 | Number(this._trans) === 1 75 | ? `rgb(${item.r}, ${item.g}, ${item.b})` 76 | : `rgba(${item.r}, ${item.g}, ${item.b}, ${this._trans})` 77 | ) 78 | } 79 | 80 | // 获取互补色 81 | _reverse() { 82 | return [this._hue, this._hue > 180 ? this._hue - 180 : this._hue + 180] 83 | } 84 | 85 | /** 86 | * 获取近似色 87 | * @param {Number} number 需要获取几组近似色 88 | */ 89 | _similar(number = 3) { 90 | let arr = [this._hue] 91 | while (number) { 92 | arr.push( 93 | this._hue + this._chunkAngle * number > 360 94 | ? this._hue + this._chunkAngle * number - 360 95 | : this._hue + this._chunkAngle * number 96 | ) 97 | arr.unshift( 98 | this._hue - this._chunkAngle * number < 0 99 | ? this._hue - this._chunkAngle * number + 360 100 | : this._hue - this._chunkAngle * number 101 | ) 102 | number-- 103 | } 104 | return arr 105 | } 106 | 107 | // 获取三角色 108 | _triangle() { 109 | return [ 110 | this._hue, 111 | this._hue + 120 > 360 ? this._hue - 240 : this._hue + 120, 112 | this._hue + 240 > 360 ? this._hue - 120 : this._hue + 240 113 | ] 114 | } 115 | 116 | /** 117 | * 获取分裂互补色 118 | * @param {Boolean} flag 第一个向前或者向后取值 119 | */ 120 | _complement(flag = true) { 121 | return [ 122 | this._hue, 123 | flag 124 | ? this._hue + this._chunkAngle * 2 > 360 125 | ? this._hue + this._chunkAngle * 2 - 360 126 | : this._hue + this._chunkAngle * 2 127 | : this._hue - this._chunkAngle * 2 < 0 128 | ? 360 + this._hue - this._chunkAngle * 2 129 | : this._hue - this._chunkAngle * 2, 130 | this._hue + this._chunkAngle + 180 > 360 ? this._hue + this._chunkAngle - 180 : this._hue + this._chunkAngle + 180 131 | ] 132 | } 133 | 134 | /** 135 | * 获取双分裂互补色 136 | * @param {Boolean} flag 第一个向前或者向后取值 137 | */ 138 | _doubleComplement(flag = true) { 139 | return [ 140 | this._hue, 141 | flag 142 | ? this._hue + this._chunkAngle * 2 > 360 143 | ? 360 - this._hue + this._chunkAngle * 2 144 | : this._hue + this._chunkAngle * 2 145 | : this._hue - this._chunkAngle * 2 < 0 146 | ? 360 + this._hue + this._chunkAngle * 2 147 | : this._hue - this._chunkAngle * 2, 148 | flag 149 | ? this._hue + 180 > 360 150 | ? this._hue - 180 151 | : this._hue + 180 152 | : this._hue - 180 < 0 153 | ? this._hue + 180 154 | : this._hue - 180, 155 | flag 156 | ? this._hue + this._chunkAngle * 2 + 180 > 360 157 | ? this._hue + this._chunkAngle * 2 - 180 158 | : this._hue + this._chunkAngle * 2 + 180 159 | : this._hue + this._chunkAngle * 2 - 180 < 0 160 | ? this._hue + this._chunkAngle * 2 + 180 161 | : this._hue + this._chunkAngle * 2 - 180 162 | ] 163 | } 164 | 165 | // 正方形配色 166 | _square() { 167 | let chunkQuarter = 360 / 4 168 | return [ 169 | this._hue, 170 | this._hue + chunkQuarter > 360 ? this._hue + chunkQuarter - 360 : this._hue + chunkQuarter, 171 | this._hue + 180 > 360 ? this._hue - 180 : this._hue + 180, 172 | this._hue + chunkQuarter + 180 > 360 ? this._hue + chunkQuarter - 180 : this._hue + chunkQuarter + 180 173 | ] 174 | } 175 | 176 | _handleChange() { 177 | const { _hue: hue, _saturation: saturation, _value: value, _trans: trans } = this 178 | const { r, g, b } = hsv2rgb(hue, saturation, value) 179 | 180 | this.output = Number(trans) === 1 ? `rgb(${r}, ${g}, ${b})` : `rgba(${r}, ${g}, ${b}, ${trans})` 181 | 182 | this.pure = `hsla(${hue}, 100%, 50%, 1)` 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/components/color-picker/convert.js: -------------------------------------------------------------------------------- 1 | function isOnePointZero(n) { 2 | return typeof n === 'string' && n.indexOf('.') !== -1 && parseFloat(n) === 1 3 | } 4 | 5 | function isPercentage(n) { 6 | return typeof n === 'string' && n.indexOf('%') !== -1 7 | } 8 | 9 | const bound01 = function(value, max) { 10 | if (isOnePointZero(value)) value = '100%' 11 | 12 | const processPercent = isPercentage(value) 13 | value = Math.min(max, Math.max(0, parseFloat(value))) 14 | 15 | // Automatically convert percentage into number 16 | if (processPercent) { 17 | value = parseInt(value * max, 10) / 100 18 | } 19 | 20 | // Handle floating point rounding errors 21 | if (Math.abs(value - max) < 0.000001) { 22 | return 1 23 | } 24 | 25 | // Convert into [0, 1] range if it isn't already 26 | return (value % max) / parseFloat(max) 27 | } 28 | 29 | export function hsv2rgb(h, s, v) { 30 | h = bound01(h, 360) * 6 31 | s = bound01(s, 100) 32 | v = bound01(v, 100) 33 | 34 | const i = Math.floor(h) 35 | const f = h - i 36 | const p = v * (1 - s) 37 | const q = v * (1 - f * s) 38 | const t = v * (1 - (1 - f) * s) 39 | const mod = i % 6 40 | const r = [v, q, p, p, t, v][mod] 41 | const g = [t, v, v, q, p, p][mod] 42 | const b = [p, p, t, v, v, q][mod] 43 | 44 | return { 45 | r: Math.round(r * 255), 46 | g: Math.round(g * 255), 47 | b: Math.round(b * 255) 48 | } 49 | } 50 | 51 | export function hsv2hsl(h, s, v) { 52 | return { 53 | h: h, 54 | s: (s * v) / ((h = (2 - s) * v) < 1 ? h : 2 - h) || 0, 55 | l: h / 2 56 | } 57 | } 58 | 59 | export function rgb2hsv(r, g, b) { 60 | r = bound01(r, 255) 61 | g = bound01(g, 255) 62 | b = bound01(b, 255) 63 | 64 | const max = Math.max(r, g, b) 65 | const min = Math.min(r, g, b) 66 | let h, s 67 | let v = max 68 | 69 | const d = max - min 70 | s = max === 0 ? 0 : d / max 71 | 72 | if (max === min) { 73 | h = 0 // achromatic 74 | } else { 75 | switch (max) { 76 | case r: 77 | h = (g - b) / d + (g < b ? 6 : 0) 78 | break 79 | case g: 80 | h = (b - r) / d + 2 81 | break 82 | case b: 83 | h = (r - g) / d + 4 84 | break 85 | } 86 | h /= 6 87 | } 88 | return { 89 | h: h * 360, 90 | s: s * 100, 91 | v: v * 100 92 | } 93 | } 94 | 95 | export function hsl2hsv(h, s, l) { 96 | s = s / 100 97 | l = l / 100 98 | let smin = s 99 | const lmin = Math.max(l, 0.01) 100 | let sv 101 | let v 102 | 103 | l *= 2 104 | s *= l <= 1 ? l : 2 - l 105 | smin *= lmin <= 1 ? lmin : 2 - lmin 106 | v = (l + s) / 2 107 | sv = l === 0 ? (2 * smin) / (lmin + smin) : (2 * s) / (l + s) 108 | 109 | return { 110 | h: h, 111 | s: sv * 100, 112 | v: v * 100 113 | } 114 | } 115 | 116 | const INT_HEX_MAP = { 117 | 10: 'A', 118 | 11: 'B', 119 | 12: 'C', 120 | 13: 'D', 121 | 14: 'E', 122 | 15: 'F' 123 | } 124 | 125 | export function toHex(r, g, b) { 126 | const hexOne = function(value) { 127 | value = Math.min(Math.round(value), 255) 128 | const high = Math.floor(value / 16) 129 | const low = value % 16 130 | return '' + (INT_HEX_MAP[high] || high) + (INT_HEX_MAP[low] || low) 131 | } 132 | 133 | if (isNaN(r) || isNaN(g) || isNaN(b)) return '' 134 | 135 | return '#' + hexOne(r) + hexOne(g) + hexOne(b) 136 | } 137 | 138 | // const HEX_INT_MAP = { 139 | // A: 10, 140 | // B: 11, 141 | // C: 12, 142 | // D: 13, 143 | // E: 14, 144 | // F: 15 145 | // }; 146 | 147 | // export function parseHex(hex) { 148 | // if (hex.length === 2) { 149 | // return (HEX_INT_MAP[hex[0].toUpperCase()] || +hex[0]) * 16 + (HEX_INT_MAP[hex[1].toUpperCase()] || +hex[1]); 150 | // } 151 | 152 | // return HEX_INT_MAP[hex[1].toUpperCase()] || +hex[1]; 153 | // }; 154 | -------------------------------------------------------------------------------- /src/components/color-picker/index.vue: -------------------------------------------------------------------------------- 1 | 73 | 74 | 75 | 180 | 181 | 182 | 362 | -------------------------------------------------------------------------------- /src/components/custom-loading/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 19 | 20 | 21 | 37 | -------------------------------------------------------------------------------- /src/components/custom-report/base/README.md: -------------------------------------------------------------------------------- 1 | # custom-report-layout 2 | 3 | ## 说明 4 | 5 | 碰到组件具有相同的布局时,可以将该组件的布局提取到该处,该处的渲染所需的 key 确定,不会因为 data 的值改变而改变。具体可见下。 6 | 7 | layout 中的某一组件应该是如下的样子。 8 | 9 | ```html 10 |
11 |
{{one}}
12 |
{{two}}
13 |
14 | ``` 15 | 16 | component 中的组件强调的更多是和数据的强耦合。 17 | 18 | ```html 19 |
20 |
{{reportData.countOne}}
21 |
{{reportData.countTwo}}
22 |
23 | ``` 24 | -------------------------------------------------------------------------------- /src/components/custom-report/base/base-chart-bar.vue: -------------------------------------------------------------------------------- 1 | 9 | 12 | 13 | 26 | -------------------------------------------------------------------------------- /src/components/custom-report/base/base-chart-line.vue: -------------------------------------------------------------------------------- 1 | 9 | 12 | 13 | 26 | -------------------------------------------------------------------------------- /src/components/custom-report/base/base-chart-pie.vue: -------------------------------------------------------------------------------- 1 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /src/components/custom-report/base/base-circle.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 71 | -------------------------------------------------------------------------------- /src/components/custom-report/base/base-table.vue: -------------------------------------------------------------------------------- 1 | 9 | 28 | 29 | 50 | -------------------------------------------------------------------------------- /src/components/custom-report/base/circle-icon.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 51 | -------------------------------------------------------------------------------- /src/components/custom-report/base/circle-three-text.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 67 | -------------------------------------------------------------------------------- /src/components/custom-report/base/three-line-text.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 50 | 51 | -------------------------------------------------------------------------------- /src/components/custom-report/custom-report-component/chart/alarm-event-distribution.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 49 | -------------------------------------------------------------------------------- /src/components/custom-report/custom-report-component/chart/alarm-interval-distribution.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 51 | -------------------------------------------------------------------------------- /src/components/custom-report/custom-report-component/chart/bar-pie.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | -------------------------------------------------------------------------------- /src/components/custom-report/custom-report-component/chart/bar-stack.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 56 | -------------------------------------------------------------------------------- /src/components/custom-report/custom-report-component/chart/driving-score-bar.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 89 | -------------------------------------------------------------------------------- /src/components/custom-report/custom-report-component/chart/driving-score-line.vue: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 49 | -------------------------------------------------------------------------------- /src/components/custom-report/custom-report-component/chart/event-processing-report.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /src/components/custom-report/custom-report-component/chart/line-area.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /src/components/custom-report/custom-report-component/chart/line.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /src/components/custom-report/custom-report-component/chart/pie.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 45 | -------------------------------------------------------------------------------- /src/components/custom-report/custom-report-component/group/table-pie.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 52 | -------------------------------------------------------------------------------- /src/components/custom-report/custom-report-component/group/vehicle-score.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | -------------------------------------------------------------------------------- /src/components/custom-report/custom-report-component/other/block.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 21 | -------------------------------------------------------------------------------- /src/components/custom-report/custom-report-component/other/circle-icon.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 38 | -------------------------------------------------------------------------------- /src/components/custom-report/custom-report-component/other/circle-security-rate.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | -------------------------------------------------------------------------------- /src/components/custom-report/custom-report-component/table/block.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 27 | -------------------------------------------------------------------------------- /src/components/custom-report/custom-report-component/text/text.vue: -------------------------------------------------------------------------------- 1 | 9 | 31 | 32 | 45 | 46 | 59 | 60 | -------------------------------------------------------------------------------- /src/components/custom-report/custom-report-component/text/three-line-text.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 34 | -------------------------------------------------------------------------------- /src/components/custom-report/default-container.vue: -------------------------------------------------------------------------------- 1 | 9 | 14 | 15 | 20 | 21 | 26 | -------------------------------------------------------------------------------- /src/components/custom-report/js/chart-variable.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: jsjzh 3 | * @Email: kimimi_king@163.com 4 | * @LastEditors: jsjzh 5 | * @Date: 2019-02-27 11:18:09 6 | * @LastEditTime: 2019-05-09 08:40:57 7 | * @Description: 该文件只是为了方便管理 default-chart-option,在 variable 中会将这些变量曝露出去 8 | */ 9 | export const defaultColor = [ '#ee7738', '#f59d2a', '#fcc419', '#ffe066', '#9bca63', '#b5c334', '#5e85a8', '#476480', '#34495d', '#2c3d4f' ] 10 | export const darkColor = [ '#dd6b66', '#759aa0', '#e69d87', '#8dc1a9', '#ea7e53', '#eedd78', '#73a373', '#73b9bc', '#7289ab', '#91ca8c', '#f49f42' ] 11 | export const shineColor = ['#c12e34', '#e6b600', '#0098d9', '#2b821d', '#005eaa', '#339ca8', '#cda819', '#32a487'] 12 | export const infographicColor = [ '#C1232B', '#27727B', '#FCCE10', '#E87C25', '#B5C334', '#FE8463', '#9BCA63', '#FAD860', '#F3A43B', '#60C0DD', '#D7504B', '#C6E579', '#F4E001', '#F0805A', '#26C0C0' ] 13 | 14 | export const defaultChartColors = defaultColor 15 | 16 | export const scoreColor = ['rgb(52, 184, 67)', 'rgb(51, 133, 253)', 'rgb(252, 196, 25)', 'rgb(255, 87, 87)'] 17 | 18 | /** 19 | * color 颜色系 20 | * title chart 标题 21 | * legend 图例组件 22 | * tooltip 提示框组件 23 | * series 系列列表 24 | * toolbox 工具栏 25 | * grid 网格 26 | * xAxis X 轴相关 27 | * yAxis Y 轴相关 28 | */ 29 | 30 | const pieDefaultData = [ 31 | { value: 335, name: 'no-data-1' }, 32 | { value: 310, name: 'no-data-2' }, 33 | { value: 234, name: 'no-data-3' }, 34 | { value: 135, name: 'no-data-4' }, 35 | { value: 465, name: 'no-data-5' }, 36 | { value: 379, name: 'no-data-6' }, 37 | { value: 664, name: 'no-data-7' } 38 | ] 39 | 40 | const barDefaultXData = ['no-data-1', 'no-data-2', 'no-data-3', 'no-data-4', 'no-data-5', 'no-data-6', 'no-data-7'] 41 | const barDefaultData = [120, 200, 150, 80, 70, 110, 130] 42 | 43 | const lineDefaultXData = ['no-data-1', 'no-data-2', 'no-data-3', 'no-data-4', 'no-data-5', 'no-data-6', 'no-data-7'] 44 | const lineDefaultData = [820, 932, 901, 934, 1290, 1330, 1320] 45 | 46 | export const defaultPieOption = { 47 | color: defaultChartColors, 48 | title: { show: false }, 49 | legend: { orient: 'vertical', right: '5%', top: '15%', icon: 'circle' }, 50 | tooltip: { confine: true, trigger: 'item', formatter: '{a}
{b}: {c} ({d}%)' }, 51 | series: [ 52 | { 53 | name: '未命名饼图', 54 | type: 'pie', 55 | radius: '55%', 56 | minAngle: 5, 57 | center: ['30%', '50%'], 58 | itemStyle: { normal: { borderWidth: 1, borderColor: '#e5e5e5' } }, 59 | label: { show: false }, 60 | labelLine: { show: false }, 61 | data: pieDefaultData 62 | } 63 | ] 64 | } 65 | 66 | export const defaultBarOption = { 67 | color: defaultChartColors, 68 | tooltip: { confine: true, trigger: 'axis', axisPointer: { type: 'cross' }, formatter: '{a}
{b} {c}' }, 69 | legend: { left: 0, top: 0 }, 70 | toolbox: { show: true, feature: { magicType: { show: true, type: ['line', 'bar'] }, restore: { show: true } } }, 71 | grid: { left: '5%', right: '5%', top: '15%', bottom: 0, containLabel: true }, 72 | xAxis: { data: barDefaultXData, axisPointer: { type: 'shadow' } }, 73 | yAxis: { type: 'value' }, 74 | series: [{ name: '未命名柱状图', type: 'bar', barWidth: '50%', data: barDefaultData }] 75 | } 76 | 77 | export const defaultLineOption = { 78 | color: defaultChartColors, 79 | tooltip: { confine: true, trigger: 'axis', axisPointer: { type: 'cross' } }, 80 | grid: { left: '5%', right: '5%', top: '10%', bottom: 0, containLabel: true }, 81 | xAxis: { type: 'category', boundaryGap: true, data: lineDefaultXData }, 82 | yAxis: { type: 'value' }, 83 | series: [{ name: '未命名曲线图图', type: 'line', data: lineDefaultData, smooth: false }] 84 | } 85 | -------------------------------------------------------------------------------- /src/components/custom-report/js/variable.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: jsjzh 3 | * @Email: kimimi_king@163.com 4 | * @LastEditors: jsjzh 5 | * @Date: 2019-02-25 17:34:43 6 | * @LastEditTime: 2019-05-14 11:37:44 7 | * @Description: 8 | * 组件公用变量 9 | * 该处不设置 data 相关内容,只设置一些特殊的 chart 样式 10 | */ 11 | export { defaultChartColors, defaultPieOption, defaultBarOption, defaultLineOption } from './chart-variable' 12 | 13 | import { scoreColor } from './chart-variable' 14 | 15 | export const tableOption = [{ prop: 'name', label: '名称' }, { prop: 'value', label: '次数' }] 16 | 17 | export const vehicleScoreTableOneOption = [ 18 | { prop: 'plate_no', label: '车牌' }, 19 | { prop: 'score', label: '评分(前 10 名)' } 20 | ] 21 | 22 | export const vehicleScoreTableTwoOption = [ 23 | { prop: 'plate_no', label: '车牌' }, 24 | { prop: 'score', label: '评分(后 10 名)' } 25 | ] 26 | 27 | export const barFirstOption = { 28 | toolbox: { show: false }, 29 | xAxis: { splitNumber: 1 }, 30 | series: [{ name: '柱状图随机数据' }] 31 | } 32 | 33 | export const barStackOption = { 34 | toolbox: { show: false }, 35 | xAxis: { show: false, data: [] }, 36 | yAxis: { show: false }, 37 | legend: { show: false }, 38 | angleAxis: {}, 39 | radiusAxis: { type: 'category', z: 10 }, 40 | polar: {}, 41 | series: [] 42 | } 43 | 44 | export const pieOption = { series: [{ name: '饼图随机数据' }] } 45 | 46 | export const pieSecondOption = { series: [{ name: '饼图2随机数据', center: ['23%', '50%'] }] } 47 | 48 | export const lineOption = { series: [{ name: '折线图随机数据' }] } 49 | 50 | export const lineAreaOptions = { xAxis: { boundaryGap: false } } 51 | 52 | export const alarmEventDistributionPieOption = { 53 | title: { text: '报警事件分布', left: '15%', top: '2%', show: true }, 54 | legend: { orient: 'vertical', right: '25%', top: '20%', icon: 'circle' }, 55 | series: [{ name: '事件分布', radius: '75%', center: ['25%', '55%'] }] 56 | } 57 | 58 | export const eventProcessingReportPieOption = { 59 | title: { text: '事件处理报表', left: '15%', top: '2%', show: true }, 60 | legend: { orient: 'vertical', right: '25%', top: '25%', icon: 'circle' }, 61 | series: [{ name: '事件处理', radius: '75%', center: ['25%', '55%'] }] 62 | } 63 | 64 | export const alarmIntervalDistributionLineOption = { 65 | legend: { left: '10%', data: ['在线车辆', '报警事件次数'] }, 66 | tooltip: { formatter: '{b} 点
在线车辆 {c} 辆' }, 67 | yAxis: [{ type: 'value', name: '辆' }, { type: 'value', name: '次' }], 68 | xAxis: { type: 'category' }, 69 | grid: { top: '15%' }, 70 | series: [ 71 | { name: '在线车辆', type: 'bar', yAxisIndex: 0 }, 72 | { name: '报警事件次数', type: 'line', yAxisIndex: 1, areaStyle: {} } 73 | ] 74 | } 75 | 76 | export const vehicleScorePieOption = { 77 | legend: { left: '35%', bottom: '10%' }, 78 | grid: { top: '10%' }, 79 | series: { name: '车辆评分', center: ['50%', '35%'], radius: '60%' } 80 | } 81 | 82 | export const drivingScoreLineOption = { 83 | series: { name: '驾驶评分', smooth: true }, 84 | grid: { top: '12%' }, 85 | yAxis: [{ type: 'value', name: '分' }], 86 | visualMap: { 87 | show: false, 88 | pieces: [ 89 | { gte: 0, lt: 40, color: scoreColor[3] }, 90 | { gte: 40, lt: 60, color: scoreColor[2] }, 91 | { gte: 60, lt: 80, color: scoreColor[1] }, 92 | { gte: 80, lt: 100, color: scoreColor[0] } 93 | ] 94 | } 95 | } 96 | 97 | export const drivingScoreBarOption = { 98 | toolbox: { show: false }, 99 | yAxis: { type: 'value', name: '人', axisLine: { show: false }, splitLine: { show: false } }, 100 | xAxis: { axisLine: { show: false }, splitLine: { show: false } }, 101 | series: [{ name: '驾驶评分' }], 102 | legend: { show: false } 103 | } 104 | 105 | export const eventName = [ 106 | { prop: 'pcw', name: '行人碰撞提示' }, 107 | { prop: 'fcw', name: '车辆碰撞预警' }, 108 | { prop: 'ldw', name: '频繁变道' }, 109 | { prop: 'hmw', name: '长时间跟车过近' }, 110 | { prop: 'sli', name: '连续超速' }, 111 | { prop: 'cw', name: '疑似碰撞及侧翻' }, 112 | { prop: 'drastic', name: '激烈驾驶' } 113 | ] 114 | 115 | export const eventDealName = [ 116 | { prop: 'untreat', name: '未处理' }, 117 | { prop: 'comms', name: '已与司机确认沟通' }, 118 | { prop: 'park', name: '建议停车休息' }, 119 | { prop: 'slow', name: '建议降低车速' }, 120 | { prop: 'ignore', name: '正常情况可忽略' }, 121 | { prop: 'other', name: '其他' } 122 | ] 123 | -------------------------------------------------------------------------------- /src/components/custom-report/mixins/chart-default.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: jsjzh 3 | * @Email: kimimi_king@163.com 4 | * @LastEditors: jsjzh 5 | * @Date: 2019-02-27 10:20:10 6 | * @LastEditTime: 2019-05-12 11:16:18 7 | * @Description: chart 实例默认 mixin 配置 8 | */ 9 | import { debounce } from 'lodash' 10 | import { defaultLineOption, defaultPieOption, defaultBarOption } from '../js/variable' 11 | 12 | const chartOptions = { defaultLineOption, defaultPieOption, defaultBarOption } 13 | 14 | export default { 15 | methods: { 16 | // 同时还接受父组件传来的 option 17 | setOption(option) { 18 | this.$$chartInstance && this.$$chartInstance.setOption(option) 19 | // TODO 某些情况下,页面第一次载入会不显示图表,猜想应该是 microtask 和 macrotask 的锅 20 | // 后续会去看看 echarts 源码里是如何 setOption 的,暂时用延时来处理 21 | setTimeout(() => { 22 | this.handleResize() 23 | }, 1000) 24 | }, 25 | handleResize() { 26 | this.$nextTick(() => { 27 | this.$$chartInstance && typeof this.$$chartInstance === 'object' && this.$$chartInstance.resize() 28 | }) 29 | }, 30 | initChart(refName) { 31 | let chartDom = this.$refs[refName] 32 | this.$$chartInstance = this.$echarts.init(chartDom) 33 | }, 34 | addResizeListener() { 35 | this.$$events.resize = debounce(this.handleResize, 200, true) 36 | window.addEventListener('resize', this.$$events.resize) 37 | } 38 | }, 39 | mounted() { 40 | this.$$chartInstance = null 41 | this.$$events = { resize: null } 42 | 43 | this.initChart(this.$options.name) 44 | this.setOption(chartOptions[this.optionName]) 45 | this.$emit('reload', this.$$chartInstance) 46 | this.addResizeListener() 47 | }, 48 | beforeDestroy() { 49 | window.removeEventListener('resize', this.$$events.resize) 50 | this.$$chartInstance.dispose() 51 | this.$$chartInstance = null 52 | this.$$events.resize = null 53 | this.$$events = null 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/components/custom-report/report-tool-select-query.vue: -------------------------------------------------------------------------------- 1 | 9 | 65 | 66 | 182 | 183 | 241 | -------------------------------------------------------------------------------- /src/components/custom-scrollbar/bar.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /src/components/custom-scrollbar/index.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 49 | 50 | -------------------------------------------------------------------------------- /src/components/default-framework/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 93 | 94 | 166 | 167 | 213 | 214 | -------------------------------------------------------------------------------- /src/components/default-layout-editor/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 76 | 77 | 156 | 157 | 237 | -------------------------------------------------------------------------------- /src/components/drag-group/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | 16 | 18 | -------------------------------------------------------------------------------- /src/components/drag-item/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 35 | 36 | 38 | -------------------------------------------------------------------------------- /src/components/te-icon/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 16 | 17 | -------------------------------------------------------------------------------- /src/directive/clickoutside/clickoutside.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: jsjzh 3 | * @Email: kimimi_king@163.com 4 | * @Date: 2018-10-30 16:30:49 5 | * @LastEditors: jsjzh 6 | * @LastEditTime: 2019-03-12 16:26:56 7 | * @Description: 判断鼠标点击是否在被绑定的元素上 8 | * @use 9 | * import Clickoutside from "@/directive/clickoutside" 10 | * directives: { Clickoutside } 11 | *
12 | */ 13 | import { on, off } from '@/utils/dom' 14 | 15 | const nodeList = [] 16 | const context = '@@clickoutsideContext' 17 | 18 | let startClick 19 | let mark = 0 20 | let isClear = true 21 | 22 | function handleStart(e) { 23 | startClick = e 24 | } 25 | 26 | function handleEnd(e) { 27 | nodeList.forEach(node => node[context].handleDom(e, startClick)) 28 | } 29 | 30 | function handleDomFn(el, binding) { 31 | return function(mouseup = {}, mousedown = {}) { 32 | if (el.contains(mouseup.target) || el.contains(mousedown.target)) return 33 | binding.value() 34 | } 35 | } 36 | 37 | export default { 38 | bind(el, binding) { 39 | // 确保 document 绑定的 click 事件只有一次 40 | if (isClear) { 41 | on(document, 'mousedown', handleStart) 42 | on(document, 'mouseup', handleEnd) 43 | isClear = false 44 | } 45 | // 缓存该 dom 绑定的相关指令至 el[context] 46 | // 在鼠标抬起 handleEnd 的时候批量调用 47 | el[context] = { 48 | id: mark++, 49 | handleDom: handleDomFn(el, binding) 50 | } 51 | 52 | nodeList.push(el) 53 | }, 54 | 55 | update(el, binding) { 56 | el[context].handleDom = handleDomFn(el, binding) 57 | }, 58 | 59 | unbind(el) { 60 | let len = nodeList.length 61 | 62 | for (let i = 0; i < len; i++) { 63 | if (nodeList[i][context].id === el[context].id) { 64 | nodeList.splice(i, 1) 65 | break 66 | } 67 | } 68 | delete el[context] 69 | 70 | if (nodeList.length === 0) { 71 | off(document, 'mousedown', handleStart) 72 | off(document, 'mouseup', handleEnd) 73 | isClear = true 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/directive/clickoutside/index.js: -------------------------------------------------------------------------------- 1 | import clickoutside from './clickoutside' 2 | 3 | clickoutside.install = function(Vue) { 4 | Vue.directive('clickoutside', clickoutside) 5 | } 6 | 7 | export default clickoutside 8 | -------------------------------------------------------------------------------- /src/directive/customLoading/customLoading.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import customLoading from '@/components/custom-loading' 3 | 4 | const context = '@@loadingContext' 5 | const Mask = Vue.extend(customLoading) 6 | 7 | const toggleLoading = (el, binding) => { 8 | if (binding.value) { 9 | if (binding.modifiers.fullscreen) { 10 | insertDom(document.body, el, binding) 11 | } 12 | } else { 13 | el[context].instance.visible = false 14 | } 15 | } 16 | 17 | const insertDom = (parent, el, binding) => { 18 | el[context].instance.visible = true 19 | parent.appendChild(el[context].mask) 20 | } 21 | 22 | export default { 23 | bind: function(el, binding, vnode) { 24 | const mask = new Mask({ 25 | el: document.createElement('div'), 26 | data() { 27 | return { 28 | fullscreen: !binding.modifiers.fullscreen 29 | } 30 | } 31 | }) 32 | if (!el[context]) { 33 | el[context] = { 34 | instance: mask, 35 | mask: mask.$el 36 | } 37 | } else { 38 | el[context].instance = mask 39 | el[context].mask = mask.$el 40 | } 41 | binding.value && toggleLoading(el, binding) 42 | }, 43 | update: function(el, binding) { 44 | if (binding.oldValue !== binding.value) { 45 | toggleLoading(el, binding) 46 | } 47 | }, 48 | unbind: function(el, binding) { 49 | el[context].instance && el[context].instance.$destroy() 50 | el[context] = null 51 | delete el[context] 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/directive/customLoading/index.js: -------------------------------------------------------------------------------- 1 | import customLoading from './customLoading' 2 | import service from './service' 3 | 4 | export default { 5 | install(Vue) { 6 | Vue.directive('customLoading', customLoading) 7 | }, 8 | service 9 | } 10 | -------------------------------------------------------------------------------- /src/directive/customLoading/service.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import customLoading from '@/components/custom-loading' 3 | 4 | const Mask = Vue.extend(customLoading) 5 | 6 | Mask.prototype.close = function() { 7 | this.visible = false 8 | let timer = setTimeout(() => { 9 | if (this.$el && this.$el.parentNode) { 10 | this.$el.parentNode.removeChild(this.$el) 11 | } 12 | this.$destroy() 13 | clearTimeout(timer) 14 | }, 2000) 15 | } 16 | 17 | const Loading = options => { 18 | let parent = document.querySelector(options.target) 19 | let mask = new Mask({ 20 | el: document.createElement('div'), 21 | data: options 22 | }) 23 | parent.appendChild(mask.$el) 24 | mask.visible = true 25 | return mask 26 | } 27 | 28 | export default Loading 29 | -------------------------------------------------------------------------------- /src/directive/dragDialog/dragDialog.js: -------------------------------------------------------------------------------- 1 | import { on, off } from '@/utils/dom' 2 | 3 | const context = '@@dragDialogContext' 4 | 5 | export default { 6 | bind(el, binding, vnode) { 7 | const header = el.querySelector('.el-dialog__header') 8 | const dialog = el.querySelector('.el-dialog') 9 | header.style.cssText += ';cursor:move;' 10 | dialog.style.cssText += ';top:0px;' 11 | 12 | function handleMousedown(e) { 13 | document.documentElement.style.cssText += ';user-select:none;' 14 | 15 | let maxLeft = window.innerWidth - dialog.offsetWidth 16 | let startLeft = e.clientX 17 | let oldLeft = dialog.offsetLeft 18 | 19 | let maxTop = window.innerHeight - dialog.offsetHeight 20 | let startTop = e.clientY 21 | let oldTop = dialog.offsetTop 22 | 23 | dialog.style.cssText += `;margin:0;left:${oldLeft}px;top:${oldTop}px;` 24 | 25 | function handleMousemove(e) { 26 | let moveX = oldLeft + e.clientX - startLeft 27 | let currLeft = (dialog.style.left = Math.min(Math.max(0, moveX), maxLeft) + 'px') 28 | let moveY = oldTop + e.clientY - startTop 29 | let currTop = (dialog.style.top = Math.min(Math.max(0, moveY), maxTop) + 'px') 30 | 31 | dialog.style.cssText += `;left:${currLeft};top:${currTop};` 32 | 33 | // emit onDrag event 34 | vnode.child.$emit('dragDialog') 35 | } 36 | 37 | function handleMouseup(e) { 38 | document.documentElement.style.cssText += ';user-select:auto;' 39 | off(document, 'mousemove', handleMousemove) 40 | off(document, 'mouseup', handleMouseup) 41 | } 42 | 43 | on(document, 'mousemove', handleMousemove) 44 | on(document, 'mouseup', handleMouseup) 45 | } 46 | 47 | on(header, 'mousedown', handleMousedown) 48 | 49 | if (!el[context]) { 50 | el[context] = { 51 | removeHandle: handleMousedown 52 | } 53 | } else { 54 | el[context].removeHandle = handleMousedown 55 | } 56 | }, 57 | unbind(el, binding, vnode) { 58 | const header = el.querySelector('.el-dialog__header') 59 | off(header, 'mousedown', el[context].removeHandle) 60 | el[context] = null 61 | delete el[context] 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/directive/dragDialog/index.js: -------------------------------------------------------------------------------- 1 | import dragDialog from './dragDialog' 2 | 3 | dragDialog.install = function(Vue) { 4 | Vue.directive('dragDialog', dragDialog) 5 | } 6 | 7 | export default dragDialog 8 | -------------------------------------------------------------------------------- /src/directive/waves/index.js: -------------------------------------------------------------------------------- 1 | import waves from './waves' 2 | 3 | waves.install = function(Vue) { 4 | Vue.directive('waves', waves) 5 | } 6 | 7 | export default waves 8 | -------------------------------------------------------------------------------- /src/directive/waves/waves.css: -------------------------------------------------------------------------------- 1 | .waves-ripple { 2 | position: absolute; 3 | border-radius: 100%; 4 | pointer-events: none; 5 | user-select: none; 6 | transform: scale(0); 7 | opacity: 1; 8 | } 9 | 10 | .waves-ripple.z-active { 11 | opacity: 0; 12 | transform: scale(2); 13 | transition: opacity 1.2s ease-out, transform 0.6s ease-out; 14 | } 15 | -------------------------------------------------------------------------------- /src/directive/waves/waves.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: jsjzh 3 | * @Email: kimimi_king@163.com 4 | * @LastEditors: jsjzh 5 | * @Date: 2019-03-13 08:39:08 6 | * @LastEditTime: 2019-03-13 15:17:01 7 | * @Description: 使用方式 8 | *
test
9 | *
test
10 | */ 11 | import './waves.css' 12 | 13 | import { on, off } from '@/utils/dom' 14 | 15 | const context = `@@wavesContext` 16 | 17 | function handleClick(el, binding) { 18 | el.style.position = 'relative' 19 | el.style.overflow = 'hidden' 20 | 21 | function handle(e) { 22 | let options = { color: 'rgba(0, 0, 0, 0.15)' } 23 | options = { ...options, ...binding.value } 24 | // getBoundingClientRect 弊端 25 | // 每次调用都会强制浏览器重新计算整个页面的布局,可能给网页造成相当大的闪烁 26 | // const rect = el.getBoundingClientRect() 27 | let ripple = el.querySelector('.waves-ripple') 28 | if (!ripple) { 29 | ripple = document.createElement('span') 30 | ripple.className = 'waves-ripple' 31 | ripple.style.height = ripple.style.width = Math.max(el.offsetWidth, el.offsetHeight) + 'px' 32 | el.appendChild(ripple) 33 | } else { 34 | ripple.className = 'waves-ripple' 35 | } 36 | ripple.style.top = 37 | (e.pageY - el.offsetTop - ripple.offsetHeight / 2 - document.documentElement.scrollTop || 38 | document.body.scrollTop) + 'px' 39 | ripple.style.left = 40 | (e.pageX - el.offsetLeft - ripple.offsetWidth / 2 - document.documentElement.scrollLeft || 41 | document.body.scrollLeft) + 'px' 42 | ripple.style.backgroundColor = options.color 43 | ripple.className = 'waves-ripple z-active' 44 | } 45 | 46 | if (!el[context]) { 47 | el[context] = { 48 | removeHandle: handle 49 | } 50 | } else { 51 | el[context].removeHandle = handle 52 | } 53 | return handle 54 | } 55 | 56 | export default { 57 | bind(el, binding) { 58 | on(el, 'click', handleClick(el, binding)) 59 | }, 60 | update(el, binding) { 61 | off(el, 'click', el[context].removeHandle) 62 | on(el, 'click', handleClick(el, binding)) 63 | }, 64 | unbind(el) { 65 | off(el, 'click', el[context].removeHandle) 66 | el[context] = null 67 | delete el[context] 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/layout/home/index.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 41 | 42 | 48 | 49 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App' 3 | import router from './router' 4 | 5 | import 'element-ui/lib/theme-chalk/index.css' 6 | import '../static/css/reset.css' 7 | import '@/styles/global.scss' 8 | import '@/assets/icons/index.css' 9 | import '@/assets/icons/iconfont.css' 10 | 11 | Vue.config.productionTip = false 12 | 13 | import './mock' 14 | import './mount' 15 | 16 | const app = new Vue({ 17 | el: '#app', 18 | router, 19 | render: h => h(App) 20 | }) 21 | -------------------------------------------------------------------------------- /src/mixins/methods/col-style.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: jsjzh 3 | * @Email: kimimi_king@163.com 4 | * @LastEditors: jsjzh 5 | * @Date: 2019-02-28 17:01:29 6 | * @LastEditTime: 2019-05-16 17:10:33 7 | * @Description: 拖拽排版图表用 col-style 8 | */ 9 | export default { 10 | methods: { 11 | previewColStyle({ width, height, baseWidth = 100, baseHeight = 3, layoutRow = 24 }, mixinStyle = {}) { 12 | return { 13 | width: `${(baseWidth * width) / layoutRow}%`, 14 | height: `${height / baseHeight}px`, 15 | ...mixinStyle 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/mock/index.js: -------------------------------------------------------------------------------- 1 | window.$$XMLHttpRequest = window.XMLHttpRequest 2 | 3 | const Mock = require('mockjs') 4 | import { transUrlParams, transBodyParams, getRamdomCountByNum } from '@/utils' 5 | import { componentDatas, dragReportData, reportListDatas } from './variable' 6 | 7 | let $$id = 0 8 | 9 | let _componentDatas = componentDatas 10 | let _dragReportData = dragReportData 11 | let _reportListDatas = reportListDatas 12 | 13 | Mock.setup({ 14 | timeout: 200 15 | }) 16 | 17 | import './modules/getcustomccdeptreport' 18 | 19 | const randomCount = '@integer(10, 100)' 20 | const randomKeyArr = [ 21 | 'email marketing', 22 | 'union advertising', 23 | 'search engine', 24 | 'from video', 25 | 'pop up ads', 26 | 'marketing', 27 | 'direct access' 28 | ] 29 | const randomXAxisArr = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'] 30 | 31 | const alarmType = [ 32 | 'pedestrian collision warning', 33 | 'vehicle collision warning', 34 | 'frequently change lanes', 35 | 'too close for too long', 36 | 'continuous speed', 37 | 'suspected collision and rollover', 38 | 'intense driving' 39 | ] 40 | 41 | const processState = [ 42 | 'untreated', 43 | 'has been confirmed with the driver', 44 | 'Suggest stopping for a rest', 45 | 'Recommend speed reduction', 46 | 'Normal circumstances can be ignored', 47 | 'other' 48 | ] 49 | 50 | /** 51 | * 获取某一报表详细布局信息 52 | */ 53 | Mock.mock(/getreportcomponentinfo/, 'get', config => { 54 | let { reportUnionKey } = transUrlParams(config.url) 55 | return _dragReportData.find(report => report.reportUnionKey === reportUnionKey) 56 | }) 57 | /** 58 | * 获取自定义报表的组件列表 59 | */ 60 | Mock.mock(/getcomponentinfo/, 'get', config => _componentDatas) 61 | /** 62 | * 获取自定义报表列表 63 | */ 64 | Mock.mock(/getreportstructurelist/, 'get', config => _reportListDatas) 65 | /** 66 | * 新增自定义报表 67 | */ 68 | Mock.mock(/operatestructureinfo/, 'post', config => { 69 | let { title, children } = transBodyParams(config.body) 70 | let newReport = { title, children, reportUnionKey: $$id++ } 71 | _reportListDatas.push(newReport) 72 | return newReport 73 | }) 74 | /** 75 | * 删除自定义报表 76 | */ 77 | Mock.mock(/delstructureinfo/, 'post', config => { 78 | let { reportUnionKey } = transBodyParams(config.body) 79 | _reportListDatas = _reportListDatas.filter(report => report.reportUnionKey !== reportUnionKey) 80 | return { status: 'success', code: 200, data: { reportUnionKey } } 81 | }) 82 | 83 | Mock.mock(/updatestructureinfo/, 'post', config => { 84 | let updateInfo = transBodyParams(config.body) 85 | let report = _dragReportData.find(reports => reports.reportUnionKey === updateInfo.reportUnionKey) 86 | report = { ...report, ...updateInfo } 87 | return { status: 'success', code: 200, data: { reportUnionKey: updateInfo.reportUnionKey } } 88 | }) 89 | 90 | Mock.mock(/report\/getCountData/, 'get', config => 91 | Mock.mock({ 92 | countKey: { 93 | countOne: randomCount, 94 | countTwo: randomCount, 95 | rate: '@integer(-100, 100)', 96 | average: randomCount 97 | } 98 | }) 99 | ) 100 | 101 | Mock.mock(/report\/getRandomData/, 'get', config => 102 | Mock.mock( 103 | randomKeyArr.map((name, index) => ({ 104 | name: name, 105 | value: randomCount, 106 | xdata: randomXAxisArr[index], 107 | arrData: new Array(7).fill(randomCount) 108 | })) 109 | ) 110 | ) 111 | 112 | Mock.mock(/report\/getAlarmData/, 'get', config => 113 | Mock.mock( 114 | alarmType.map((name, index) => ({ 115 | name: name, 116 | value: randomCount, 117 | xdata: randomXAxisArr[index], 118 | arrData: new Array(7).fill(randomCount) 119 | })) 120 | ) 121 | ) 122 | 123 | Mock.mock(/report\/getDealData/, 'get', config => 124 | Mock.mock( 125 | processState.map((name, index) => ({ 126 | name: name, 127 | value: randomCount, 128 | xdata: randomXAxisArr[index], 129 | arrData: new Array(7).fill(randomCount) 130 | })) 131 | ) 132 | ) 133 | 134 | Mock.mock(/report\/getAlarmTimeData/, 'get', config => 135 | Mock.mock( 136 | new Array(24).fill(null).map((time, index) => ({ 137 | xdata: index, 138 | carCount: randomCount, 139 | alarmCount: randomCount 140 | })) 141 | ) 142 | ) 143 | 144 | Mock.mock(/report\/getVehicleScoreData/, 'get', config => { 145 | return Mock.mock( 146 | new Array(20).fill(null).map(() => ({ 147 | score: randomCount, 148 | plate: '沪DK@integer(1000, 9999)' 149 | })) 150 | ) 151 | }) 152 | 153 | Mock.mock(/report\/getDrivingScoreLineData/, 'get', config => { 154 | return Mock.mock( 155 | new Array(31).fill(null).map((time, index) => ({ 156 | xdata: index, 157 | score: randomCount 158 | })) 159 | ) 160 | }) 161 | 162 | Mock.mock(/report\/getDrivingScoreBarData/, 'get', config => { 163 | return Mock.mock( 164 | new Array(31).fill(null).map((time, index) => { 165 | const counts = getRamdomCountByNum(100, 4) 166 | return { 167 | xdata: index, 168 | countOne: counts[0], 169 | countTwo: counts[1], 170 | countThree: counts[2], 171 | countFour: counts[3] 172 | } 173 | }) 174 | ) 175 | }) 176 | -------------------------------------------------------------------------------- /src/mock/modules/getcustomccdeptreport.js: -------------------------------------------------------------------------------- 1 | const Mock = require('mockjs') 2 | 3 | import { getRamdomCountByNum } from '@/utils' 4 | 5 | const randomCount = '@integer(10, 100)' 6 | 7 | const baseParams = [ 8 | { prop: 'safe_score', title: '安全评分' }, 9 | { prop: 'day_score', title: '日间评分' }, 10 | { prop: 'night_score', title: '夜间评分' }, 11 | { prop: 'total_hours', title: '总时长' }, 12 | { prop: 'avg_hours', title: '平均时长' }, 13 | { prop: 'operate_score', title: '运营评分' }, 14 | { prop: 'unoperate_score', title: '非运营评分' }, 15 | { prop: 'expressway_score', title: '高速评分' }, 16 | { prop: 'normalway_score', title: '非高速评分' }, 17 | { prop: 'total_mile', title: '总里程' }, 18 | { prop: 'avg_mile', title: '平均里程' }, 19 | { prop: 'total_event', title: '总事件' }, 20 | { prop: 'avg_event', title: '平均事件' }, 21 | { prop: 'total_operate', title: '运营次数' }, 22 | { prop: 'avg_operate', title: '平均运营次数' }, 23 | { prop: 'max_speed', title: '最高车速' }, 24 | { prop: 'avg_speed', title: '平均车速' } 25 | ] 26 | 27 | const dimensionScores = [ 28 | { prop: 'cornering', title: '转弯评分', tips: '继续保持 or 危险请注意' }, 29 | { prop: 'acc', title: '加速评分', tips: '继续保持 or 危险请注意' }, 30 | { prop: 'slow', title: '减速评分', tips: '继续保持 or 危险请注意' }, 31 | { prop: 'sli', title: '速度评分', tips: '继续保持 or 危险请注意' }, 32 | { prop: 'fcw', title: '追尾碰撞评分', tips: '继续保持 or 危险请注意' }, 33 | { prop: 'lane', title: '车道保持评分', tips: '继续保持 or 危险请注意' }, 34 | { prop: 'hmw', title: '车距控制评分', tips: '继续保持 or 危险请注意' }, 35 | { prop: 'pcw', title: '行人和非机动碰撞评分', tips: '继续保持 or 危险请注意' }, 36 | { prop: 'turn_ctrl', title: '转向灯控制评分', tips: '继续保持 or 危险请注意' }, 37 | { prop: 'break_ctrl', title: '刹车控制评分', tips: '继续保持 or 危险请注意' } 38 | ] 39 | 40 | const eventInfos = [ 41 | 'pcw', 42 | 'fcw', 43 | 'ldw', 44 | 'hmw', 45 | 'sli', 46 | 'cw', 47 | 'drastic', 48 | 'untreat', 49 | 'comms', 50 | 'park', 51 | 'slow', 52 | 'ignore', 53 | 'other' 54 | ] 55 | 56 | const baseParamData = {} 57 | baseParams.forEach(baseParam => { 58 | baseParamData[baseParam.prop] = { 59 | name: baseParam.title, 60 | value: randomCount, 61 | trend: '@integer(-1, 1)', 62 | rate: randomCount 63 | } 64 | }) 65 | const dimensionScoreData = {} 66 | dimensionScores.forEach(dimensionScore => { 67 | dimensionScoreData[dimensionScore.prop] = { 68 | name: dimensionScore.title, 69 | score: '@integer(0, 10)', 70 | tips: dimensionScore.tips 71 | } 72 | }) 73 | const eventInfoData = {} 74 | eventInfos.forEach(eventInfo => { 75 | eventInfoData[eventInfo] = randomCount 76 | }) 77 | const eventHourListData = new Array(24).fill(null).map((value, index) => ({ 78 | hour: index, 79 | vehicle_count: randomCount, 80 | event_count: randomCount, 81 | operation_count: randomCount 82 | })) 83 | 84 | const vehicleScoreData = { 85 | excellent: randomCount, 86 | good: randomCount, 87 | normal: randomCount, 88 | bad: randomCount, 89 | ranking_top_10: new Array(10).fill(null).map((value, index) => ({ 90 | score: randomCount, 91 | plate_no: '沪DK@integer(1000, 9999)' 92 | })), 93 | ranking_tail_10: new Array(10).fill(null).map((value, index) => ({ 94 | score: randomCount, 95 | plate_no: '沪DK@integer(1000, 9999)' 96 | })) 97 | } 98 | 99 | Mock.mock(/report\/getcustomccdeptreport/, 'post', config => { 100 | const driveScoreListData = new Array(31).fill(null).map((value, index) => { 101 | const counts = getRamdomCountByNum(100, 4) 102 | return { 103 | date: index, 104 | score: randomCount, 105 | excellent: counts[0], 106 | good: counts[1], 107 | normal: counts[2], 108 | bad: counts[3] 109 | } 110 | }) 111 | 112 | return Mock.mock({ 113 | status: 200, 114 | message: 'OK', 115 | data: { 116 | base_param: baseParamData, 117 | dimension_score: dimensionScoreData, 118 | event_info: eventInfoData, 119 | event_hour_list: eventHourListData, 120 | vehicle_score: vehicleScoreData, 121 | drive_score_list: driveScoreListData 122 | } 123 | }) 124 | }) 125 | -------------------------------------------------------------------------------- /src/mount/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import echarts from 'echarts' 3 | import { $msg } from '@/utils' 4 | import ElementUI from 'element-ui' 5 | import customLoading from '@/directive/customLoading' 6 | 7 | Vue.prototype.$echarts = echarts 8 | Vue.prototype.$msg = $msg 9 | Vue.prototype.$customLoading = customLoading.service 10 | 11 | Vue.use(ElementUI) 12 | Vue.use(customLoading) 13 | 14 | import teIcon from '@/components/te-icon' 15 | Vue.component('te-icon', teIcon) 16 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | 4 | const importLayout = name => require(`@/layout/${name}/index.vue`).default 5 | const importView = name => require(`@/views/${name}/index.vue`).default 6 | 7 | const importLayoutJsx = name => require(`@/layout/${name}/index.js`).default 8 | const importViewJsx = name => require(`@/views/${name}/index.js`).default 9 | 10 | Vue.use(Router) 11 | 12 | export const routes = [ 13 | { 14 | path: '/', 15 | name: 'home', 16 | component: importLayout('home'), 17 | children: [ 18 | { 19 | path: 'palette', 20 | name: 'palette', 21 | component: importView('palette') 22 | }, 23 | { 24 | path: 'dragReport', 25 | name: 'dragReport', 26 | component: importView('dragReport') 27 | }, 28 | { 29 | path: 'previewReport', 30 | name: 'previewReport', 31 | component: importView('previewReport') 32 | }, 33 | { 34 | path: 'editComponent', 35 | name: 'editComponent', 36 | component: importView('editComponent') 37 | }, 38 | { 39 | path: 'customReportList', 40 | name: 'customReportList', 41 | component: importView('customReportList') 42 | }, 43 | { 44 | path: 'previewComponent', 45 | name: 'previewComponent', 46 | component: importView('previewComponent') 47 | }, 48 | { 49 | path: 'waves', 50 | name: 'waves', 51 | component: importView('waves') 52 | }, 53 | { 54 | path: 'dragDialog', 55 | name: 'dragDialog', 56 | component: importView('dragDialog') 57 | }, 58 | { 59 | path: 'customLoading', 60 | name: 'customLoading', 61 | component: importView('customLoading') 62 | }, 63 | { 64 | path: 'customScrollbar', 65 | name: 'customScrollbar', 66 | component: importView('customScrollbar') 67 | }, 68 | { 69 | path: 'dragList', 70 | name: 'dragList', 71 | component: importView('dragList') 72 | } 73 | ] 74 | }, 75 | { 76 | path: '*', 77 | redirect: '/' 78 | } 79 | ] 80 | 81 | export default new Router({ 82 | mode: 'history', 83 | base: process.env.BASE_URL, 84 | routes 85 | }) 86 | -------------------------------------------------------------------------------- /src/styles/global.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: jsjzh 3 | * @Email: kimimi_king@163.com 4 | * @LastEditors: jsjzh 5 | * @Date: 2019-02-28 11:41:17 6 | * @LastEditTime: 2019-05-14 11:45:36 7 | * @Description: 全局样式 8 | */ 9 | @import './variable.scss'; 10 | 11 | body, 12 | html { 13 | font-size: 14px; 14 | color: $deep-color-1; 15 | } 16 | 17 | .slide-fade-enter-active { 18 | transition: all 0.3s ease; 19 | } 20 | .slide-fade-leave-active { 21 | transition: all 0.4s cubic-bezier(1, 0.5, 0.8, 1); 22 | } 23 | .slide-fade-enter, 24 | .slide-fade-leave-to { 25 | transform: translateY(10px); 26 | opacity: 0; 27 | } 28 | 29 | .ps-a { 30 | position: absolute; 31 | } 32 | 33 | .ps-r { 34 | position: relative; 35 | } 36 | 37 | .ps-center { 38 | top: 50%; 39 | left: 50%; 40 | transform: translate(-50%, -50%); 41 | } 42 | 43 | .flex { 44 | display: flex; 45 | } 46 | 47 | .flex-1 { 48 | flex: 1; 49 | } 50 | 51 | .flex-col { 52 | flex-flow: column; 53 | } 54 | 55 | .flex-just-start { 56 | justify-content: flex-start; 57 | } 58 | 59 | .flex-align-center { 60 | align-items: center; 61 | } 62 | 63 | .flex-center { 64 | justify-content: center; 65 | align-items: center; 66 | } 67 | 68 | .flex-nowrap { 69 | flex-wrap: nowrap; 70 | } 71 | 72 | .flex-wrap { 73 | flex-wrap: wrap; 74 | } 75 | 76 | .mr-center { 77 | margin: 0 auto; 78 | } 79 | 80 | .w100 { 81 | width: 100%; 82 | } 83 | 84 | .h100 { 85 | height: 100%; 86 | } 87 | 88 | .border-r5 { 89 | border-radius: 5px; 90 | } 91 | 92 | .cur-p { 93 | cursor: pointer; 94 | } 95 | 96 | .color-success { 97 | color: #4caf50; 98 | } 99 | 100 | .color-danger { 101 | color: #f44336; 102 | } 103 | 104 | .cur-all { 105 | cursor: all-scroll; 106 | } 107 | 108 | .t0 { 109 | top: 0; 110 | } 111 | 112 | .r0 { 113 | right: 0; 114 | } 115 | 116 | .b0 { 117 | bottom: 0; 118 | } 119 | 120 | .l0 { 121 | left: 0; 122 | } 123 | 124 | .fr { 125 | float: right; 126 | } 127 | 128 | .fl { 129 | float: left; 130 | } 131 | 132 | .text-r { 133 | text-align: right; 134 | } 135 | 136 | .text-l { 137 | text-align: left; 138 | } 139 | 140 | .text-c { 141 | text-align: center; 142 | } 143 | 144 | .ellipsis { 145 | overflow: hidden; 146 | white-space: nowrap; 147 | text-overflow: ellipsis; 148 | } 149 | 150 | @keyframes transIcon { 151 | 0% { 152 | transform: rotate(0); 153 | } 154 | 100% { 155 | transform: rotate(360deg); 156 | } 157 | } 158 | 159 | @keyframes transIconReversal { 160 | 0% { 161 | transform: rotate(0); 162 | } 163 | 100% { 164 | transform: rotate(-360deg); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/styles/mixin.scss: -------------------------------------------------------------------------------- 1 | @mixin default-flex { 2 | display: flex; 3 | justify-content: center; 4 | align-items: center; 5 | } 6 | 7 | @mixin flex-full { 8 | width: 100%; 9 | } 10 | 11 | @mixin flexWrap { 12 | flex-wrap: wrap; 13 | } 14 | 15 | @mixin ellipsis { 16 | overflow: hidden; 17 | white-space: nowrap; 18 | text-overflow: ellipsis; 19 | } 20 | 21 | @mixin flexCol { 22 | @include flexWrap; 23 | flex-flow: column; 24 | } 25 | 26 | @mixin ps-r { 27 | position: relative; 28 | } 29 | 30 | @mixin ps-a { 31 | position: absolute; 32 | } 33 | 34 | @mixin fr { 35 | float: right; 36 | } 37 | 38 | @mixin fl { 39 | float: left; 40 | } 41 | 42 | @mixin cur-p { 43 | cursor: pointer; 44 | } 45 | 46 | @mixin cur-all { 47 | cursor: all-scroll; 48 | } 49 | 50 | /* 待整理 */ 51 | 52 | @mixin btn-icon { 53 | font-size: 1.8rem; 54 | } 55 | 56 | @mixin defaultBackgroundColor { 57 | background-color: $defaultBackground; 58 | } 59 | 60 | @mixin default-col-style { 61 | @include default-col-border; 62 | @include default-col-radius; 63 | // background-color: $previewColBg; 64 | } 65 | 66 | @mixin default-col-layout { 67 | margin: 0 0.2rem; 68 | @include default-flex; 69 | } 70 | 71 | @mixin default-col-border { 72 | border: 1px dashed $deep-color-2; 73 | } 74 | 75 | @mixin defaultBorder { 76 | border: 1px solid black; 77 | } 78 | 79 | @mixin minHeight { 80 | min-height: 100px; 81 | } 82 | 83 | @mixin default-col-radius { 84 | border-radius: 5px; 85 | } 86 | 87 | @mixin boxShadow { 88 | box-shadow: 2px 2px 5px rgba(158, 158, 158, 0.4); 89 | } 90 | 91 | @mixin boxShadowReversal { 92 | box-shadow: -2px 2px 5px rgba(158, 158, 158, 0.4); 93 | } 94 | 95 | @mixin title-margin { 96 | margin: 0 0.5rem; 97 | } 98 | 99 | @mixin previewRow { 100 | @include default-flex; 101 | width: 100%; 102 | flex-wrap: wrap; 103 | } 104 | 105 | @mixin default-background-img { 106 | background-size: 100%; 107 | background-position: center center; 108 | } 109 | 110 | @mixin controller-bar { 111 | @include ps-a; 112 | @include default-col-radius; 113 | width: 100%; 114 | height: 20px; 115 | line-height: 20px; 116 | background-color: $defaultControllerBarBackground; 117 | color: $defaultActiveColor; 118 | } 119 | 120 | @mixin previewItem { 121 | @include default-flex; 122 | @include default-col-radius; 123 | height: 50px; 124 | margin: 0 0.2rem; 125 | } 126 | 127 | @mixin side-controller-box { 128 | @include ps-a; 129 | @include default-col-radius; 130 | top: 0; 131 | height: 800px; 132 | width: 450px; 133 | overflow: hidden; 134 | } 135 | 136 | @keyframes transIcon { 137 | 0% { 138 | transform: rotate(0); 139 | } 140 | 100% { 141 | transform: rotate(360deg); 142 | } 143 | } 144 | 145 | @keyframes transIconReversal { 146 | 0% { 147 | transform: rotate(0); 148 | } 149 | 100% { 150 | transform: rotate(-360deg); 151 | } 152 | } 153 | 154 | @mixin componentContainer { 155 | @include flexCol; 156 | height: 100%; 157 | width: 100%; 158 | padding: 0.6rem; 159 | } 160 | 161 | @mixin flexFullRow { 162 | width: 100%; 163 | } 164 | 165 | @mixin title-4_8 { 166 | font-size: 1.3rem; 167 | font-weight: 600; 168 | } 169 | 170 | @mixin count-4_8 { 171 | color: $active-color-1; 172 | font-size: 2.5rem; 173 | } 174 | -------------------------------------------------------------------------------- /src/styles/variable.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: jsjzh 3 | * @Email: kimimi_king@163.com 4 | * @LastEditors: jsjzh 5 | * @Date: 2019-02-27 18:21:10 6 | * @LastEditTime: 2019-03-06 15:06:02 7 | * @Description: 全局样式变量 8 | */ 9 | @import './mixin.scss'; 10 | 11 | $deep-color-1: rgb(52, 73, 93); 12 | $deep-color-2: rgb(44, 61, 79); 13 | $active-color-1: rgb(238, 119, 56); 14 | $active-color-2: rgb(245, 157, 42); 15 | $shallow-color-1: rgb(242, 245, 247); 16 | $shallow-color-2: rgb(253, 241, 235); 17 | 18 | $danger-color: #f44336; 19 | $success-color: #4caf50; 20 | 21 | $defaultBackground: rgba(102, 102, 102, 0.8); 22 | $defaultControllerBarBackground: rgba(158, 158, 158, 0.5); 23 | $defaultControllerBoxBackground: white; 24 | $defaultColor: white; 25 | $defaultActiveColor: black; 26 | $previewColBg: rgb(238, 238, 238); 27 | -------------------------------------------------------------------------------- /src/utils/dom.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: jsjzh 3 | * @Email: kimimi_king@163.com 4 | * @Date: 2018-10-30 15:29:59 5 | * @LastEditors: jsjzh 6 | * @LastEditTime: 2019-01-29 22:30:55 7 | * @Description: dom 操作工具箱 8 | */ 9 | export const on = (function() { 10 | if (document.addEventListener) { 11 | return function(element, event, handler) { 12 | if (element && event && handler) { 13 | element.addEventListener(event, handler, false) 14 | } 15 | } 16 | } else { 17 | return function(element, event, handler) { 18 | if (element && event && handler) { 19 | element.attachEvent('on' + event, handler) 20 | } 21 | } 22 | } 23 | })() 24 | 25 | export const off = (function() { 26 | if (document.removeEventListener) { 27 | return function(element, event, handler) { 28 | if (element && event && handler) { 29 | element.removeEventListener(event, handler, false) 30 | } 31 | } 32 | } else { 33 | return function(element, event, handler) { 34 | if (element && event && handler) { 35 | element.detachEvent('on' + event, handler) 36 | } 37 | } 38 | } 39 | })() 40 | 41 | export function addClass(el, classNames) { 42 | if (el.classList) { 43 | return classNames.forEach(function(cl) { 44 | el.classList.add(cl) 45 | }) 46 | } 47 | el.className += ' ' + classNames.join(' ') 48 | } 49 | 50 | export function removeClass(el, classNames) { 51 | if (el.classList) { 52 | return classNames.forEach(function(cl) { 53 | el.classList.remove(cl) 54 | }) 55 | } 56 | el.className = el.className.replace(new RegExp('(^|\\b)' + classNames.join('|') + '(\\b|$)', 'gi'), ' ') 57 | } 58 | 59 | export function getScrollbarWidth() { 60 | const outer = document.createElement('div') 61 | outer.className = 'el-scrollbar__wrap' 62 | outer.style.visibility = 'hidden' 63 | outer.style.width = '100px' 64 | outer.style.position = 'absolute' 65 | outer.style.top = '-9999px' 66 | document.body.appendChild(outer) 67 | 68 | const widthNoScroll = outer.offsetWidth 69 | outer.style.overflow = 'scroll' 70 | 71 | const inner = document.createElement('div') 72 | inner.style.width = '100%' 73 | outer.appendChild(inner) 74 | 75 | const widthWithScroll = inner.offsetWidth 76 | outer.parentNode.removeChild(outer) 77 | 78 | return widthNoScroll - widthWithScroll 79 | } 80 | -------------------------------------------------------------------------------- /src/utils/drag.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: jsjzh 3 | * @Email: kimimi_king@163.com 4 | * @LastEditors: jsjzh 5 | * @Date: 2019-01-30 09:34:29 6 | * @LastEditTime: 2019-03-06 15:41:47 7 | * @Description: 对鼠标点击、移动、抬起事件进行包装 8 | */ 9 | 10 | let isDrag = false 11 | /** 12 | * @param {Element} elem 13 | * @param {Object} options 14 | */ 15 | export default function(elem, options) { 16 | const moveFn = function(event) { 17 | options.move && options.move(event || window.event, elem) 18 | } 19 | 20 | const endFn = function(event) { 21 | document.removeEventListener('mousemove', moveFn) 22 | document.removeEventListener('mouseup', endFn) 23 | document.onselectstart = null 24 | document.ondragstart = null 25 | isDrag = false 26 | options.end && options.end(event || window.event, elem) 27 | } 28 | 29 | elem.addEventListener('mousedown', function(event) { 30 | if (isDrag) return 31 | document.onselectstart = function() { 32 | return false 33 | } 34 | document.ondragstart = function() { 35 | return false 36 | } 37 | document.addEventListener('mousemove', moveFn) 38 | document.addEventListener('mouseup', endFn) 39 | isDrag = true 40 | options.start && options.start(event || window.event, elem) 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /src/utils/dragReport.js: -------------------------------------------------------------------------------- 1 | import { mixinObjs, isSame } from './index' 2 | import Vue from 'vue' 3 | 4 | export const alignType = [ 5 | { title: '左对齐', label: 'left', value: 'flex-start' }, 6 | { title: '居中对齐', label: 'center', value: 'center' }, 7 | { title: '右对齐', label: 'right', value: 'flex-end' }, 8 | { title: '两侧留白', label: 'around', value: 'space-around' }, 9 | { title: '两侧对齐', label: 'between', value: 'space-between' } 10 | ] 11 | 12 | export function getInitCol(mixinOptions) { 13 | return mixinObjs( 14 | { 15 | title: null, 16 | col: 0, 17 | componentKey: null, 18 | initCol: 0, 19 | showChildrenControllerBar: false 20 | }, 21 | mixinOptions 22 | ) 23 | } 24 | 25 | export function getInitRow(row) { 26 | return { 27 | index: null, 28 | align: 'flex-start', 29 | initHeight: row[0].height, 30 | showControllerBar: false, 31 | children: row.map(col => getInitCol({ initCol: col.value })) 32 | } 33 | } 34 | 35 | export function resetIndex(row, index) { 36 | row.index = index + 1 37 | } 38 | 39 | export function PreviewDataToLayoutData(layoutData, componentDatas, rowMixin = {}, colMixin = {}) { 40 | const { children: rows } = layoutData 41 | rows.forEach(row => { 42 | row = mixinObjs(row, rowMixin) 43 | let { children: cols } = row 44 | cols.forEach((col, colIndex) => { 45 | let currComponent = componentDatas.find(isSame('componentKey', col)) || {} 46 | Vue.set(cols, colIndex, mixinObjs(col, currComponent, colMixin)) 47 | }) 48 | }) 49 | return layoutData 50 | } 51 | 52 | export function layoutDataToPreviewData(layoutData) { 53 | return { 54 | title: layoutData.title, 55 | reportUnionKey: layoutData.reportUnionKey, 56 | children: layoutData.children.map(row => { 57 | return { 58 | title: row.title, 59 | align: row.align, 60 | initHeight: row.initHeight, 61 | index: row.index, 62 | children: row.children.map(col => ({ 63 | col: col.col, 64 | componentKey: col.componentKey, 65 | initCol: col.initCol, 66 | title: col.title 67 | })) 68 | } 69 | }) 70 | } 71 | } 72 | 73 | /** 74 | * 75 | * @param {Object} transObj 76 | * @param {Array} transArray 77 | */ 78 | export function transObjFromArray(transObj, transArray) { 79 | return transArray.reduce((pre, curr) => { 80 | if (curr.prop in transObj) { 81 | return [ 82 | ...pre, 83 | { 84 | name: curr.name, 85 | value: transObj[curr.prop] 86 | } 87 | ] 88 | } else { 89 | return pre 90 | } 91 | }, []) 92 | } 93 | -------------------------------------------------------------------------------- /src/utils/exportPDF.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: jsjzh 3 | * @Email: kimimi_king@163.com 4 | * @LastEditors: jsjzh 5 | * @Date: 2019-03-06 15:10:59 6 | * @LastEditTime: 2019-05-05 14:38:03 7 | * @Description: from https://github.com/linwalker/render-html-to-pdf 8 | */ 9 | import html2canvas from 'html2canvas' 10 | import jsPDF from 'jspdf' 11 | 12 | function exportPDF(dom, title = '未命名 PDF', options = {}) { 13 | html2canvas(document.querySelector(dom), options).then(canvas => { 14 | // A4 尺寸 [595.28, 841.89] 15 | let contentWidth = canvas.width 16 | let contentHeight = canvas.height 17 | // 一页 pdf 显示 html 页面生成的 canvas 高度 18 | let pageHeight = (contentWidth / 592.28) * 841.89 19 | // 未生成 pdf 的 html 页面高度 20 | let leftHeight = contentHeight 21 | // 页面偏移 22 | let position = 0 23 | // html 页面生成的 canvas 在 pdf 中图片的宽高 24 | let imgWidth = 595.28 25 | let imgHeight = (592.28 / contentWidth) * contentHeight 26 | 27 | let pageData = canvas.toDataURL('image/jpeg', 1) 28 | 29 | let pdf = new jsPDF('', 'pt', 'a4') 30 | 31 | // 有两个高度需要区分,一个是 html 页面的实际高度,和生成 pdf 的页面高度 32 | // 当内容未超过 pdf 一页显示的范围,无需分页 33 | if (leftHeight < pageHeight) { 34 | pdf.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight) 35 | } else { 36 | while (leftHeight > 0) { 37 | pdf.addImage(pageData, 'JPEG', 0, position, imgWidth, imgHeight) 38 | leftHeight -= pageHeight 39 | position -= 841.89 40 | //避免添加空白页 41 | if (leftHeight > 0) { 42 | pdf.addPage() 43 | } 44 | } 45 | } 46 | 47 | pdf.save(`${title} ${new Date().getTime()}.pdf`) 48 | }) 49 | } 50 | 51 | export default exportPDF 52 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: jsjzh 3 | * @Email: kimimi_king@163.com 4 | * @Date: 2018-6-28 15:13:23 5 | * @LastEditors: jsjzh 6 | * @LastEditTime: 2019-05-16 16:40:34 7 | * @Description: 常用函数包装 8 | */ 9 | import * as R from 'ramda' 10 | import { Message } from 'element-ui' 11 | 12 | const OBJECT = '[object Object]' 13 | const ARRAY = '[object Array]' 14 | const NUMBER = '[object Number]' 15 | const FUNCTION = '[object Function]' 16 | const STRING = '[object String]' 17 | const NULL = '[object Null]' 18 | const UNDEFINED = '[object Undefined]' 19 | 20 | // 判断两者数据类型是否相等 21 | // 若不相等 返回 true 22 | export function EXtypeof(item, type) { 23 | return Object.prototype.toString.call(item) === Object.prototype.toString.call(type) 24 | } 25 | 26 | /** 27 | * 合并 promise 请求 28 | * @param {Array} promises promise 请求 29 | */ 30 | export function mergePromises(promises) { 31 | if (!EXtypeof(promises, new Array())) return 32 | return new Promise((resolve, reject) => { 33 | Promise.all(promises).then(resArr => { 34 | resolve(resArr) 35 | }) 36 | }) 37 | } 38 | 39 | /** 40 | * 传入数组或者对象 深度遍历 将所有 value 为 null undefined "" 转为 - 41 | * @param {Object | Array} obj 42 | */ 43 | export function filterObject(obj) { 44 | if (typeof obj !== 'array' && typeof obj !== 'object') return 45 | if (Object.prototype.toString.call(obj) === ARRAY) { 46 | obj.forEach(elem => { 47 | filterObject(elem) 48 | }) 49 | } else if (Object.prototype.toString.call(obj) === OBJECT) { 50 | for (const key in obj) { 51 | if (obj.hasOwnProperty(key)) { 52 | let item = obj[key] 53 | if (Object.prototype.toString.call(item) === OBJECT) { 54 | filterObject(item) 55 | } else if (Object.prototype.toString.call(item) === ARRAY) { 56 | filterObject(item) 57 | } else { 58 | obj[key] = item !== null && item !== undefined && item !== '' ? item : '-' 59 | } 60 | } 61 | } 62 | } 63 | } 64 | 65 | /** 66 | * 递归删除树状图的 data 的 type 67 | * @param {Object} todoData 68 | * @param {String} type 69 | */ 70 | export function recursionData(todoData, type) { 71 | return todoData.filter(item => { 72 | item.children.length ? (item.children = recursionData(item.children, type)) : '' 73 | return item.type !== type 74 | }) 75 | } 76 | 77 | export function bindResize() { 78 | if (this.instance) { 79 | this.instance.resize() 80 | } 81 | if (this.chartObj) { 82 | this.chartObj.resize() 83 | } 84 | } 85 | 86 | export function deepClone(obj) { 87 | return JSON.parse(JSON.stringify(obj)) 88 | } 89 | 90 | /** 91 | * 将数组组中相同 key 的数组的 item 合并 92 | * @param {String} key key 93 | * @param {...any} args 94 | */ 95 | export function mergeArrByKey(key, ...args) { 96 | return args.splice(0, 1)[0].map(item => { 97 | args.forEach(arg => { 98 | let obj = arg.find(ite => ite[key] === item[key]) 99 | obj && (item = { ...item, ...obj }) 100 | }) 101 | return item 102 | }) 103 | } 104 | /** 105 | * 按照 byArr 的顺序排序数组 106 | * @param {Array} byArr name 组成的数组 107 | * @param {Array} arr 待排序的数组 108 | */ 109 | export function sortByName(byArr, arr, key = 'name') { 110 | let rtn = [] 111 | byArr.forEach(it => { 112 | for (let idx = 0; idx < arr.length; idx++) { 113 | if (it === arr[idx][key]) { 114 | rtn.push(arr[idx]) 115 | arr.splice(idx, 1) 116 | break 117 | } 118 | } 119 | }) 120 | return rtn.concat(arr) 121 | } 122 | 123 | export const diff = R.curry(function(target, a, b) { 124 | if (!target) return a - b 125 | return a[target] - b[target] 126 | }) 127 | 128 | export const diffIndex = diff('index') 129 | 130 | export function filterZtreeDataByType(data, type) { 131 | if (!Array.isArray(data)) { 132 | data = data.children || [] 133 | } 134 | data.forEach(item => { 135 | if (item.type === type - 1) { 136 | item.children = [] 137 | } else { 138 | filterZtreeDataByType(item.children, type) 139 | } 140 | }) 141 | } 142 | 143 | /** 144 | * 拍平树状图数据 145 | * @param {Array Object} data 树状图 data 146 | * @param {String} key 树状图表示子代的 key 147 | */ 148 | export function flatZtreeData(data, key = 'children') { 149 | if (!Array.isArray(data)) { 150 | data = [data] 151 | } 152 | return data.reduce((pre, curr) => { 153 | if (Array.isArray(curr[key])) { 154 | return [...pre, curr, ...flatZtreeData(curr[key])] 155 | } else { 156 | return [pre] 157 | } 158 | }, []) 159 | } 160 | 161 | export function flatLayout(data, key = 'children') { 162 | if (!Array.isArray(data)) { 163 | data = [data] 164 | } 165 | 166 | return data.reduce((pre, curr) => { 167 | if (!curr[key]) { 168 | return [...pre, curr] 169 | } else { 170 | return [...pre, ...flatLayout(curr[key])] 171 | } 172 | }, []) 173 | } 174 | 175 | export function mapZtreeDataByType(data, type) { 176 | if (!Array.isArray(data)) { 177 | data = [data] 178 | } 179 | return flatZtreeData(data).filter(item => item.type === type) 180 | } 181 | 182 | export function filterByKey(key = 'col', data) { 183 | let arr = [] 184 | data.forEach(item => { 185 | if (arr.findIndex(it => it[key] === item[key]) === -1) { 186 | arr.push(item) 187 | } 188 | }) 189 | return arr 190 | } 191 | 192 | export function transLineChartData({ valueKey, nameKey, toValueKey = 'value', toNameKey = 'name' }, data) { 193 | return data.reduce( 194 | (pre, curr) => { 195 | pre[[toValueKey]].push(curr[[valueKey]]) 196 | pre[[toNameKey]].push(curr[[nameKey]]) 197 | return pre 198 | }, 199 | { [toValueKey]: [], [toNameKey]: [] } 200 | ) 201 | } 202 | 203 | export function transPieChartData({ valueKey, nameKey, toValueKey = 'value', toNameKey = 'name' }, data) { 204 | return data.map(item => ({ [toValueKey]: item[valueKey], [toNameKey]: item[nameKey] })) 205 | } 206 | 207 | export function transBarChartData({ valueKey, nameKey, toValueKey = 'value', toNameKey = 'name' }, data) { 208 | return data.reduce( 209 | (pre, curr) => { 210 | pre[[toValueKey]].push(curr[[valueKey]]) 211 | pre[[toNameKey]].push(curr[[nameKey]]) 212 | return pre 213 | }, 214 | { [toValueKey]: [], [toNameKey]: [] } 215 | ) 216 | } 217 | 218 | export function mixinData(item, mixinData) { 219 | return { ...item, ...mixinData } 220 | } 221 | 222 | export function transUrlParams(url) { 223 | const paramStr = url.split('?')[1] 224 | if (!paramStr) return {} 225 | let paramObj = JSON.parse(`{"${paramStr.replace(/&/g, '","').replace(/=/g, '":"')}"}`) 226 | Object.keys(paramObj).forEach(key => { 227 | paramObj[key] = decodeURI(paramObj[key]) 228 | }) 229 | return paramObj 230 | } 231 | 232 | export function transBodyParams(body) { 233 | let obj = JSON.parse(body) 234 | return obj.params ? obj.params : obj 235 | } 236 | 237 | export function hyphen2hump(str) { 238 | return str.replace(/-(\w)/g, ($0, $1) => $1.toUpperCase()) 239 | } 240 | 241 | export function dir2file(str) { 242 | // TODO 判断文件夹现在用的是 \w\/\w,欠妥 243 | return str.replace(/(\w\/\w)/g, ($0, $1) => $1.replace('/', '-')) 244 | } 245 | 246 | export function $msg(message = '0_操作成功', callback, duration = 1500) { 247 | let arr = ['success', 'error', 'info'] 248 | let dealArr = message.split('_') 249 | let type = arr[~~dealArr[0]] || 'info' 250 | message = dealArr[1] || '未知信息' 251 | Message({ 252 | type, 253 | showClose: true, 254 | message, 255 | duration 256 | }) 257 | if (typeof callback === 'function') callback(true) 258 | return Promise.resolve(true) 259 | } 260 | 261 | export function openNewTab(url) { 262 | window.open(url, '_blank') 263 | } 264 | 265 | export function openNewWindow(url, title, w, h) { 266 | const dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : screen.left 267 | const dualScreenTop = window.screenTop !== undefined ? window.screenTop : screen.top 268 | 269 | const width = window.innerWidth 270 | ? window.innerWidth 271 | : document.documentElement.clientWidth 272 | ? document.documentElement.clientWidth 273 | : screen.width 274 | const height = window.innerHeight 275 | ? window.innerHeight 276 | : document.documentElement.clientHeight 277 | ? document.documentElement.clientHeight 278 | : screen.height 279 | 280 | const left = width / 2 - w / 2 + dualScreenLeft 281 | const top = height / 2 - h / 2 + dualScreenTop 282 | const newWindow = window.open( 283 | url, 284 | title, 285 | `toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=no,resizable=yes,copyhistory=no,width=${w},height=${h},top=${top},left=${left}` 286 | ) 287 | 288 | if (window.focus) { 289 | newWindow.focus() 290 | } 291 | } 292 | 293 | export function setStorage(key, value, type = 'local') { 294 | window[type + 'Storage'].setItem(key, value) 295 | } 296 | 297 | export function resolveStorage(key, type = 'local') { 298 | let value = window[type + 'Storage'].getItem(key) 299 | if (value === 'false') return false 300 | if (value === 'true') return true 301 | if (value.indexOf('{') !== -1 && value.indexOf('}') !== -1) return JSON.parse(value) 302 | return value 303 | } 304 | 305 | export function mixinObjs(...objs) { 306 | return Object.assign.apply(null, objs) 307 | } 308 | 309 | export const isSame = R.curry(function(target, a, b) { 310 | if (!target) return a === b 311 | return a[target] === b[target] 312 | }) 313 | 314 | export function noop() { 315 | return function() {} 316 | } 317 | 318 | export function getRamdomCountByNum(total = 100, num = 4) { 319 | let arr = [] 320 | let _total = total 321 | for (let index = 1; index < num; index++) { 322 | arr[index - 1] = (Math.random() * total).toFixed(0) 323 | total -= arr[index - 1] 324 | } 325 | arr[num - 1] = arr.reduce((pre, curr) => pre - curr, _total) 326 | return arr 327 | } 328 | 329 | export function reDup(array) { 330 | return Array.from(new Set(array)) 331 | } 332 | -------------------------------------------------------------------------------- /src/utils/paste.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: jsjzh 3 | * @Email: kimimi_king@163.com 4 | * @LastEditors: jsjzh 5 | * @Date: 2019-01-30 09:34:29 6 | * @LastEditTime: 2019-03-06 15:35:39 7 | * @Description: 复制函数 8 | */ 9 | export default function(text) { 10 | const body = document.querySelector('body') 11 | const input = document.createElement('input') 12 | input.value = text 13 | input.style.opacity = 0 14 | input.style.position = 'fixed' 15 | input.style.top = '0' 16 | input.style.left = '0' 17 | body.appendChild(input) 18 | input.select() 19 | document.execCommand('copy') 20 | body.removeChild(input) 21 | } 22 | -------------------------------------------------------------------------------- /src/utils/ramdaUtil.js: -------------------------------------------------------------------------------- 1 | import { reDup } from './index' 2 | import * as R from 'ramda' 3 | 4 | export const diff = R.curry(function(target, a, b) { 5 | if (!target) return a - b 6 | return a[target] - b[target] 7 | }) 8 | 9 | export const diffCount = diff(null) 10 | 11 | /** 12 | * 对数组去重并排序,必须是一维的数字数组 13 | */ 14 | export const sortAndDup = R.compose( 15 | reDup, 16 | R.sort(diffCount) 17 | ) 18 | 19 | /** 20 | * 遍历并提取一个 prop 21 | */ 22 | export const mapProp = function(prop) { 23 | return R.map(R.prop(prop)) 24 | } 25 | 26 | /** 27 | * 遍历并提取一个 prop,去重然后排序 28 | */ 29 | export const mapPropAndSortAndDup = R.curry(function(prop, array) { 30 | return R.compose( 31 | sortAndDup, 32 | mapProp(prop) 33 | )(array) 34 | }) 35 | 36 | /** 37 | * 过滤一个数组中元素的某 prop 和 traget 相同的部分 38 | */ 39 | export const filterPropEquals = R.curry(function(prop, target) { 40 | return R.filter( 41 | R.compose( 42 | R.equals(target), 43 | R.prop(prop) 44 | ) 45 | ) 46 | }) 47 | 48 | export const isIndexOf = R.curry(function(target, from) { 49 | return R.indexOf(target, from) !== -1 50 | }) 51 | -------------------------------------------------------------------------------- /src/utils/resize-event.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: jsjzh 3 | * @Email: kimimi_king@163.com 4 | * @Date: 2018-11-22 10:42:19 5 | * @LastEditors: jsjzh 6 | * @LastEditTime: 2019-01-29 22:30:56 7 | * @Description: resize 窗口函数调用 8 | * @use 9 | * addResizeListener(element, fn) 10 | * removeResizeListener(element, fn) 11 | */ 12 | import ResizeObserver from 'resize-observer-polyfill' 13 | 14 | const isServer = typeof window === 'undefined' 15 | 16 | const listenerCtx = '$$resizeListener' 17 | const observerCtx = '$$resizeObserver' 18 | 19 | function resizeHandler(entries) { 20 | for (let entry of entries) { 21 | const listeners = entry.target[listenerCtx] || [] 22 | listeners.length && listeners.forEach(fn => fn()) 23 | } 24 | } 25 | 26 | export const addResizeListener = function(element, fn) { 27 | if (isServer || !element || !fn) return 28 | if (!element[listenerCtx]) { 29 | element[listenerCtx] = [] 30 | element[observerCtx] = new ResizeObserver(resizeHandler) 31 | element[observerCtx].observe(element) 32 | } 33 | element[listenerCtx].push(fn) 34 | } 35 | 36 | export const removeResizeListener = function(element, fn) { 37 | if (isServer || !element || !element[listenerCtx]) return 38 | element[listenerCtx].splice(element[listenerCtx].indexOf(fn), 1) 39 | !element[listenerCtx].length && element[observerCtx].disconnect() 40 | } 41 | -------------------------------------------------------------------------------- /src/utils/resize.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: jsjzh 3 | * @Email: kimimi_king@163.com 4 | * @Date: 2018-11-22 14:20:41 5 | * @LastEditors: jsjzh 6 | * @LastEditTime: 2019-01-29 22:31:02 7 | * @Description: resize 8 | * @from: http://www.backalleycoder.com/2013/03/18/cross-browser-event-based-element-resize-detection/ 9 | */ 10 | let attachEvent = document.attachEvent 11 | let isIE = navigator.userAgent.match(/Trident/) 12 | let requestFrame = (function() { 13 | let raf = 14 | window.requestAnimationFrame || 15 | window.mozRequestAnimationFrame || 16 | window.webkitRequestAnimationFrame || 17 | function(fn) { 18 | return window.setTimeout(fn, 20) 19 | } 20 | return function(fn) { 21 | return raf(fn) 22 | } 23 | })() 24 | 25 | let cancelFrame = (function() { 26 | let cancel = 27 | window.cancelAnimationFrame || 28 | window.mozCancelAnimationFrame || 29 | window.webkitCancelAnimationFrame || 30 | window.clearTimeout 31 | return function(id) { 32 | return cancel(id) 33 | } 34 | })() 35 | 36 | function resizeListener(e) { 37 | let win = e.target || e.srcElement 38 | if (win.__resizeRAF__) cancelFrame(win.__resizeRAF__) 39 | win.__resizeRAF__ = requestFrame(function() { 40 | let trigger = win.__resizeTrigger__ 41 | trigger.__resizeListeners__.forEach(function(fn) { 42 | fn.call(trigger, e) 43 | }) 44 | }) 45 | } 46 | 47 | function objectLoad(e) { 48 | this.contentDocument.defaultView.__resizeTrigger__ = this.__resizeElement__ 49 | this.contentDocument.defaultView.addEventListener('resize', resizeListener) 50 | } 51 | 52 | export function addResizeListener(element, fn) { 53 | if (!element.__resizeListeners__) { 54 | element.__resizeListeners__ = [] 55 | if (attachEvent) { 56 | element.__resizeTrigger__ = element 57 | element.attachEvent('onresize', resizeListener) 58 | } else { 59 | if (getComputedStyle(element).position == 'static') element.style.position = 'relative' 60 | let obj = (element.__resizeTrigger__ = document.createElement('object')) 61 | obj.setAttribute( 62 | 'style', 63 | 'display: block; position: absolute; top: 0; left: 0; height: 100%; width: 100%; overflow: hidden; pointer-events: none; z-index: -1;' 64 | ) 65 | obj.__resizeElement__ = element 66 | obj.onload = objectLoad 67 | obj.type = 'text/html' 68 | if (isIE) element.appendChild(obj) 69 | obj.data = 'about:blank' 70 | if (!isIE) element.appendChild(obj) 71 | } 72 | } 73 | element.__resizeListeners__.push(fn) 74 | } 75 | 76 | export function removeResizeListener(element, fn) { 77 | element.__resizeListeners__.splice(element.__resizeListeners__.indexOf(fn), 1) 78 | if (!element.__resizeListeners__.length) { 79 | if (attachEvent) element.detachEvent('onresize', resizeListener) 80 | else { 81 | element.__resizeTrigger__.contentDocument.defaultView.removeEventListener('resize', resizeListener) 82 | element.__resizeTrigger__ = !element.removeChild(element.__resizeTrigger__) 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/views/customLoading/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 36 | 37 | 39 | -------------------------------------------------------------------------------- /src/views/customReportList/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 62 | 63 | 161 | 162 | 169 | 170 | -------------------------------------------------------------------------------- /src/views/customReportList/layout.scss: -------------------------------------------------------------------------------- 1 | @import '~@/styles/variable.scss'; 2 | 3 | .custom-report-list-main-container { 4 | @include ps-r; 5 | .report-list-container { 6 | width: 1200px; 7 | margin: 0 auto; 8 | .controller-bar { 9 | @include default-flex; 10 | justify-content: flex-start; 11 | padding: 2rem; 12 | > i { 13 | margin-right: 2rem; 14 | } 15 | } 16 | .report-list { 17 | overflow: auto; 18 | display: flex; 19 | flex-wrap: wrap; 20 | justify-content: flex-start; 21 | max-height: 500px; 22 | .report-item { 23 | width: 20%; 24 | height: 150px; 25 | margin: 1rem; 26 | background-color: #666; 27 | border-radius: 5px; 28 | box-shadow: 1px 1px 1px#999; 29 | position: relative; 30 | .report-type { 31 | @include ellipsis; 32 | position: absolute; 33 | color: white; 34 | width: 100%; 35 | top: 5%; 36 | left: 5%; 37 | } 38 | .report-info { 39 | @include ellipsis; 40 | position: absolute; 41 | text-align: center; 42 | color: white; 43 | width: 100%; 44 | top: 50%; 45 | left: 50%; 46 | transform: translate(-50%, -50%); 47 | } 48 | .shade { 49 | position: absolute; 50 | width: 100%; 51 | height: 100%; 52 | background-color: rgba(255, 255, 255, 0.3); 53 | display: flex; 54 | .shade-edit-view { 55 | width: 50%; 56 | @include default-flex; 57 | flex-flow: column; 58 | justify-content: space-around; 59 | } 60 | .shade-del { 61 | width: 50%; 62 | @include default-flex; 63 | } 64 | } 65 | } 66 | } 67 | } 68 | 69 | .default-icon { 70 | font-size: 2.5rem; 71 | cursor: pointer; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/views/customScrollbar/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 20 | 21 | 28 | -------------------------------------------------------------------------------- /src/views/dragDialog/index.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 98 | 99 | 101 | -------------------------------------------------------------------------------- /src/views/dragList/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 22 | 23 | 38 | -------------------------------------------------------------------------------- /src/views/dragList/layout.scss: -------------------------------------------------------------------------------- 1 | @import '~@/styles/variable.scss'; 2 | 3 | .container { 4 | width: 100%; 5 | height: 100%; 6 | background-color: #eee; 7 | display: flex; 8 | justify-content: space-around; 9 | align-items: center; 10 | .box { 11 | float: left; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/views/dragReport/layout.scss: -------------------------------------------------------------------------------- 1 | .report-container { 2 | .report-title { 3 | width: 1000px; 4 | height: 80px; 5 | font-size: 3rem; 6 | .report-title-input { 7 | width: 500px; 8 | } 9 | } 10 | 11 | .report { 12 | width: 1200px; 13 | .layout-row { 14 | margin: 2.5rem 0; 15 | .row-controller-bar { 16 | background-color: #cccccc; 17 | color: black; 18 | justify-content: space-between; 19 | height: 28px; 20 | line-height: 28px; 21 | top: -28px; 22 | .bar-info-box { 23 | max-width: 40%; 24 | .bar-layout-info { 25 | margin: 0 0.5rem; 26 | } 27 | } 28 | .bar-btn-box { 29 | .align-type-item { 30 | margin: 0 0.5rem; 31 | } 32 | } 33 | } 34 | 35 | .layout-col-box { 36 | background-size: 100%; 37 | background-position: center center; 38 | border: 1px dashed #cccccc; 39 | margin: 0 0.1rem; 40 | .col-controller-bar { 41 | height: 20px; 42 | line-height: 20px; 43 | background-color: #cccccc; 44 | color: black; 45 | top: 0; 46 | justify-content: space-between; 47 | .bar-title-box { 48 | margin: 0 0.5rem; 49 | max-width: 35%; 50 | } 51 | } 52 | } 53 | } 54 | } 55 | 56 | .drag-report-add-box { 57 | top: 0; 58 | height: 800px; 59 | width: 450px; 60 | overflow: hidden; 61 | background-color: white; 62 | justify-content: flex-start; 63 | flex-wrap: wrap; 64 | flex-flow: column; 65 | box-shadow: 0px 0px 5px #9e9e9e; 66 | .title-box { 67 | font-size: 1.8rem; 68 | } 69 | } 70 | 71 | .ps-icon-btn { 72 | transition: top 0.5s; 73 | font-size: 2.5rem; 74 | &.add-row-icon { 75 | left: 10%; 76 | &:hover { 77 | animation: transIcon 1s infinite; 78 | } 79 | } 80 | &.add-col-icon { 81 | right: 10%; 82 | &:hover { 83 | animation: transIconReversal 1s infinite; 84 | } 85 | } 86 | &.preview-icon { 87 | right: 5%; 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/views/editComponent/index.vue: -------------------------------------------------------------------------------- 1 | 100 | 101 | 231 | 232 | 235 | 236 | -------------------------------------------------------------------------------- /src/views/editComponent/layout.scss: -------------------------------------------------------------------------------- 1 | @import '~@/styles/variable.scss'; 2 | 3 | .container { 4 | width: 100%; 5 | height: 100%; 6 | .component-container { 7 | float: left; 8 | height: 100%; 9 | width: 20%; 10 | // background-color: #eee; 11 | box-shadow: 1px 1px 1px#999; 12 | .filter-container { 13 | width: 100%; 14 | height: 30%; 15 | // background-color: #f44336; 16 | display: flex; 17 | flex-flow: column; 18 | justify-content: space-around; 19 | align-items: center; 20 | box-shadow: 1px 1px 1px#999; 21 | .filter-item { 22 | width: 100%; 23 | /deep/ label { 24 | margin: 0.5rem; 25 | } 26 | // white-space: nowrap; 27 | // overflow: auto; 28 | } 29 | } 30 | .component-list-container { 31 | width: 100%; 32 | height: 70%; 33 | overflow: auto; 34 | .item-box { 35 | @include cur-p; 36 | margin: 1rem; 37 | box-shadow: 1px 1px 1px#999; 38 | .item-infos { 39 | width: 100%; 40 | } 41 | .component-item { 42 | margin: 0 auto; 43 | @include default-background-img; 44 | } 45 | } 46 | } 47 | } 48 | .edit-component-container { 49 | height: 100%; 50 | width: 80%; 51 | margin-left: 20%; 52 | // background-color: #eee; 53 | display: flex; 54 | flex-flow: column; 55 | .preview-container { 56 | width: 100%; 57 | min-height: 250px; 58 | box-shadow: 1px 1px 1px#999; 59 | // background-color: #2196f3; 60 | @include default-flex; 61 | .preview-item { 62 | max-width: 100%; 63 | margin: 0 auto; 64 | @include default-background-img; 65 | } 66 | } 67 | .edit-container { 68 | width: 100%; 69 | flex: 1; 70 | // background-color: #8bc34a; 71 | display: flex; 72 | flex-flow: column; 73 | .controller-bar { 74 | text-align: right; 75 | box-shadow: 1px 1px 1px#999; 76 | /deep/ .el-button { 77 | margin: 1rem 2rem; 78 | &:last-of-type { 79 | margin-right: 3rem; 80 | } 81 | } 82 | } 83 | .edit-form-container { 84 | width: 100%; 85 | height: 100%; 86 | flex: 1; 87 | overflow: auto; 88 | display: flex; 89 | flex-wrap: wrap; 90 | align-items: center; 91 | .item-col { 92 | width: 50%; 93 | padding-left: 2rem; 94 | .form-box { 95 | width: 80%; 96 | @include default-flex; 97 | justify-content: flex-start; 98 | .tip { 99 | font-size: 1.5rem; 100 | } 101 | .title { 102 | @include ellipsis; 103 | text-align: center; 104 | padding-right: 1rem; 105 | width: 150px; 106 | } 107 | } 108 | } 109 | } 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/views/palette/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | 16 | -------------------------------------------------------------------------------- /src/views/previewComponent/index.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 90 | 91 | 104 | -------------------------------------------------------------------------------- /src/views/previewReport/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: jsjzh 3 | * @Email: kimimi_king@163.com 4 | * @LastEditors: jsjzh 5 | * @Date: 2019-02-18 10:49:33 6 | * @LastEditTime: 2019-05-07 09:32:32 7 | * @Description: 用了 webpack 的动态加载加载组件,组件名要满足 custom-report-xxx.vue 8 | */ 9 | 10 | import { hyphen2hump, dir2file } from '@/utils' 11 | 12 | // TODO 后续还需要改进,应该只读取 custom-report-component 目录下的组件 13 | const customReports = require.context('@/components/custom-report', true, /custom\-report\-.+\.vue$/) 14 | 15 | const reports = {} 16 | 17 | function requireAll(requireContext) { 18 | requireContext.keys().forEach(filePath => { 19 | const realName = dir2file(filePath) 20 | const componentName = hyphen2hump(realName.match(/(custom\-report\-.+)\.vue$/)[1]) 21 | reports[componentName] = requireContext(filePath).default 22 | }) 23 | } 24 | 25 | requireAll(customReports) 26 | 27 | export default reports 28 | -------------------------------------------------------------------------------- /src/views/previewReport/layout.scss: -------------------------------------------------------------------------------- 1 | .preview-report { 2 | overflow: hidden; 3 | .preview-container { 4 | width: 1200px; 5 | .preview-header { 6 | margin: 1rem 0; 7 | border-bottom: 1px solid #e6e6e6; 8 | padding-bottom: 1rem; 9 | .preview-report-title { 10 | font-size: 3rem; 11 | } 12 | .query-infos { 13 | justify-content: space-around; 14 | margin-top: 1rem; 15 | .query-box { 16 | flex: 1; 17 | justify-content: space-around; 18 | } 19 | } 20 | } 21 | .layout-row { 22 | border: 1px solid #e6e6e6; 23 | margin: 1rem 0; 24 | .layout-row-title { 25 | color: #ee7738; 26 | font-size: 1.5rem; 27 | margin: 0.5rem 0; 28 | .title-line { 29 | background-color: #e6e6e6; 30 | width: 20%; 31 | margin: 0 2rem; 32 | height: 1px; 33 | } 34 | } 35 | .layout-row-message { 36 | color: #ee7738; 37 | font-size: 1.2rem; 38 | line-height: 24px; 39 | } 40 | .add-message-btn-box { 41 | right: 0; 42 | bottom: 0; 43 | position: absolute; 44 | transform: translateX(100%); 45 | } 46 | } 47 | } 48 | 49 | .preview-paging-line { 50 | position: absolute; 51 | width: 100%; 52 | height: 2px; 53 | top: 1863px; 54 | background-color: red; 55 | } 56 | 57 | .ps-icon { 58 | font-size: 2.5rem; 59 | transition: top 0.5s; 60 | &.export-icon { 61 | right: 10%; 62 | } 63 | &.query-icon { 64 | left: 10%; 65 | } 66 | &.save-icon { 67 | left: 15%; 68 | } 69 | } 70 | 71 | .color-bar { 72 | right: -6.5%; 73 | transition: all 0.5s; 74 | &:hover { 75 | right: 1%; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/views/waves/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 23 | 24 | 36 | -------------------------------------------------------------------------------- /static/css/reset.css: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v4.0 | 20180602 3 | License: none (public domain) 4 | */ 5 | 6 | html, body, div, span, applet, object, iframe, 7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 8 | a, abbr, acronym, address, big, cite, code, 9 | del, dfn, em, img, ins, kbd, q, s, samp, 10 | small, strike, strong, sub, sup, tt, var, 11 | b, u, i, center, 12 | dl, dt, dd, ol, ul, li, 13 | fieldset, form, label, legend, 14 | table, caption, tbody, tfoot, thead, tr, th, td, 15 | article, aside, canvas, details, embed, 16 | figure, figcaption, footer, header, hgroup, 17 | main, menu, nav, output, ruby, section, summary, 18 | time, mark, audio, video { 19 | margin: 0; 20 | padding: 0; 21 | border: 0; 22 | font-size: 100%; 23 | font: inherit; 24 | box-sizing: border-box; 25 | vertical-align: baseline; 26 | } 27 | /* HTML5 display-role reset for older browsers */ 28 | article, aside, details, figcaption, figure, 29 | footer, header, hgroup, main, menu, nav, section { 30 | display: block; 31 | } 32 | /* HTML5 hidden-attribute fix for newer browsers */ 33 | *[hidden] { 34 | display: none; 35 | } 36 | body { 37 | line-height: 1; 38 | } 39 | ol, ul { 40 | list-style: none; 41 | } 42 | blockquote, q { 43 | quotes: none; 44 | } 45 | blockquote:before, blockquote:after, 46 | q:before, q:after { 47 | content: ''; 48 | content: none; 49 | } 50 | table { 51 | border-collapse: collapse; 52 | border-spacing: 0; 53 | } -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | configureWebpack: config => { 3 | config.devtool = 'eval-source-map' 4 | } 5 | } 6 | --------------------------------------------------------------------------------