feat: 数据集导出

This commit is contained in:
dataeaseShu 2022-11-08 18:12:36 +08:00
parent a2da48f2dd
commit e28b80f302
5 changed files with 1725 additions and 11 deletions

View File

@ -190,7 +190,7 @@
v-dialogDrag
:visible.sync="showExport"
width="600px"
class="de-dialog-form"
class="de-dialog-form form-tree-cont"
:title="$t('dataset.export_dataset')"
append-to-body
>
@ -216,11 +216,9 @@
:label="$t('dataset.export_filter')"
prop="expressionTree"
>
<!--TODO 下面的input需用行权限的树形过滤组件替换-->
<el-input
v-model.trim="exportForm.expressionTree"
placeholder="请输入筛选条件"
/>
<div class="tree-cont">
<rowAuth ref="rowAuth" />
</div>
</el-form-item>
</el-form>
<span class="tip">提示最多支持导出10万条数据</span>
@ -251,6 +249,8 @@ import FieldEdit from './FieldEdit'
import { pluginLoaded } from '@/api/user'
import PluginCom from '@/views/system/plugin/PluginCom'
import UpdateRecords from './UpdateRecords'
import rowAuth from './components/rowAuth.vue'
export default {
name: 'ViewTable',
components: {
@ -259,8 +259,14 @@ export default {
UpdateInfo,
TabDataPreview,
UpdateRecords,
rowAuth,
PluginCom
},
provide() {
return {
filedList: () => this.filedList
}
},
props: {
param: {
type: Object,
@ -273,6 +279,7 @@ export default {
name: ''
},
fields: [],
filedList: [],
data: [],
syncStatus: '',
lastRequestComplete: true,
@ -289,8 +296,7 @@ export default {
isPluginLoaded: false,
showExport: false,
exportForm: {
name: '',
expressionTree: ''
name: ''
},
exportFormRules: {
name: [
@ -345,6 +351,13 @@ export default {
this.initTable(this.param.id)
},
methods: {
fetchFiledList() {
this.filedList = []
post('dataset/field/listForPermissionSeting/' + this.param.id,
{}).then((res) => {
this.filedList = res.data
})
},
initTable(id) {
this.resetPage()
this.tableViewRowForm.row = 1000
@ -455,8 +468,8 @@ export default {
exportDataset() {
this.showExport = true
this.fetchFiledList()
this.exportForm.name = this.table.name
this.exportForm.expressionTree = ''
},
closeExport() {
this.showExport = false
@ -467,7 +480,16 @@ export default {
if (this.table.id) {
this.table.row = 100000
this.table.filename = this.exportForm.name
this.table.expressionTree = this.exportForm.expressionTree
const { logic, items, errorMessage } = this.$refs.rowAuth.submit()
if (errorMessage) {
this.$message({
message: errorMessage,
type: 'error',
showClose: true
})
return
}
this.table.expressionTree = JSON.stringify({ items, logic })
exportDataset(this.table).then((res) => {
const blob = new Blob([res], { type: 'application/vnd.ms-excel' })
const link = document.createElement('a')
@ -488,7 +510,14 @@ export default {
}
</script>
<style scoped>
<style lang="scss">
.form-tree-cont {
.tree-cont {
height: 200px;
width: 100%;
overflow-x: auto;
}
}
.icon-class {
color: #6c6c6c;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,350 @@
<template>
<div class="rowAuth">
<rowAuthTree
:relation-list="relationList"
:logic.sync="logic"
@del="(idx) => del(idx)"
@addCondReal="addCondReal"
@removeRelationList="removeRelationList"
@changeAndOrDfs="(type) => changeAndOrDfs(relationList, type)"
/>
<svg
width="388"
height="100%"
class="real-line"
>
<path
stroke-linejoin="round"
stroke-linecap="round"
:d="svgRealinePath"
fill="none"
stroke="#CCCCCC"
stroke-width="0.5"
/>
</svg>
<svg
width="388"
height="100%"
class="dash-line"
>
<path
stroke-linejoin="round"
stroke-linecap="round"
:d="svgDashinePath"
fill="none"
stroke="#CCCCCC"
stroke-width="0.5"
stroke-dasharray="4,4"
/>
</svg>
</div>
</template>
<script>
import rowAuthTree from './rowAuthTree.vue'
export default {
name: 'RowAuth',
components: {
rowAuthTree
},
data() {
return {
relationList: [],
logic: 'or',
errorMessage: ''
}
},
computed: {
svgRealinePath() {
const lg = this.relationList.length
const a = { x: 0, y: 0, child: this.relationList }
a.y = Math.floor(this.dfsXY(a, 0) / 2)
if (!lg) return ''
const path = this.calculateDepth(a)
return path
},
svgDashinePath() {
const lg = this.relationList.length
const a = { x: 0, y: 0, child: this.relationList }
a.y = Math.floor(this.dfsXY(a, 0) / 2)
if (!lg) return `M48 20 L68 20`
const path = this.calculateDepthDash(a)
return path
}
},
methods: {
init(expressionTree) {
const { logic = 'or', items = [] } = expressionTree
this.logic = logic
this.relationList = this.dfsInit(items)
},
submit() {
this.errorMessage = ''
return {
logic: this.logic,
items: this.dfsSubmit(this.relationList),
errorMessage: this.errorMessage
}
},
errorDetected({ enumValue, deType, filterType, term, value }) {
if (filterType === 'logic') {
if (
!term.includes('null') &&
!term.includes('empty') &&
(value === '')
) {
this.errorMessage = this.$t('chart.filter_value_can_null')
return
}
if ([2, 3].includes(deType)) {
if (parseFloat(value).toString() === 'NaN') {
this.errorMessage = this.$t('chart.filter_value_can_not_str')
return
}
}
}
if (filterType === 'enum') {
if (enumValue.length < 1) {
this.errorMessage = this.$t('chart.enum_value_can_not_null')
return
}
}
},
dfsInit(arr) {
const elementList = []
arr.forEach((ele) => {
const { subTree } = ele
if (subTree) {
const { items, logic } = subTree
const child = this.dfsInit(items)
elementList.push({ logic, child })
} else {
const {
enumValue,
fieldId,
filterType,
term,
value,
field
} = ele
const { name, deType } = (field || {})
elementList.push({
enumValue: enumValue.join(','),
fieldId,
filterType,
term,
value,
name,
deType
})
}
})
return elementList
},
dfsSubmit(arr) {
const items = []
arr.forEach((ele) => {
const { child = [] } = ele
if (child.length) {
const { logic } = ele
const subTree = this.dfsSubmit(child)
items.push({
enumValue: [],
fieldId: '',
filterType: '',
term: '',
type: 'tree',
value: '', subTree: { logic, items: subTree }})
} else {
const { enumValue, fieldId, filterType, deType, term, value } = ele
this.errorDetected({ deType, enumValue, filterType, term, value })
if (fieldId) {
items.push({
enumValue: enumValue ? enumValue.split(',') : [],
fieldId,
filterType,
term,
value,
type: 'item',
subTree: null
})
}
}
})
return items
},
removeRelationList() {
this.relationList = []
},
getY(arr) {
const [a] = arr
if (a.child?.length) {
return this.getY(a.child)
}
return a.y
},
getLastY(arr) {
const a = arr[arr.length]
if (a.child?.length) {
return this.getLastY(a.child)
}
return a.y
},
calculateDepthDash(obj) {
const lg = obj.child?.length
let path = ''
if (!lg && Array.isArray(obj.child)) {
const { x, y } = obj
path += `M${48 + x * 68} ${y * 41.4 + 20} L${88 + x * 68} ${
y * 41.4 + 20
}`
} else if (obj.child?.length) {
const y = Math.max(
this.dfsY(obj, 0),
this.dfs(obj.child, 0) + this.getY(obj.child) - 1
)
const parent =
(this.dfs(obj.child, 0) * 41.4) / 2 +
(this.getY(obj.child) || 0) * 41.4
const { x } = obj
path += `M${24 + x * 68} ${parent} L${24 + x * 68} ${y * 41.4 + 20} L${
64 + x * 68
} ${y * 41.4 + 20}`
obj.child.forEach((item) => {
path += this.calculateDepthDash(item)
})
}
return path
},
calculateDepth(obj) {
const lg = obj.child.length
if (!lg) return ''
let path = ''
const { x: depth, y } = obj
obj.child.forEach((item, index) => {
const { y: sibingLg, z } = item
if (item.child?.length) {
const parent =
(this.dfs(obj.child, 0) * 41.4) / 2 +
(this.getY(obj.child) || 0) * 41.4
const children =
(this.dfs(item.child, 0) * 41.4) / 2 + this.getY(item.child) * 41.4
let path1 = 0
let path2 = 0
if (parent < children) {
path1 = parent
path2 = children
} else {
[path1, path2] = [children, parent]
}
if (y >= sibingLg) {
path1 = parent
path2 = children
}
path += `M${24 + depth * 68} ${path1} L${24 + depth * 68} ${path2} L${
68 + depth * 68
} ${path2}`
path += this.calculateDepth(item)
}
if (!item.child?.length) {
if (sibingLg >= y) {
path += `M${24 + depth * 68} ${y * 40} L${24 + depth * 68} ${
(sibingLg + 1) * 41.4 - 20.69921875
} L${68 + depth * 68} ${(sibingLg + 1) * 41.4 - 20.69921875}`
} else {
path += `M${24 + depth * 68} ${
(sibingLg +
(lg === 1 && index === 0 ? 0 : 1) +
(obj.child[index + 1]?.child?.length ? y - sibingLg - 1 : 0)) *
41.4 +
20 +
(lg === 1 && index === 0 ? 26 : 0)
} L${24 + depth * 68} ${
(sibingLg + 1) * 41.4 -
20.69921875 -
(lg === 1 && index === 0 ? (z || 0) * 1.4 : 0)
} L${68 + depth * 68} ${
(sibingLg + 1) * 41.4 -
20.69921875 -
(lg === 1 && index === 0 ? (z || 0) * 1.4 : 0)
}`
}
}
})
return path
},
changeAndOrDfs(arr, logic) {
arr.forEach((ele) => {
if (ele.child) {
ele.logic = logic === 'and' ? 'or' : 'and'
this.changeAndOrDfs(ele.child, ele.logic)
}
})
},
dfs(arr, count) {
arr.forEach((ele) => {
if (ele.child?.length) {
count = this.dfs(ele.child, count)
} else {
count += 1
}
})
count += 1
return count
},
dfsY(obj, count) {
obj.child.forEach((ele) => {
if (ele.child?.length) {
count = this.dfsY(ele, count)
} else {
count = Math.max(count, ele.y, obj.y)
}
})
return count
},
dfsXY(obj, count) {
obj.child.forEach((ele) => {
ele.x = obj.x + 1
if (ele.child?.length) {
const l = this.dfs(ele.child, 0)
ele.y = Math.floor(l / 2) + count
count = this.dfsXY(ele, count)
} else {
count += 1
ele.y = count - 1
}
})
count += 1
return count
},
addCondReal(type, logic) {
this.relationList.push(
type === 'condition'
? { fieldId: '', value: '', enumValue: '', term: '', filterType: 'logic', name: '', deType: '' }
: { child: [], logic }
)
},
del(index) {
this.relationList.splice(index, 1)
}
}
}
</script>
<style>
.rowAuth {
font-family: Avenir, Helvetica, Arial, sans-serif;
text-align: center;
color: #2c3e50;
position: relative;
}
.real-line,
.dash-line {
position: absolute;
top: 0;
left: 0;
user-select: none;
}
</style>

View File

@ -0,0 +1,251 @@
<template>
<div
class="logic"
:style="marginLeft"
>
<div class="logic-left">
<div class="operate-title">
<span
v-if="x"
style="
{
color: '#bfbfbf';
}
"
class="mrg-title"
>
{{ logic === 'or' ? "OR" : "AND" }}
</span>
<el-dropdown
v-else
trigger="click"
@command="handleCommand"
>
<span
style="
{
color: 'rgba(0,0,0,.65)';
}
"
class="mrg-title"
>
{{ logic === 'or' ? "OR" : "AND" }}<i class="el-icon-arrow-down" />
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="and">AND</el-dropdown-item>
<el-dropdown-item command="or">OR</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
<span
v-if="x"
class="operate-icon"
>
<i
class="el-icon-delete"
@click="$emit('removeRelationList')"
/>
</span>
</div>
<div class="logic-right">
<template v-for="(item, index) in relationList">
<logic-relation
v-if="item.child"
:key="index"
:x="item.x"
:logic="item.logic"
:relation-list="item.child"
@del="(idx) => del(idx, item.child)"
@addCondReal="(type, logic) => add(type, item.child, logic)"
@removeRelationList="removeRelationList(index)"
/>
<filter-filed
v-else
:key="index"
:item="item"
:index="index"
@del="$emit('del', index)"
/>
</template>
<div class="logic-right-add">
<button
class="operand-btn"
@click="addCondReal('condition')"
>
+ {{ $t('auth.add_condition') }}
</button>
<button
v-if="x < 2"
class="operand-btn"
@click="addCondReal('relation')"
>
+ {{ $t('auth.add_relationship') }}
</button>
</div>
</div>
</div>
</template>
<script>
import filterFiled from './filterFiled'
export default {
name: 'LogicRelation',
components: {
filterFiled
},
props: {
relationList: {
type: Array,
default: () => []
},
x: {
type: Number,
default: 0
},
logic: {
type: String,
default: 'or'
}
},
computed: {
marginLeft() {
return {
marginLeft: this.x ? '20px' : 0
}
}
},
methods: {
handleCommand(type) {
this.$emit('update:logic', type)
this.$emit('changeAndOrDfs', type)
},
removeRelationList(index) {
this.relationList.splice(index, 1)
},
addCondReal(type) {
this.$emit('addCondReal', type, this.logic === 'or' ? 'and' : 'or')
},
add(type, child, logic) {
child.push(type === 'condition' ? { fieldId: '', value: '', enumValue: '', term: '', filterType: 'logic', name: '', deType: '' } : { child: [], logic })
},
del(index, child) {
child.splice(index, 1)
}
}
}
</script>
<style lang="scss" scope>
.logic {
display: flex;
align-items: center;
position: relative;
z-index: 1;
width: 100%;
.logic-left {
box-sizing: border-box;
width: 48px;
display: flex;
position: relative;
flex-direction: row;
z-index: 10;
.operate-title {
font-family: PingFang SC, Hiragino Sans GB, Microsoft YaHei, sans-serif;
word-wrap: break-word;
box-sizing: border-box;
color: rgba(0, 0, 0, 0.65);
font-size: 12px;
display: inline-block;
white-space: nowrap;
margin: 0;
padding: 0;
width: 65px;
background-color: #f8f8fa;
line-height: 28px;
position: relative;
z-index: 1;
height: 28px;
.mrg-title {
text-align: left;
box-sizing: border-box;
position: relative;
display: block;
margin-left: 11px;
margin-right: 11px;
line-height: 28px;
height: 28px;
i {
font-size: 12px;
}
}
}
&:hover {
.operate-icon {
display: flex;
align-items: center;
}
.operate-title {
.mrg-title {
margin-right: 0 !important;
}
}
}
.operate-icon {
width: 40px;
height: 28px;
line-height: 28px;
background-color: #f8f8fa;
z-index: 1;
display: none;
i {
font-size: 12px;
font-style: normal;
display: unset;
padding: 5px 3px;
cursor: pointer;
position: relative;
z-index: 10;
}
}
}
.logic-right-add {
display: flex;
height: 41.4px;
align-items: center;
padding-left: 26px;
.operand-btn {
box-sizing: border-box;
font-weight: 400;
text-align: center;
box-shadow: 0 2px 0 rgba(0, 0, 0, 0.015);
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
outline: 0;
display: inline-flex;
align-items: center;
justify-content: center;
line-height: 1;
-webkit-appearance: button;
cursor: pointer;
height: 28px;
padding: 0 10px;
margin-right: 10px;
font-size: 12px;
color: #246dff;
background: #fff;
border: 1px solid #246dff;
border-radius: 2px;
}
}
}
</style>

View File

@ -0,0 +1,24 @@
function formatEnum(ele) {
return {
value: ele,
label: `chart.filter_${ele.replace(' ', '_')}`
}
}
const textEnum = ['eq', 'not_eq', 'like', 'not like', 'null', 'not_null', 'empty', 'not_empty']
const textOptions = textEnum.map(formatEnum)
const dateEnum = ['eq', 'not_eq', 'lt', 'gt', 'le', 'ge']
const dateOptions = dateEnum.map(formatEnum)
const valueEnum = [...dateEnum]
const valueOptions = valueEnum.map(formatEnum)
const fieldEnum = ['text', 'time', 'value', 'value', '', 'location']
export {
textOptions,
dateOptions,
valueOptions,
fieldEnum
}