├── .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 | 
16 |
17 | 
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 |
2 |
3 |
4 |
5 |
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 |
2 |
3 |
9 |
10 |
11 |
15 |
29 |
30 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
49 |
50 |
60 |
61 |
62 |
71 |
72 |
73 |
74 |
75 |
180 |
181 |
182 |
362 |
--------------------------------------------------------------------------------
/src/components/custom-loading/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
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 |
10 |
11 |
12 |
13 |
26 |
--------------------------------------------------------------------------------
/src/components/custom-report/base/base-chart-line.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
26 |
--------------------------------------------------------------------------------
/src/components/custom-report/base/base-chart-pie.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/components/custom-report/base/base-circle.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
71 |
--------------------------------------------------------------------------------
/src/components/custom-report/base/base-table.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
18 |
26 |
27 |
28 |
29 |
50 |
--------------------------------------------------------------------------------
/src/components/custom-report/base/circle-icon.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{circleTitle}}
4 |
10 |
11 |
12 | {{iconText}}
13 |
14 |
15 |
{{circleTip}}
16 |
17 |
18 |
19 |
51 |
--------------------------------------------------------------------------------
/src/components/custom-report/base/circle-three-text.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{percentage}}
13 | {{circleTitle}}
20 | {{realRate}}
28 |
29 |
30 |
31 |
32 |
33 |
67 |
--------------------------------------------------------------------------------
/src/components/custom-report/base/three-line-text.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{blockTitle}}
4 |
5 | {{count}}
6 | {{unit}}
7 |
8 |
9 |
10 |
11 |
12 |
50 |
51 |
--------------------------------------------------------------------------------
/src/components/custom-report/custom-report-component/chart/alarm-event-distribution.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
49 |
--------------------------------------------------------------------------------
/src/components/custom-report/custom-report-component/chart/alarm-interval-distribution.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
51 |
--------------------------------------------------------------------------------
/src/components/custom-report/custom-report-component/chart/bar-pie.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/components/custom-report/custom-report-component/chart/bar-stack.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
56 |
--------------------------------------------------------------------------------
/src/components/custom-report/custom-report-component/chart/driving-score-bar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
89 |
--------------------------------------------------------------------------------
/src/components/custom-report/custom-report-component/chart/driving-score-line.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
49 |
--------------------------------------------------------------------------------
/src/components/custom-report/custom-report-component/chart/event-processing-report.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/components/custom-report/custom-report-component/chart/line-area.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/components/custom-report/custom-report-component/chart/line.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/components/custom-report/custom-report-component/chart/pie.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
45 |
--------------------------------------------------------------------------------
/src/components/custom-report/custom-report-component/group/table-pie.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
52 |
--------------------------------------------------------------------------------
/src/components/custom-report/custom-report-component/group/vehicle-score.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/components/custom-report/custom-report-component/other/block.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
(゚д゚≡゚д゚)
4 |
空欸~
5 |
6 |
7 |
8 |
21 |
--------------------------------------------------------------------------------
/src/components/custom-report/custom-report-component/other/circle-icon.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
14 |
15 |
16 |
38 |
--------------------------------------------------------------------------------
/src/components/custom-report/custom-report-component/other/circle-security-rate.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/components/custom-report/custom-report-component/table/block.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
27 |
--------------------------------------------------------------------------------
/src/components/custom-report/custom-report-component/text/text.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 | {{reportData.countOne}} 次
14 |
15 |
16 | {{reportData.countTwo}} 次
17 |
18 |
19 |
20 |
21 | 环比
22 | {{reportData.rate}} %
23 |
24 |
25 | 平均
26 | {{reportData.average}} %
27 |
28 |
29 |
30 |
31 |
32 |
45 |
46 |
59 |
60 |
--------------------------------------------------------------------------------
/src/components/custom-report/custom-report-component/text/three-line-text.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
34 |
--------------------------------------------------------------------------------
/src/components/custom-report/default-container.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
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 |
10 |
11 |
32 |
33 |
57 |
58 |
59 |
60 | search
61 |
62 |
63 |
64 |
65 |
66 |
182 |
183 |
241 |
--------------------------------------------------------------------------------
/src/components/custom-scrollbar/bar.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/src/components/custom-scrollbar/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
19 |
20 |
21 |
49 |
50 |
--------------------------------------------------------------------------------
/src/components/default-framework/index.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
22 |
27 |
28 |
29 | +
30 |
31 |
38 |
43 |
44 |
45 |
46 |
47 |
48 |
53 |
54 |
59 |
60 | 搜索组件
66 |
67 |
68 |
69 |
70 |
71 |
76 |
77 |
{{component.label}}
78 |
height: {{component.height}} col: {{component.col}}
79 |
80 |
89 |
90 |
91 |
92 |
93 |
94 |
166 |
167 |
213 |
214 |
--------------------------------------------------------------------------------
/src/components/default-layout-editor/index.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
limit-total: 24
15 |
real-total: {{PAGE_realTotal}}
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
组件高度设置需参考组件基本信息,不正确的高度可能导致显示错误
30 |
42 |
43 |
56 |
57 |
suggest-layouts
58 |
73 |
74 |
75 |
76 |
77 |
156 |
157 |
237 |
--------------------------------------------------------------------------------
/src/components/drag-group/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
15 |
16 |
18 |
--------------------------------------------------------------------------------
/src/components/drag-item/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
35 |
36 |
38 |
--------------------------------------------------------------------------------
/src/components/te-icon/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
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 |
2 |
3 |
4 | {{path.title}}
10 |
11 |
12 |
13 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
36 |
37 |
39 |
--------------------------------------------------------------------------------
/src/views/customReportList/index.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
24 |
25 |
32 |
type: {{report.type}}
33 |
{{report.title}}
34 |
35 |
36 |
37 |
42 |
47 |
48 |
49 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
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 |
2 |
11 |
12 |
13 |
20 |
21 |
28 |
--------------------------------------------------------------------------------
/src/views/dragDialog/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | open a Drag Dialog
4 | open a Drag Dialog
5 |
11 |
12 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
32 |
33 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
98 |
99 |
101 |
--------------------------------------------------------------------------------
/src/views/dragList/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | drag-item-info drag-item-info drag-item-info drag-item-info
7 |
8 |
9 |
10 |
11 |
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 |
2 |
3 |
4 |
5 |
10 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
col:{{component.col}}
33 |
height:{{component.height}}
34 |
35 |
39 |
40 |
41 |
42 |
43 |
49 |
50 |
51 | add
56 | clear
62 | del
63 | save
64 |
65 |
96 |
97 |
98 |
99 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
15 |
16 |
--------------------------------------------------------------------------------
/src/views/previewComponent/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
12 |
13 |
14 |
15 |
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 |
2 |
3 | wave-demo-btn
4 | wave-demo-btn
5 | wave-demo-btn
6 | wave-demo-btn
7 |
8 |
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 |
--------------------------------------------------------------------------------