Merge pull request #3730 from dataease/pr@dev@feat_filter_custom_sort

feat(过滤器): 下拉过滤器自定义排序(等待设计请勿merge)
This commit is contained in:
xuwei-fit2cloud 2022-11-15 17:48:15 +08:00 committed by GitHub
commit fc2469c45d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 594 additions and 282 deletions

View File

@ -1,115 +1,13 @@
<template>
<span>
<el-dropdown
trigger="click"
size="mini"
@command="clickItem"
<span class="">
<el-tag
size="small"
closable
class="item-axis"
@close="removeItem"
>
<span class="el-dropdown-link">
<el-tag
size="small"
class="item-axis"
>
{{ item.name }}<i class="el-icon-arrow-down el-icon--right" />
</el-tag>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item
v-if="isSortWidget"
:disabled="disabledSort"
:command="beforeClickItem('none')"
>
<span
class="de-sort-menu"
:class="!disabledSort && (!sortNode || sortNode.sort === 'none') ? 'de-active-li': ''"
>{{
$t('chart.none')
}}</span>
</el-dropdown-item>
<el-dropdown-item
v-if="isSortWidget"
:disabled="disabledSort"
:command="beforeClickItem('asc')"
>
<span
v-popover:popoverasc
class="el-dropdown-link inner-dropdown-menu de-sort-menu"
:class="!disabledSort && sortNode.sort === 'asc' ? 'de-active-li': ''"
>
<span>
<span>{{ $t('chart.asc') }}</span>
</span>
<i class="el-icon-arrow-right el-icon--right" />
</span>
<el-popover
ref="popoverasc"
v-model="ascFieldsShow"
placement="right-start"
width="120"
:close-delay="500"
trigger="hover"
>
<ul class="de-ul">
<li
v-for="(node, i) in allFields"
:key="node.id"
:index="i"
class="de-sort-field-span"
:class="sortNode.sort === 'asc' && sortNode.id === node.id ? 'de-active-li': ''"
@click="saveAscField(node)"
>
<span>{{ node.name }}</span>
</li>
</ul>
</el-popover>
</el-dropdown-item>
<el-dropdown-item
v-if="isSortWidget"
:disabled="disabledSort"
:command="beforeClickItem('desc')"
>
<span
v-popover:popoverdesc
class="el-dropdown-link inner-dropdown-menu de-sort-menu"
:class="!disabledSort && sortNode.sort === 'desc' ? 'de-active-li': ''"
>
<span>
<span>{{ $t('chart.desc') }}</span>
</span>
<i class="el-icon-arrow-right el-icon--right" />
</span>
<el-popover
ref="popoverdesc"
v-model="descFieldsShow"
placement="right-start"
width="120"
:close-delay="500"
trigger="hover"
>
<ul class="de-ul">
<li
v-for="(node, i) in allFields"
:key="node.id"
:index="i"
class="de-sort-field-span"
:class="sortNode.sort === 'desc' && sortNode.id === node.id ? 'de-active-li': ''"
@click="saveDescField(node)"
>
<span>{{ node.name }}</span>
</li>
</ul>
</el-popover>
</el-dropdown-item>
<el-dropdown-item
:divided="isSortWidget"
icon="el-icon-delete"
:command="beforeClickItem('remove')"
>
<span class="de-delete-field">{{ $t('chart.delete') }}</span>
</el-dropdown-item>
<slot />
</el-dropdown-menu>
</span>
</el-dropdown>
{{ item.name }}
</el-tag>
</span>
</template>
@ -124,103 +22,34 @@ export default {
index: {
type: Number,
required: true
},
allFields: {
type: Array,
default: () => []
},
sort: {
type: Object,
default: () => null
},
isSortWidget: {
type: Boolean,
default: false
}
},
data() {
return {
radio: 0,
ascFieldsShow: false,
descFieldsShow: false,
defaultSortProp: {
sort: 'none'
},
sortNode: null
}
},
computed: {
disabledSort() {
return this.index > 0
}
},
watch: {
index(val, old) {
/* index(val, old) {
if (val !== old) {
this.sortChange('none')
}
}
} */
},
mounted() {
},
created() {
if (!this.sortNode) {
this.sortNode = this.sort && this.sort.id ? JSON.parse(JSON.stringify(this.sort)) : JSON.parse(JSON.stringify(this.defaultSortProp))
}
},
methods: {
clickItem(param) {
if (!param) {
return
}
switch (param.type) {
case 'none':
this.sortChange('none')
break
case 'asc':
this.sortChange('asc')
break
case 'desc':
this.sortChange('desc')
break
case 'remove':
this.removeItem()
break
default:
break
}
},
beforeClickItem(type) {
return {
type: type
}
},
removeItem() {
this.item.index = this.index
this.$emit('closeItem', this.item)
},
saveAscField({ id, name }) {
this.ascFieldsShow = false
const sort = 'asc'
this.sortNode = { id, name, sort }
this.$emit('sort-change', this.sortNode)
},
saveDescField({ id, name }) {
this.descFieldsShow = false
const sort = 'desc'
this.sortNode = { id, name, sort }
this.$emit('sort-change', this.sortNode)
},
sortChange(type) {
this.sortNode.sort = type
if (type === 'none') {
this.sortNode = { sort: 'none' }
}
this.$emit('sort-change', this.sortNode)
}
}
@ -249,51 +78,4 @@ span {
font-size: 12px;
}
.de-ul li {
margin: 5px 2px;
cursor: pointer;
&:hover {
color: #409EFF;
border-color: rgb(198, 226, 255);
background-color: rgb(236, 245, 255);
}
&:before {
content: "";
width: 6px;
height: 6px;
display: inline-block;
border-radius: 50%;
vertical-align: middle;
margin-right: 5px;
}
}
.de-active-li {
&:before {
background: #409EFF;
}
}
.de-sort-menu::before {
content: "";
width: 6px;
height: 6px;
display: inline-block;
border-radius: 50%;
vertical-align: middle;
margin-right: 5px;
}
.de-delete-field {
margin-left: 4px;
}
.de-sort-field-span {
display: inline-flexbox;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
</style>

View File

@ -43,11 +43,10 @@
import ElVisualSelect from '@/components/elVisualSelect'
import { linkMultFieldValues, multFieldValues } from '@/api/dataset/dataset'
import bus from '@/utils/bus'
import { isSameVueObj } from '@/utils'
import { isSameVueObj, mergeCustomSortOption } from '@/utils'
import { getLinkToken, getToken } from '@/utils/auth'
import customInput from '@/components/widget/deWidget/customInput'
import { textSelectWidget } from '@/components/widget/deWidget/serviceNameFn.js'
export default {
components: { ElVisualSelect },
mixins: [customInput],
@ -123,6 +122,9 @@ export default {
const i18nKey = this.element.options.attrs.multiple ? 'panel.multiple_choice' : 'panel.single_choice'
const i18nValue = this.$t(i18nKey)
return '(' + i18nValue + ')'
},
isCustomSortWidget() {
return this.element.serviceName === 'textSelectWidget'
}
},
@ -347,7 +349,11 @@ export default {
},
optionData(data) {
if (!data) return null
return data.filter(item => !!item).map(item => {
let tempData = data.filter(item => !!item)
if (this.isCustomSortWidget && this.element.options.attrs?.sort?.sort === 'custom') {
tempData = mergeCustomSortOption(this.element.options.attrs.sort.list, tempData)
}
return tempData.map(item => {
return {
id: item,
text: item

View File

@ -99,6 +99,9 @@ class TextSelectServiceImpl extends WidgetService {
isSortWidget() {
return true
}
isCustomSortWidget() {
return true
}
fillValueDerfault(element) {
const defaultV = element.options.value === null ? '' : element.options.value.toString()

View File

@ -325,3 +325,12 @@ export const changeFavicon = link => {
document.head.appendChild($favicon)
}
}
export const mergeCustomSortOption = (customSortList, sourceList) => {
if (!customSortList?.length) return sourceList?.length ? sourceList : []
if (!sourceList?.length) return customSortList?.length ? customSortList : []
const result = [...customSortList, ...sourceList]
return [...new Set(result)]
}

View File

@ -267,7 +267,6 @@
<div v-if="currentElement.options && currentElement.options.attrs">
<filter-head
:element="currentElement"
:widget="widget"
/>
<filter-control
@ -413,6 +412,10 @@ export default {
this.myAttrs.fieldId = null
this.myAttrs.activeName = null
}
if (this.myAttrs.sort?.sort === 'custom') {
this.myAttrs.sort.list = []
}
this.enableSureButton()
},
@ -857,7 +860,6 @@ export default {
.de-dialog-container {
height: 50vh !important;
}
.ms-aside-container {

View File

@ -51,6 +51,19 @@
</el-popover>
</span>
<span
v-if="widget.isCustomSortWidget && widget.isCustomSortWidget()"
style="padding-left: 10px;"
>
<filter-sort
:widget="widget"
:element="element"
@sort-change="sortChange"
/>
</span>
</div>
</el-col>
@ -197,9 +210,10 @@
</template>
<script>
import FilterSort from './FilterSort'
export default {
name: 'FilterControl',
components: { FilterSort },
props: {
widget: {
type: Object,
@ -235,7 +249,11 @@ export default {
]
}
},
computed: {},
computed: {
fieldIds() {
return this.element.options.attrs.fieldId || []
}
},
watch: {
'childViews.datasetParams': {
handler(newName, oldName) {
@ -265,6 +283,10 @@ export default {
}
},
methods: {
sortChange(param) {
this.element.options.attrs.sort = param
},
multipleChange(value) {
this.fillAttrs2Filter()
},

View File

@ -0,0 +1,150 @@
<template>
<div>
<draggable
v-model="sortList"
group="drag"
animation="300"
:move="onMove"
class="drag-list"
@update="onUpdate"
>
<transition-group class="draggable-group">
<span
v-for="(item) in sortList"
:key="item"
class="item-dimension"
:title="item"
>
<svg-icon
icon-class="drag"
class="item-icon"
/>
<span class="item-span">
{{ item }}
</span>
</span>
</transition-group>
</draggable>
</div>
</template>
<script>
import { linkMultFieldValues, multFieldValues } from '@/api/dataset/dataset'
import { getLinkToken, getToken } from '@/utils/auth'
import { mergeCustomSortOption } from '@/utils'
export default {
name: 'FilterCustomSort',
props: {
fieldId: {
type: String,
default: null
},
customSortList: {
type: Array,
default: null
}
},
data() {
return {
sortList: []
}
},
computed: {
panelInfo() {
return this.$store.state.panel.panelInfo
}
},
watch: {
fieldId() {
this.init()
}
},
mounted() {
this.init()
},
methods: {
init() {
let method = multFieldValues
const token = this.$store.getters.token || getToken()
const linkToken = this.$store.getters.linkToken || getLinkToken()
if (!token && linkToken) {
method = linkMultFieldValues
}
const param = { fieldIds: this.fieldId.split(',') }
if (this.panelInfo.proxy) {
param.userId = this.panelInfo.proxy
}
method(param).then(res => {
this.sortList = this.optionData(res.data)
})
},
optionData(data) {
if (!data) return null
return mergeCustomSortOption(this.customSortList, data.filter(item => !!item))
},
onMove() {
},
onUpdate() {
this.$emit('on-filter-sort-change', this.sortList)
}
}
}
</script>
<style scoped>
.drag-list {
overflow: auto;
height: 30vh;
}
.item-dimension {
padding: 2px;
margin: 2px;
border: solid 1px #eee;
text-align: left;
color: #606266;
/*background-color: rgba(35,46,64,.05);*/
background-color: white;
display: flex;
align-items: center;
}
.item-icon{
cursor: move;
margin: 0 2px;
}
.item-span{
display: inline-block;
width: 100%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.blackTheme .item-dimension {
border: solid 1px;
border-color: var(--TableBorderColor);
color: var(--TextPrimary);
background-color: var(--MainBG);
}
.item-dimension + .item-dimension {
margin-top: 6px;
}
.item-dimension:hover {
color: #1890ff;
background: #e8f4ff;
border-color: #a3d3ff;
cursor: pointer;
}
.blackTheme .item-dimension:hover {
color: var(--Main);
background: var(--ContentBG);
cursor: pointer;
}
</style>

View File

@ -23,13 +23,10 @@
>
<drag-item
:key="item.id"
:is-sort-widget="isSortWidget"
:item="item"
:index="index"
:sort="element.options.attrs.sort"
:all-fields="index ? [] : tableFields"
@closeItem="closeItem"
@sort-change="sortChange"
/>
</v-flex>
@ -46,7 +43,6 @@
<script>
import draggable from 'vuedraggable'
import DragItem from '@/components/dragItem'
import { fieldListWithPermission } from '@/api/dataset/dataset'
export default {
name: 'FilterHead',
@ -59,55 +55,22 @@ export default {
type: Object,
default: () => {
}
},
widget: {
type: Object,
default: null
}
},
data() {
return {
targets: [],
tableFields: []
targets: []
}
},
computed: {
isSortWidget() {
return this.widget && this.widget.isSortWidget && this.widget.isSortWidget()
},
firstTableId() {
if (!this.isSortWidget) return null
if (this.element.options.attrs.dragItems && this.element.options.attrs.dragItems.length) {
return this.element.options.attrs.dragItems[0].tableId
}
return null
}
},
watch: {
firstTableId(val, old) {
if (val !== old) {
this.loadFields()
}
}
},
created() {
if (this.isSortWidget && this.element.options.attrs.dragItems && this.element.options.attrs.dragItems.length) {
this.loadFields()
}
},
methods: {
loadFields() {
if (this.firstTableId) {
fieldListWithPermission(this.firstTableId).then(res => {
this.tableFields = JSON.parse(JSON.stringify(res.data))
})
} else {
this.tableFields = []
}
},
onMove(e, originalEvent) {
return true
},
@ -120,9 +83,6 @@ export default {
if (!index) {
this.element.options.attrs.sort = null
}
},
sortChange(param) {
this.element.options.attrs.sort = param
}
}
}

View File

@ -0,0 +1,378 @@
<template>
<div>
<el-dropdown
trigger="click"
size="mini"
@command="clickItem"
>
<span class="el-dropdown-link filter-sort-span">
{{ $t('chart.sort') }}
<i class="el-icon-sort i-filter i-filter-active" />
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item
:command="beforeClickItem('none')"
>
<span
class="de-sort-menu"
:class="(!sortNode || sortNode.sort === 'none') ? 'de-active-li': ''"
>{{
$t('chart.none')
}}</span>
</el-dropdown-item>
<el-dropdown-item
:command="beforeClickItem('asc')"
>
<span
v-popover:popoverasc
class="el-dropdown-link inner-dropdown-menu de-sort-menu"
:class="sortNode.sort === 'asc' ? 'de-active-li': ''"
>
<span>
<span>{{ $t('chart.asc') }}</span>
</span>
<i class="el-icon-arrow-right el-icon--right" />
</span>
<el-popover
ref="popoverasc"
v-model="ascFieldsShow"
placement="right-start"
width="120"
:close-delay="500"
trigger="hover"
>
<ul class="de-ul">
<li
v-for="(node, i) in tableFields"
:key="node.id"
:index="i"
class="de-sort-field-span"
:class="sortNode.sort === 'asc' && sortNode.id === node.id ? 'de-active-li': ''"
@click="saveAscField(node)"
>
<span>{{ node.name }}</span>
</li>
</ul>
</el-popover>
</el-dropdown-item>
<el-dropdown-item
:command="beforeClickItem('desc')"
>
<span
v-popover:popoverdesc
class="el-dropdown-link inner-dropdown-menu de-sort-menu"
:class="sortNode.sort === 'desc' ? 'de-active-li': ''"
>
<span>
<span>{{ $t('chart.desc') }}</span>
</span>
<i class="el-icon-arrow-right el-icon--right" />
</span>
<el-popover
ref="popoverdesc"
v-model="descFieldsShow"
placement="right-start"
width="120"
:close-delay="500"
trigger="hover"
>
<ul class="de-ul">
<li
v-for="(node, i) in tableFields"
:key="node.id"
:index="i"
class="de-sort-field-span"
:class="sortNode.sort === 'desc' && sortNode.id === node.id ? 'de-active-li': ''"
@click="saveDescField(node)"
>
<span>{{ node.name }}</span>
</li>
</ul>
</el-popover>
</el-dropdown-item>
<el-dropdown-item
:command="beforeClickItem('custom')"
>
<span
class="de-sort-menu"
:class="(sortNode && sortNode.sort === 'custom') ? 'de-active-li': ''"
>{{ $t('chart.custom_sort') }}...</span>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<el-dialog
v-if="isCustomSortWidget && showCustomSort"
append-to-body
:title="$t('chart.custom_sort')"
:visible="showCustomSort"
:show-close="false"
width="300px"
class="dialog-css"
>
<div style="width: 100%;overflow-y: auto;overflow-x: hidden;word-break: break-all;position: relative;">
<filter-custom-sort
:field-id="fieldIds"
:custom-sort-list="customSortList"
@on-filter-sort-change="customSortChange"
/>
</div>
<div
slot="footer"
class="dialog-footer filter-custom-sort-footer"
>
<el-button
size="mini"
@click="cancelCustomSort"
>{{ $t('chart.cancel') }}</el-button>
<el-button
type="primary"
size="mini"
@click="saveCustomSort"
>{{ $t('chart.confirm') }}</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { fieldListWithPermission } from '@/api/dataset/dataset'
import FilterCustomSort from './FilterCustomSort'
export default {
name: 'FilterSort',
components: { FilterCustomSort },
props: {
widget: {
type: Object,
default: null
},
element: {
type: Object,
default: null
}
},
data() {
return {
ascFieldsShow: false,
descFieldsShow: false,
defaultSortProp: {
sort: 'none'
},
tableFields: [],
sortNode: null,
showCustomSort: false,
customSortList: []
}
},
computed: {
fieldIds() {
return this.element.options.attrs.fieldId || []
},
isSortWidget() {
return this.widget && this.widget.isSortWidget && this.widget.isSortWidget()
},
isCustomSortWidget() {
return this.widget && this.widget.isCustomSortWidget && this.widget.isCustomSortWidget()
},
firstTableId() {
if (!this.isSortWidget) return null
if (this.element.options.attrs.dragItems && this.element.options.attrs.dragItems.length) {
return this.element.options.attrs.dragItems[0].tableId
}
return null
}
},
watch: {
firstTableId(val, old) {
if (val !== old) {
this.loadFields()
}
}
},
mounted() {
},
created() {
if (this.isSortWidget && this.element.options.attrs.dragItems && this.element.options.attrs.dragItems.length) {
if (this.element.options.attrs.sort) {
this.sortNode = JSON.parse(JSON.stringify(this.element.options.attrs.sort))
if (this.isCustomSortWidget && this.sortNode.sort === 'custom' && this.sortNode.list) {
this.customSortList = JSON.parse(JSON.stringify(this.sortNode.list))
}
}
if (!this.sortNode) {
this.sortNode = JSON.parse(JSON.stringify(this.defaultSortProp))
}
this.loadFields()
}
},
methods: {
customSortChange(list) {
this.customSortList = list
},
cancelCustomSort() {
this.customSortList = this.sortNode.list?.length ? JSON.parse(JSON.stringify(this.sortNode.list)) : []
this.showCustomSort = false
},
saveCustomSort() {
this.sortNode = {
sort: 'custom',
list: this.customSortList
}
this.$emit('sort-change', this.sortNode)
this.showCustomSort = false
},
loadFields() {
if (this.firstTableId) {
fieldListWithPermission(this.firstTableId).then(res => {
this.tableFields = JSON.parse(JSON.stringify(res.data))
})
} else {
this.tableFields = []
}
},
clickItem(param) {
if (!param) {
return
}
switch (param.type) {
case 'none':
this.sortChange('none')
break
case 'asc':
this.sortChange('asc')
break
case 'desc':
this.sortChange('desc')
break
case 'custom':
this.sortChange('custom')
break
default:
break
}
},
beforeClickItem(type) {
return {
type: type
}
},
saveAscField({ id, name }) {
this.ascFieldsShow = false
const sort = 'asc'
this.sortNode = { id, name, sort }
this.$emit('sort-change', this.sortNode)
},
saveDescField({ id, name }) {
this.descFieldsShow = false
const sort = 'desc'
this.sortNode = { id, name, sort }
this.$emit('sort-change', this.sortNode)
},
sortChange(type) {
if (type === 'custom') {
this.showCustomSort = true
return
}
this.sortNode.sort = type
if (type === 'none') {
this.sortNode = { sort: 'none' }
}
this.$emit('sort-change', this.sortNode)
}
}
}
</script>
<style lang="scss" scoped>
.de-ul li {
margin: 5px 2px;
cursor: pointer;
&:hover {
color: #409EFF;
border-color: rgb(198, 226, 255);
background-color: rgb(236, 245, 255);
}
&:before {
content: "";
width: 6px;
height: 6px;
display: inline-block;
border-radius: 50%;
vertical-align: middle;
margin-right: 5px;
}
}
.de-active-li {
&:before {
background: #409EFF;
}
}
.de-sort-menu::before {
content: "";
width: 6px;
height: 6px;
display: inline-block;
border-radius: 50%;
vertical-align: middle;
margin-right: 5px;
}
.de-delete-field {
margin-left: 4px;
}
.de-sort-field-span {
display: inline-flexbox;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
::v-deep .filter-custom-sort-footer {
padding-top: 5px !important;
border-top: solid 1px #eee;
text-align: end;
}
.filter-sort-span {
color: #303133;
font-weight: 500;
cursor: pointer;
margin-left: 10px;
i {
margin-left: 1px;
}
}
.dialog-css ::v-deep .el-dialog__title {
font-size: 14px;
}
.dialog-css ::v-deep .el-dialog__header {
padding: 20px 20px 0;
}
.dialog-css ::v-deep .el-dialog__body {
padding: 10px 20px 5px;
}
</style>