提交 410ac96f authored 作者: wangjiahao's avatar wangjiahao

Merge remote-tracking branch 'origin/main' into main

[submodule "dataease-plugins"]
path = dataease-plugins
url = git@github.com:dataease/dataease-plugins.git
[submodule "dataease-plugin-xpack"]
path = dataease-plugin-xpack
url = git@github.com:dataease/dataease-plugin-xpack.git
![license](https://img.shields.io/github/license/dataease/dataease?color=%231890FF&style=flat-square)
![release](https://img.shields.io/github/v/release/dataease/dataease?color=%231890FF&sort=semver&style=flat-square)
![stars](https://img.shields.io/github/stars/dataease/dataease?color=%231890FF&style=flat-square)
![downloads](https://img.shields.io/github/downloads/dataease/dataease/total)
<p align="center"><a href="https://dataease.io"><img src="https://dataease.oss-cn-hangzhou.aliyuncs.com/img/dataease-logo.png" alt="DataEase" width="300" /></a></p>
<h3 align="center">人人可用的开源数据可视化分析工具</h3>
<p align="center">
<a href="https://www.gnu.org/licenses/old-licenses/gpl-2.0"><img src="https://img.shields.io/github/license/dataease/dataease?color=%231890FF&style=flat-square" alt="License: GPL v2"></a>
<a href="https://app.codacy.com/gh/metersphere/metersphere?utm_source=github.com&utm_medium=referral&utm_content=metersphere/metersphere&utm_campaign=Badge_Grade_Dashboard"><img src="https://api.codacy.com/project/badge/Grade/176186d132df448b955f8bdd5e6ef9c0" alt="Codacy"></a>
<a href="https://github.com/dataease/dataease/releases/latest"><img src="https://img.shields.io/github/v/release/dataease/dataease" alt="Latest release"></a>
<a href="https://github.com/dataease/dataease"><img src="https://img.shields.io/github/stars/dataease/dataease?color=%231890FF&style=flat-square" alt="Stars"></a>
<a href="https://github.com/dataease/dataease/releases/latest"><img src="https://img.shields.io/github/downloads/dataease/dataease/total" alt="Downloads"></a>
</p>
<hr />
DataEase 是开源的数据可视化分析工具,帮助用户快速分析数据并洞察业务趋势,从而实现业务的改进与优化。DataEase 支持丰富的数据源连接,能够通过拖拉拽方式快速制作图表,并可以方便的与他人分享。
# DataEase - 人人可用的开源数据可视化分析工具
### DataEase 的功能:
DataEase 是开源的数据可视化分析工具,帮助用户快速分析数据并洞察业务趋势,从而实现业务的改进与优化。DataEase 支持丰富的数据源连接,能够通过拖拉拽方式快速制作图表,并可以方便的与他人分享。
- 图表展示:支持 PC 端、移动端及大屏;
- 图表制作:支持丰富的图表类型(基于 Apache ECharts 实现)、支持拖拉拽方式快速制作仪表板;
- 数据引擎:支持直连模式、本地模式(基于 Apache Doris / Kettle 实现);
- 数据连接:支持关系型数据库、Excel 等文件、Hadoop 等大数据平台、NoSQL 等各种数据源。
- 图表展示: 支持 PC 端、移动端及大屏;
- 图表制作: 支持丰富的图表类型(基于 Apache ECharts 实现)、支持拖拉拽方式快速制作仪表板;
- 数据引擎: 支持直连模式、本地模式(基于 Apache Doris / Kettle 实现);
- 数据连接: 支持关系型数据库、Excel 等文件、Hadoop 等大数据平台、NoSQL 等各种数据源。
### DataEase 的优势:
- 开源开放:零门槛,线上快速获取和安装;快速获取用户反馈、按月发布新版本;
- 简单易用:极易上手,通过鼠标点击和拖拽即可完成分析;
- 秒级响应:集成 Apache Doris,超大数据量下秒级查询返回延时;
- 安全分享:支持多种数据分享方式,确保数据安全。
## UI 展示
![de-ui](https://dataease.oss-cn-hangzhou.aliyuncs.com/img/de-ui.png)
## 功能架构
![de-architecture](https://dataease.oss-cn-hangzhou.aliyuncs.com/img/de-architecture.png)
## 在线体验
- 环境地址:<https://demo.dataease.io/>
......@@ -40,343 +56,13 @@ curl -sSL https://github.com/dataease/dataease/releases/latest/download/quick_st
![wechat-group](https://dataease.oss-cn-hangzhou.aliyuncs.com/img/wechat-group.png)
## 功能架构
![de-architecture](https://dataease.oss-cn-hangzhou.aliyuncs.com/img/de-architecture.png)
## 产品优势
- 开源开放:零门槛,线上快速获取和安装;快速获取用户反馈、按月发布新版本;
- 简单易用:极易上手,通过鼠标点击和拖拽即可完成分析;
- 秒级响应:集成 Apache Doris,超大数据量下秒级查询返回延时;
- 安全分享:支持多种数据分享方式,确保数据安全。
## 功能列表
<table>
<tr>
<td bgcolor="#3779d9" align="middle" style="font-weight:bold;color: white;width: 150px">
功能模块
</td>
<td bgcolor="#3779d9" align="middle" style="font-weight:bold;color: white;width: 170px">
功能
</td>
<td bgcolor="#3779d9" align="middle" style="font-weight:bold;color: white;width: 750px">
功能描述
</td>
</tr>
<tr>
<td rowspan="19">
仪表板
</td>
<td rowspan="7">
仪表板管理
</td>
<td>
支持新建仪表板
</td>
</tr>
<tr>
<td>
支持重命名仪表板
</td>
</tr>
<tr>
<td>
支持删除仪表板
</td>
</tr>
<tr>
<td>
支持在线编辑仪表板
</td>
</tr>
<tr>
<td>
支持以树状形式展示仪表板分组
</td>
</tr>
<tr>
<td>
支持重命名仪表板分组
</td>
</tr>
<tr>
<td>
支持删除仪表板分组
</td>
</tr>
<tr>
<td rowspan="10">
仪表板制作
</td>
<td>
支持多种组件,视图/时间组件/文本组件/数字组件/样式组件/图片等
</td>
</tr>
<tr>
<td>
支持组件样式设置,如图形属性、组件样式等
</td>
</tr>
<tr>
<td>
支持仪表板背景、以及组件间隙设置等
</td>
</tr>
<tr>
<td>
支持一键切换仪表板主题
</td>
</tr>
<tr>
<td>
支持调整仪表板画布大小
</td>
</tr>
<tr>
<td>
支持撤销、重做、清空画布内容
</td>
</tr>
<tr>
<td>
支持仪表板的全屏预览
</td>
</tr>
<tr>
<td>
支持仪表板模板的导出
</td>
</tr>
<tr>
<td>
支持默认仪表板的设置
</td>
</tr>
<tr>
<td>
支持仪表板的收藏
</td>
</tr>
<tr>
<td rowspan="2">
仪表板共享
</td>
<td>
支持按组织/角色/用户分享
</td>
</tr>
<tr>
<td>
支持生成分享链接,外部用户可通过密码访问
</td>
</tr>
<tr>
<td rowspan="14">
视图
</td>
<td rowspan="6">
视图管理
</td>
<td>
支持新增视图
</td>
</tr>
<tr>
<td>
支持编辑视图
</td>
</tr>
<tr>
<td>
支持删除视图
</td>
</tr>
<tr>
<td>
支持对视图进行分组
</td>
</tr>
<tr>
<td>
支持重命名视图分组
</td>
</tr>
<tr>
<td>
支持删除视图分组
</td>
</tr>
<tr>
<td rowspan="8">
视图制作
</td>
<td>
支持通过简单的拖拉操作,制作视图
</td>
</tr>
<tr>
<td>
支持多种图表类型,明细表/指标卡/基础柱状图/堆叠柱状图/横向柱状图/横向堆叠柱状图/基础折线图/堆叠折线图/饼图/南丁格尔玫瑰图/漏斗图/雷达图/仪表盘等
</td>
</tr>
<tr>
<td>
支持选择图表的样式优先级
</td>
</tr>
<tr>
<td>
支持选择图表的排序方式,根据维度、指标升序、降序展示
</td>
</tr>
<tr>
<td>
支持指标的多种汇总计算方式,如求和、平均、最大值、最小值等
</td>
</tr>
<tr>
<td>
支持对图表类型的图形属性进行设置
</td>
</tr>
<tr>
<td>
支持对图表类型的组件样式进行设置
</td>
</tr>
<tr>
<td>
支持通过过滤条件筛选视图数据
</td>
</tr>
<tr>
<td rowspan="8">
数据集
</td>
<td rowspan="8">
数据集管理
</td>
<td>
支持多种类型的数据集,数据库数据集/SQL 数据集/Excel 数据集/自定义数据集
</td>
</tr>
<tr>
<td>
数据库数据集和 SQL 数据集支持直连和定时同步两种连接方式
</td>
</tr>
<tr>
<td>
定时同步类型数据集,支持全量更新和增量更新两种方式
</td>
</tr>
<tr>
<td>
支持创建定时任务,以此控制数据集的更新
</td>
</tr>
<tr>
<td>
支持定时更新任务的查看
</td>
</tr>
<tr>
<td>
支持对数据集的字段类型/字段名/展示字段进行设置
</td>
</tr>
<tr>
<td>
支持创建数据集间的关联关系(1:1,一对一;1:N,一对N;N:1,N对1)
</td>
</tr>
<tr>
<td>
支持 Excel 数据集数据的替换和追加
</td>
</tr>
<tr>
<td rowspan="2">
数据源
</td>
<td rowspan="2">
数据源管理
</td>
<td>
创建 MySQL数据源
</td>
</tr>
</tr>
<tr>
<td>
校验数据源的有效性
</td>
</tr>
<tr>
<td rowspan="5">
系统管理
</td>
<td rowspan="2">
用户租户管理
</td>
<td>
支持多级租户体系
</td>
</tr>
<tr>
<td>
支持多种租户角色
</td>
</tr>
<tr>
<td rowspan="2">
权限管理
</td>
<td>
支持组织/角色/用户三个维度的权限管理
</td>
</tr>
<tr>
<td>
支持数据源/数据集/视图/仪表板/菜单和操作权限的细颗粒度权限控制
</td>
</tr>
<tr>
<td>
显示设置
</td>
<td>
支持对Logo/系统名/标题等属性的设置
</td>
</tr>
</table>
## 技术栈
- 后端: [Spring Boot](https://spring.io/projects/spring-boot)
- 前端: [Vue.js](https://vuejs.org/)
- 中间件: [MySQL](https://www.mysql.com/)
- 数据处理: [Kettle](https://github.com/pentaho/pentaho-kettle)[Apache Doris](https://github.com/apache/incubator-doris/)
- 基础设施: [Docker](https://www.docker.com/)
## 致谢
- [Kettle](https://github.com/pentaho/pentaho-kettle/):DataEase 使用了 Kettle 进行数据处理工作;
- [Apache Doris](https://doris.apache.org/):DataEase 使用了 Apache Doris 进行快速的数据分析;
- [Element](https://element.eleme.cn/):感谢 Element 提供的优秀组件库。
## 版本说明
DataEase 版本号命名规则为:v大版本.功能版本.Bug修复版本。比如:
```text
v1.0.1 是 v1.0.0 之后的 Bug 修复版本;
v1.1.0 是 v1.0.0 之后的功能版本。
```
像其它优秀开源项目一样,DataEase 将每月发布一个功能版本。
- 后端:[Spring Boot](https://spring.io/projects/spring-boot)
- 前端:[Vue.js](https://vuejs.org/)[Element](https://element.eleme.cn/)
- 中间件:[MySQL](https://www.mysql.com/)
- 数据处理:[Kettle](https://github.com/pentaho/pentaho-kettle)[Apache Doris](https://github.com/apache/incubator-doris/)
- 基础设施:[Docker](https://www.docker.com/)
## License & Copyright
......
Subproject commit 9879b705aa4a5105056109ecf5b9da70b783969d
Subproject commit 2541de3d2c82f37dcf9916fda70ada0ca3fc6132
......@@ -1083,7 +1083,6 @@ export default {
this.element.propValue && this.element.propValue.viewId && eventBus.$emit('resizing', this.element.propValue.viewId)
},
changeWidth(val) {
debugger
// console.log('parentWidth', this.parentWidth)
// console.log('parentHeight', this.parentHeight)
const [newWidth, _] = snapToGrid(this.grid, val, 0, this.scale)
......
......@@ -126,7 +126,6 @@ export default {
changeStyleWithScale,
getStyle,
restore() {
debugger
const canvasHeight = document.getElementById('canvasInfoTemp').offsetHeight
const canvasWidth = document.getElementById('canvasInfoTemp').offsetWidth
this.scaleWidth = canvasWidth * 100 / parseInt(this.canvasStyleData.width)// 获取宽度比
......
......@@ -57,7 +57,6 @@ export default {
watch: {
active: {
handler(newVal, oldVla) {
debugger
this.removeSelectText()
},
deep: true
......
......@@ -807,7 +807,7 @@ export default {
}
}
} else {
resolve(node.data.children)
node.data.children && resolve(node.data.children)
}
},
......
......@@ -828,7 +828,7 @@ export default {
}
}
} else {
resolve(node.data.children)
node.data.children && resolve(node.data.children)
}
},
......
......@@ -447,7 +447,6 @@ export default {
this.$store.commit('recordSnapshot')
this.clearCurrentInfo()
debugger
// 文字组件
if (component.type === 'v-text' || component.type === 'rect-shape') {
this.$store.commit('setCurComponent', { component: component, index: this.componentData.length })
......
......@@ -13,34 +13,38 @@
</el-breadcrumb>
</div>
<div class="component-result-content filter-common">
<el-col>
<el-row>
<el-form>
<el-form-item class="my-form-item">
<el-input
v-model="keyWord"
size="mini"
:placeholder="$t('dataset.search')"
prefix-icon="el-icon-search"
clearable
/>
</el-form-item>
</el-form>
</el-row>
<el-row>
<el-tree
v-if="showDomType === 'tree'"
:data="data"
:default-expanded-keys="expandedArray"
node-key="id"
:data="datas"
:props="defaultProps"
:render-content="renderNode"
lazy
:load="loadTree"
@node-click="handleNodeClick"
/>
<el-table
v-else-if="showDomType === 'db'"
class="de-filter-data-table"
:data="sceneDatas"
:show-header="false"
size="mini"
:highlight-current-row="true"
style="width: 100%"
>
<el-table-column prop="name" :label="$t('commons.name')">
<template v-if="showDomType === 'db'" :id="scope.row.id" slot-scope="scope">
<div class="filter-db-row" @click="showFieldDatas(scope.row)">
<i class="el-icon-s-data" />
<span> {{ scope.row.name }}</span>
<div slot-scope="{ node, data }" class="custom-tree-node">
<el-button v-if="data.type === 'db'" icon="el-icon-s-data" type="text" size="mini" />
<span class="label-span">{{ node.label }}</span>
</div>
</template>
</el-table-column>
</el-table>
</el-tree>
<div v-else-if="showDomType === 'field'">
<div v-if="showDomType === 'field'">
<draggable
v-model="fieldDatas"
:disabled="selectField.length !== 0"
......@@ -52,13 +56,15 @@
@start="start1"
>
<transition-group>
<div v-for="item in fieldDatas" :key="item.id" class="filter-db-row">
<div v-for="item in fieldDatas.filter(item => !keyWord || (item.name && item.name.toLocaleLowerCase().includes(keyWord)))" :key="item.id" class="filter-db-row">
<i class="el-icon-s-data" />
<span> {{ item.name }}</span>
</div>
</transition-group>
</draggable>
</div>
</el-row>
</el-col>
</div>
</el-tab-pane>
<el-tab-pane :lazy="true" class="de-tab" :label="$t('panel.select_by_module')" name="assembly">
......@@ -72,10 +78,26 @@
</div>
<div class="component-result-content filter-common">
<el-col>
<el-row>
<el-form>
<el-form-item class="my-form-item">
<el-input
v-model="viewKeyWord"
size="mini"
:placeholder="$t('dataset.search')"
prefix-icon="el-icon-search"
clearable
/>
</el-form-item>
</el-form>
</el-row>
<el-row>
<el-table
v-if="comShowDomType === 'view'"
class="de-filter-data-table"
:data="viewInfos"
:data="viewInfos.filter(item => !viewKeyWord || item.name.toLocaleLowerCase().includes(viewKeyWord))"
:show-header="false"
size="mini"
:highlight-current-row="true"
......@@ -103,13 +125,15 @@
@start="start1"
>
<transition-group>
<div v-for="item in comFieldDatas" :key="item.id" class="filter-db-row">
<div v-for="item in comFieldDatas.filter(item => !viewKeyWord || item.name.toLocaleLowerCase().includes(viewKeyWord))" :key="item.id" class="filter-db-row">
<i class="el-icon-s-data" />
<span> {{ item.name }}</span>
</div>
</transition-group>
</draggable>
</div>
</el-row>
</el-col>
</div>
</el-tab-pane>
</el-tabs>
......@@ -212,8 +236,9 @@ import draggable from 'vuedraggable'
import DragItem from '@/components/DragItem'
import { mapState } from 'vuex'
// import { ApplicationContext } from '@/utils/ApplicationContext'
import { groupTree, loadTable, fieldList, fieldValues } from '@/api/dataset/dataset'
import { groupTree, fieldList, fieldValues, post } from '@/api/dataset/dataset'
import { viewsWithIds } from '@/api/panel/view'
import { authModel } from '@/api/system/sysAuth'
export default {
name: 'FilterDialog',
components: {
......@@ -246,20 +271,37 @@ export default {
componentSetBreads: [
{ label: this.$t('panel.component_list'), link: false, type: 'root' }
],
data: [],
datas: [],
sceneDatas: [],
// viewDatas: [],
fieldDatas: [],
comFieldDatas: [],
defaultProps: {
label: 'name',
children: 'children',
label: 'label'
isLeaf: 'isLeaf',
id: 'id',
parentId: 'pid'
},
selectField: [],
widget: null,
fieldValues: [],
popovervisible: false,
viewInfos: []
viewInfos: [],
groupForm: {
name: '',
pid: '0',
level: 0,
type: '',
children: [],
sort: 'type desc,name asc'
},
isTreeSearch: false,
defaultDatas: [],
keyWord: '',
timer: null,
expandedArray: [],
viewKeyWord: ''
}
},
computed: {
......@@ -296,12 +338,24 @@ export default {
this.componentInfo.options.attrs.fieldId = null
this.$emit('re-fresh-component', this.componentInfo)
}
},
keyWord(val) {
this.expandedArray = []
if (this.showDomType === 'field') {
return
}
if (this.timer) {
clearTimeout(this.timer)
}
this.timer = setTimeout(() => {
this.getTreeData(val)
}, (val && val !== '') ? 1000 : 0)
}
},
created() {
// this.widget = ApplicationContext.getService(this.widgetId)
this.widget = this.widgetInfo
this.loadDataSetTree()
this.treeNode(this.groupForm)
if (this.componentInfo && this.componentInfo.options.attrs.dragItems) {
this.selectField = this.componentInfo.options.attrs.dragItems
......@@ -310,8 +364,51 @@ export default {
},
methods: {
attr() {
return 'aaa'
getTreeData(val) {
if (val) {
this.isTreeSearch = true
this.searchTree(val)
} else {
this.isTreeSearch = false
this.treeNode(this.groupForm)
}
},
searchTree(val) {
this.expandedArray = []
const queryCondition = {
withExtend: 'parent',
modelType: 'dataset',
name: val
}
authModel(queryCondition).then(res => {
this.datas = this.buildTree(res.data)
})
},
buildTree(arrs) {
const idMapping = arrs.reduce((acc, el, i) => {
acc[el[this.defaultProps.id]] = i
return acc
}, {})
const roots = []
arrs.forEach(el => {
// 判断根节点 ###
el.type = el.modelInnerType
el.isLeaf = el.leaf
if (el[this.defaultProps.parentId] === null || el[this.defaultProps.parentId] === 0 || el[this.defaultProps.parentId] === '0') {
roots.push(el)
return
}
// 用映射表找到父元素
const parentEl = arrs[idMapping[el[this.defaultProps.parentId]]]
// 把当前元素添加到父元素的`children`数组中
parentEl.children = [...(parentEl.children || []), el]
// 设置展开节点 如果没有子节点则不进行展开
if (parentEl.children.length > 0) {
this.expandedArray.push(parentEl[this.defaultProps.id])
}
})
return roots
},
loadViews() {
const viewIds = this.componentData
......@@ -323,10 +420,32 @@ export default {
})
},
handleNodeClick(data) {
if (data.type === 'scene') {
this.showSceneTable(data)
if (data.type !== 'group') {
this.showFieldDatas(data)
}
},
loadTree(node, resolve) {
if (!this.isTreeSearch) {
if (node.level > 0) {
if (node.data.id) {
post('/dataset/table/listAndGroup', {
sort: 'type asc,name asc,create_time desc',
sceneId: node.data.id
}).then(res => {
resolve(res.data)
})
}
}
} else {
node.data.children && resolve(node.data.children)
}
},
treeNode(group) {
post('/dataset/group/treeNode', group).then(res => {
this.defaultDatas = res.data
this.datas = res.data
})
},
loadDataSetTree() {
groupTree({}).then(res => {
const datas = res.data
......@@ -334,25 +453,7 @@ export default {
this.data = datas
})
},
renderNode(h, { node, data, store }) {
return (
<div class='custom-tree-node' >
{ data.type === 'scene' ? (
<el-button icon='el-icon-folder' type='text' size='mini' />
) : (
''
)}
<span class='label-span' >{node.label}</span>
</div>
)
},
showSceneTable(node) {
this.showDomType = 'db'
this.setTailLink(node)
this.addTail(node)
this.loadTable(node.id)
},
setTailLink(node) {
const tail = this.dataSetBreads[this.dataSetBreads.length - 1]
tail.type = node.type
......@@ -389,26 +490,34 @@ export default {
this.componentSetBreads[this.componentSetBreads.length - 1]['link'] = false
},
backToLink(bread) {
if (bread.type === 'db') {
this.showDomType = 'db'
} else {
// if (bread.type === 'field') {
// this.showDomType = 'db'
// } else {
// this.showDomType = 'tree'
// }
this.showDomType = 'tree'
}
this.removeTail(bread)
this.$nextTick(() => {
this.expandedArray = []
this.keyWord = ''
this.isTreeSearch = false
this.datas = JSON.parse(JSON.stringify(this.defaultDatas))
})
},
comBackLink(bread) {
this.comShowDomType = 'view'
this.viewKeyWord = ''
this.comRemoveTail()
},
loadTable(sceneId) {
loadTable({ sceneId: sceneId, sort: 'type asc,create_time desc,name asc' }).then(res => {
res && res.data && (this.sceneDatas = res.data.map(tb => {
tb.type = 'db'
return tb
}))
})
},
// loadTable(sceneId) {
// loadTable({ sceneId: sceneId, sort: 'type asc,create_time desc,name asc' }).then(res => {
// res && res.data && (this.sceneDatas = res.data.map(tb => {
// tb.type = 'db'
// return tb
// }))
// })
// },
loadField(tableId) {
fieldList(tableId).then(res => {
......@@ -429,12 +538,14 @@ export default {
})
},
showFieldDatas(row) {
this.keyWord = ''
this.showDomType = 'field'
this.setTailLink(row)
this.addTail(row)
this.loadField(row.id)
},
comShowFieldDatas(row) {
this.viewKeyWord = ''
this.comShowDomType = 'field'
this.comSetTailLink(row)
this.comAddTail(row)
......@@ -505,6 +616,9 @@ export default {
</script>
<style lang="scss" scoped>
.my-form-item {
cursor: text;
}
.de-dialog-container {
height: 50vh !important;
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论