mirror of
https://github.com/dataease/dataease.git
synced 2025-02-24 19:42:56 +08:00
commit
47b685d551
@ -11,6 +11,13 @@ export interface DatasetOrFolder {
|
||||
allFields?: Array<{}>
|
||||
}
|
||||
|
||||
export interface EnumValue {
|
||||
queryId: string
|
||||
displayId?: string
|
||||
sortId?: string
|
||||
sort?: string
|
||||
}
|
||||
|
||||
interface Fields {
|
||||
fields: Array<{}>
|
||||
data: Array<{}>
|
||||
@ -76,6 +83,12 @@ export const renameDatasetTree = async (data: DatasetOrFolder): Promise<IRespons
|
||||
})
|
||||
}
|
||||
|
||||
export const enumValueObj = async (data: EnumValue): Promise<Record<string, string>[]> => {
|
||||
return request.post({ url: '/datasetData/enumValueObj', data }).then(res => {
|
||||
return res?.data
|
||||
})
|
||||
}
|
||||
|
||||
export const moveDatasetTree = async (data: DatasetOrFolder): Promise<IResponse> => {
|
||||
return request.post({ url: '/datasetTree/move', data }).then(res => {
|
||||
return res?.data
|
||||
|
@ -198,23 +198,24 @@ const dragover = () => {
|
||||
}
|
||||
|
||||
const drop = e => {
|
||||
const componentInfo: ComponentInfo = JSON.parse(e.dataTransfer.getData('dimension') || '{}')
|
||||
if (!componentInfo.id) return
|
||||
const checkedFields = []
|
||||
const checkedFieldsMap = {}
|
||||
datasetFieldList.value.forEach(ele => {
|
||||
if (ele.tableId === componentInfo.datasetId) {
|
||||
checkedFields.push(ele.id)
|
||||
checkedFieldsMap[ele.id] = componentInfo.id
|
||||
}
|
||||
})
|
||||
list.value.push({
|
||||
...infoFormat(componentInfo),
|
||||
auto: true,
|
||||
optionValueSource: 1,
|
||||
checkedFields,
|
||||
checkedFieldsMap,
|
||||
displayType: `${componentInfo.deType}`
|
||||
const componentInfoArr: ComponentInfo[] = JSON.parse(e.dataTransfer.getData('dimension') || '{}')
|
||||
componentInfoArr.forEach(componentInfo => {
|
||||
const checkedFields = []
|
||||
const checkedFieldsMap = {}
|
||||
datasetFieldList.value.forEach(ele => {
|
||||
if (ele.tableId === componentInfo.datasetId) {
|
||||
checkedFields.push(ele.id)
|
||||
checkedFieldsMap[ele.id] = componentInfo.id
|
||||
}
|
||||
})
|
||||
list.value.push({
|
||||
...infoFormat(componentInfo),
|
||||
auto: true,
|
||||
optionValueSource: 1,
|
||||
checkedFields,
|
||||
checkedFieldsMap,
|
||||
displayType: `${componentInfo.deType}`
|
||||
})
|
||||
})
|
||||
element.value.propValue = [...list.value]
|
||||
snapshotStore.recordSnapshotCache()
|
||||
|
@ -240,6 +240,8 @@ const computedTree = computed(() => {
|
||||
|
||||
const handleDatasetChange = () => {
|
||||
curComponent.value.field.id = ''
|
||||
curComponent.value.displayId = ''
|
||||
curComponent.value.sortId = ''
|
||||
getOptions(curComponent.value.dataset.id, curComponent.value)
|
||||
}
|
||||
|
||||
@ -672,6 +674,7 @@ const parameterCompletion = () => {
|
||||
const attributes = {
|
||||
timeType: 'fixed',
|
||||
required: false,
|
||||
defaultMapValue: [],
|
||||
parametersStart: null,
|
||||
conditionType: 0,
|
||||
conditionValueOperatorF: 'eq',
|
||||
@ -691,6 +694,9 @@ const parameterCompletion = () => {
|
||||
timeNumRange: 0,
|
||||
relativeToCurrentTypeRange: 'year',
|
||||
aroundRange: 'f',
|
||||
displayId: '',
|
||||
sortId: '',
|
||||
sort: 'asc',
|
||||
arbitraryTimeRange: new Date(),
|
||||
setTimeRange: false,
|
||||
showEmpty: false,
|
||||
@ -743,10 +749,6 @@ const handleCondition = item => {
|
||||
valueSource.value.push('')
|
||||
valueSource.value.push('')
|
||||
}
|
||||
curComponent.value.sortField = curComponent.value.sortField ?? {
|
||||
id: '',
|
||||
sortType: 'asc'
|
||||
}
|
||||
parameterCompletion()
|
||||
nextTick(() => {
|
||||
curComponent.value.showError = showError.value
|
||||
@ -1330,7 +1332,7 @@ defineExpose({
|
||||
<div class="value">
|
||||
<el-select
|
||||
@change="handleFieldChange"
|
||||
placeholder="请选择展示字段"
|
||||
placeholder="查询字段"
|
||||
v-model="curComponent.field.id"
|
||||
>
|
||||
<template v-if="curComponent.field.id" #prefix>
|
||||
@ -1374,24 +1376,18 @@ defineExpose({
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="value">
|
||||
<el-select
|
||||
clearable
|
||||
placeholder="请选择排序字段"
|
||||
v-model="curComponent.sortField.id"
|
||||
class="sort-field"
|
||||
@change="handleFieldChange"
|
||||
>
|
||||
<template v-if="curComponent.sortField.id" #prefix>
|
||||
<el-select placeholder="显示字段" v-model="curComponent.displayId">
|
||||
<template v-if="curComponent.displayId" #prefix>
|
||||
<el-icon>
|
||||
<Icon
|
||||
:name="`field_${
|
||||
fieldType[
|
||||
getDetype(curComponent.sortField.id, curComponent.dataset.fields)
|
||||
getDetype(curComponent.displayId, curComponent.dataset.fields)
|
||||
]
|
||||
}`"
|
||||
:className="`field-icon-${
|
||||
fieldType[
|
||||
getDetype(curComponent.sortField.id, curComponent.dataset.fields)
|
||||
getDetype(curComponent.displayId, curComponent.dataset.fields)
|
||||
]
|
||||
}`"
|
||||
></Icon>
|
||||
@ -1424,9 +1420,57 @@ defineExpose({
|
||||
</div>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="value">
|
||||
<el-select
|
||||
clearable
|
||||
placeholder="请选择排序字段"
|
||||
v-model="curComponent.sortId"
|
||||
class="sort-field"
|
||||
@change="handleFieldChange"
|
||||
>
|
||||
<template v-if="curComponent.sortId" #prefix>
|
||||
<el-icon>
|
||||
<Icon
|
||||
:name="`field_${
|
||||
fieldType[getDetype(curComponent.sortId, curComponent.dataset.fields)]
|
||||
}`"
|
||||
:className="`field-icon-${
|
||||
fieldType[getDetype(curComponent.sortId, curComponent.dataset.fields)]
|
||||
}`"
|
||||
></Icon>
|
||||
</el-icon>
|
||||
</template>
|
||||
<el-option
|
||||
v-for="ele in curComponent.dataset.fields.filter(
|
||||
ele =>
|
||||
ele.deType === +curComponent.displayType ||
|
||||
([3, 4].includes(ele.deType) && +curComponent.displayType === 2)
|
||||
)"
|
||||
:key="ele.id"
|
||||
:label="ele.name"
|
||||
:value="ele.id"
|
||||
:disabled="ele.desensitized"
|
||||
>
|
||||
<div
|
||||
class="flex-align-center icon"
|
||||
:title="ele.desensitized ? '脱敏字段,不能被设置为查询条件' : ''"
|
||||
>
|
||||
<el-icon>
|
||||
<Icon
|
||||
:name="`field_${fieldType[ele.deType]}`"
|
||||
:className="`field-icon-${fieldType[ele.deType]}`"
|
||||
></Icon>
|
||||
</el-icon>
|
||||
<span>
|
||||
{{ ele.name }}
|
||||
</span>
|
||||
</div>
|
||||
</el-option>
|
||||
</el-select>
|
||||
<el-select
|
||||
class="sort-type"
|
||||
v-model="curComponent.sortField.sortType"
|
||||
v-model="curComponent.sort"
|
||||
@change="handleFieldChange"
|
||||
>
|
||||
<el-option label="升序" value="asc" />
|
||||
|
@ -1,15 +1,19 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, toRefs, PropType, onBeforeMount, shallowRef, watch, nextTick, computed } from 'vue'
|
||||
import { getEnumValue } from '@/api/dataset'
|
||||
import { enumValueObj, type EnumValue, getEnumValue } from '@/api/dataset'
|
||||
import { cloneDeep, debounce } from 'lodash-es'
|
||||
|
||||
interface SelectConfig {
|
||||
selectValue: any
|
||||
defaultMapValue: any
|
||||
defaultValue: any
|
||||
checkedFieldsMap: object
|
||||
displayType: string
|
||||
showEmpty: boolean
|
||||
id: string
|
||||
displayId: string
|
||||
sort: string
|
||||
sortId: string
|
||||
checkedFields: string[]
|
||||
field: {
|
||||
id: string
|
||||
@ -44,22 +48,49 @@ const props = defineProps({
|
||||
}
|
||||
})
|
||||
const { config } = toRefs(props)
|
||||
|
||||
let enumValueArr = []
|
||||
const selectValue = ref()
|
||||
const loading = ref(false)
|
||||
const multiple = ref(false)
|
||||
const options = shallowRef([])
|
||||
|
||||
const setDefaultMapValue = arr => {
|
||||
const { displayId, field } = config.value
|
||||
if (!displayId || displayId === field?.id) {
|
||||
return []
|
||||
}
|
||||
let defaultMapValue = {}
|
||||
let defaultValue = []
|
||||
arr.forEach(ele => {
|
||||
defaultMapValue[ele] = []
|
||||
})
|
||||
enumValueArr.forEach(ele => {
|
||||
if (defaultMapValue[ele[displayId]]) {
|
||||
defaultMapValue[ele[displayId]].push(ele[field?.id])
|
||||
}
|
||||
})
|
||||
Object.values(defaultMapValue).forEach(ele => {
|
||||
defaultValue = [...defaultValue, ...(ele as unknown as string[])]
|
||||
})
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
const handleValueChange = () => {
|
||||
const value = Array.isArray(selectValue.value) ? [...selectValue.value] : selectValue.value
|
||||
if (!props.isConfig) {
|
||||
config.value.selectValue = Array.isArray(selectValue.value)
|
||||
? [...selectValue.value]
|
||||
: selectValue.value
|
||||
config.value.defaultMapValue = setDefaultMapValue(
|
||||
Array.isArray(selectValue.value) ? [...selectValue.value] : [selectValue.value]
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
config.value.defaultValue = value
|
||||
config.value.defaultMapValue = setDefaultMapValue(
|
||||
Array.isArray(selectValue.value) ? [...selectValue.value] : [selectValue.value]
|
||||
)
|
||||
}
|
||||
|
||||
const displayTypeChange = () => {
|
||||
@ -68,7 +99,7 @@ const displayTypeChange = () => {
|
||||
selectValue.value = config.value.multiple ? [] : undefined
|
||||
}
|
||||
|
||||
const handleFieldIdChange = (val: string[]) => {
|
||||
const handleFieldIdDefaultChange = (val: string[]) => {
|
||||
loading.value = true
|
||||
getEnumValue(val)
|
||||
.then(res => {
|
||||
@ -96,6 +127,40 @@ const handleFieldIdChange = (val: string[]) => {
|
||||
})
|
||||
}
|
||||
|
||||
const handleFieldIdChange = (val: EnumValue) => {
|
||||
enumValueArr = []
|
||||
loading.value = true
|
||||
enumValueObj(val)
|
||||
.then(res => {
|
||||
enumValueArr = res || []
|
||||
options.value = [
|
||||
...new Set(
|
||||
(res || []).map(ele => {
|
||||
return ele[val.displayId || val.queryId]
|
||||
})
|
||||
)
|
||||
].map(ele => {
|
||||
return {
|
||||
label: ele,
|
||||
value: ele
|
||||
}
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
if (config.value.defaultValueCheck) {
|
||||
selectValue.value = Array.isArray(config.value.defaultValue)
|
||||
? [...config.value.defaultValue]
|
||||
: config.value.defaultValue
|
||||
} else {
|
||||
selectValue.value = Array.isArray(selectValue.value)
|
||||
? [...selectValue.value]
|
||||
: selectValue.value
|
||||
}
|
||||
setEmptyData()
|
||||
})
|
||||
}
|
||||
|
||||
const visible = ref(false)
|
||||
const visibleChange = (val: boolean) => {
|
||||
setTimeout(() => {
|
||||
@ -181,7 +246,12 @@ watch(
|
||||
)
|
||||
|
||||
watch(
|
||||
() => config.value.field.id,
|
||||
[
|
||||
() => config.value.field.id,
|
||||
() => config.value.displayId,
|
||||
() => config.value.sort,
|
||||
() => config.value.sortId
|
||||
],
|
||||
val => {
|
||||
if (!val) return
|
||||
debounceOptions(1)
|
||||
@ -214,19 +284,30 @@ watch(
|
||||
|
||||
const setOptions = (num: number) => {
|
||||
if (num !== config.value.optionValueSource) return
|
||||
const { optionValueSource, checkedFieldsMap, checkedFields, field, valueSource } = config.value
|
||||
const {
|
||||
optionValueSource,
|
||||
checkedFieldsMap,
|
||||
checkedFields,
|
||||
field,
|
||||
valueSource,
|
||||
displayId,
|
||||
sort,
|
||||
sortId
|
||||
} = config.value
|
||||
switch (optionValueSource) {
|
||||
case 0:
|
||||
const arr = Object.values(checkedFieldsMap).filter(ele => !!ele) as string[]
|
||||
if (!!checkedFields.length && !!arr.length) {
|
||||
handleFieldIdChange(checkedFields.map(ele => checkedFieldsMap[ele]).filter(ele => !!ele))
|
||||
handleFieldIdDefaultChange(
|
||||
checkedFields.map(ele => checkedFieldsMap[ele]).filter(ele => !!ele)
|
||||
)
|
||||
} else {
|
||||
options.value = []
|
||||
}
|
||||
break
|
||||
case 1:
|
||||
if (field.id) {
|
||||
handleFieldIdChange([field.id])
|
||||
handleFieldIdChange({ queryId: field.id, displayId: displayId || field.id, sort, sortId })
|
||||
} else {
|
||||
options.value = []
|
||||
}
|
||||
|
@ -15,10 +15,19 @@ const infoFormat = (obj: ComponentInfo) => {
|
||||
name,
|
||||
deType
|
||||
},
|
||||
sortField: {
|
||||
id: '',
|
||||
sortType: 'asc'
|
||||
},
|
||||
displayId: '',
|
||||
sortId: '',
|
||||
sort: 'asc',
|
||||
defaultMapValue: [],
|
||||
conditionType: 0,
|
||||
conditionValueOperatorF: 'eq',
|
||||
conditionValueF: '',
|
||||
conditionValueOperatorS: 'like',
|
||||
conditionValueS: '',
|
||||
defaultConditionValueOperatorF: 'eq',
|
||||
defaultConditionValueF: '',
|
||||
defaultConditionValueOperatorS: 'like',
|
||||
defaultConditionValueS: '',
|
||||
timeType: 'fixed',
|
||||
relativeToCurrent: 'custom',
|
||||
required: false,
|
||||
|
@ -81,8 +81,17 @@ const getValueByDefaultValueCheckOrFirstLoad = (
|
||||
defaultValue: any,
|
||||
selectValue: any,
|
||||
firstLoad: boolean,
|
||||
multiple: boolean
|
||||
multiple: boolean,
|
||||
defaultMapValue: any,
|
||||
optionValueSource: number
|
||||
) => {
|
||||
if (optionValueSource === 1) {
|
||||
if (firstLoad && !selectValue?.length) {
|
||||
return defaultValueCheck ? defaultMapValue : multiple ? [] : ''
|
||||
}
|
||||
return (selectValue?.length ? defaultMapValue : selectValue) || ''
|
||||
}
|
||||
|
||||
if (firstLoad && !selectValue?.length) {
|
||||
return defaultValueCheck ? defaultValue : multiple ? [] : ''
|
||||
}
|
||||
@ -134,7 +143,8 @@ const getOperator = (
|
||||
conditionValueF,
|
||||
conditionValueOperatorS,
|
||||
conditionValueS,
|
||||
firstLoad
|
||||
firstLoad,
|
||||
optionValueSource
|
||||
) => {
|
||||
const valueF = firstLoad ? defaultConditionValueF : conditionValueF
|
||||
const valueS = firstLoad ? defaultConditionValueS : conditionValueS
|
||||
@ -153,7 +163,11 @@ const getOperator = (
|
||||
return valueF === '' ? operatorS : operatorF
|
||||
}
|
||||
|
||||
return [1, 7].includes(+displayType) ? 'between' : multiple ? 'in' : 'eq'
|
||||
return [1, 7].includes(+displayType)
|
||||
? 'between'
|
||||
: multiple || optionValueSource === 1
|
||||
? 'in'
|
||||
: 'eq'
|
||||
}
|
||||
|
||||
export const searchQuery = (queryComponentList, filter, curComponentId, firstLoad) => {
|
||||
@ -184,6 +198,8 @@ export const searchQuery = (queryComponentList, filter, curComponentId, firstLoa
|
||||
defaultValueCheck,
|
||||
timeType = 'fixed',
|
||||
defaultValue,
|
||||
optionValueSource,
|
||||
defaultMapValue,
|
||||
parameters = [],
|
||||
parametersCheck = false,
|
||||
isTree = false,
|
||||
@ -247,7 +263,9 @@ export const searchQuery = (queryComponentList, filter, curComponentId, firstLoa
|
||||
defaultValue,
|
||||
value,
|
||||
firstLoad,
|
||||
multiple
|
||||
multiple,
|
||||
defaultMapValue,
|
||||
optionValueSource
|
||||
)
|
||||
}
|
||||
if (
|
||||
@ -273,7 +291,8 @@ export const searchQuery = (queryComponentList, filter, curComponentId, firstLoa
|
||||
conditionValueF,
|
||||
conditionValueOperatorS,
|
||||
conditionValueS,
|
||||
firstLoad
|
||||
firstLoad,
|
||||
optionValueSource
|
||||
)
|
||||
filter.push({
|
||||
componentId: ele.id,
|
||||
|
@ -9,7 +9,8 @@ import {
|
||||
nextTick,
|
||||
onBeforeMount,
|
||||
provide,
|
||||
h
|
||||
h,
|
||||
unref
|
||||
} from 'vue'
|
||||
import Icon from '@/components/icon-custom/src/Icon.vue'
|
||||
import type { FormInstance, FormRules } from 'element-plus-secondary'
|
||||
@ -289,7 +290,14 @@ const realQuota = computed(() => {
|
||||
provide('quotaData', realQuota)
|
||||
|
||||
const startToMove = (e, item) => {
|
||||
e.dataTransfer.setData('dimension', JSON.stringify({ ...item, datasetId: view.value.tableId }))
|
||||
e.dataTransfer.setData(
|
||||
'dimension',
|
||||
JSON.stringify(
|
||||
item
|
||||
.filter(ele => ele.id)
|
||||
.map(ele => ({ ...cloneDeep(unref(ele)), datasetId: view.value.tableId }))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
const dimensionItemChange = () => {
|
||||
@ -785,7 +793,6 @@ const onChangeYAxisForm = val => {
|
||||
}
|
||||
|
||||
const onChangeYAxisExtForm = val => {
|
||||
console.log('onChangeYAxisExtForm', val)
|
||||
view.value.customStyle.yAxisExt = val
|
||||
renderChart(view.value)
|
||||
}
|
||||
@ -1297,8 +1304,54 @@ const setActiveCtrl = (ele, type = 'dimension') => {
|
||||
activeChild.value.push(ele)
|
||||
}
|
||||
|
||||
const setActiveShift = (ele, type = 'dimension') => {
|
||||
const activeChild = type === 'dimension' ? activeDimension : activeQuota
|
||||
const deactivateChild = type === 'quota' ? activeDimension : activeQuota
|
||||
const dataArr = type === 'dimension' ? dimensionData : quotaData
|
||||
deactivateChild.value = []
|
||||
const dimensionDataId = dataArr.value.map(ele => ele.id)
|
||||
const dimensionDataActiveChild = activeChild.value.filter(ele => dimensionDataId.includes(ele.id))
|
||||
if (!dimensionDataActiveChild.length) {
|
||||
const index = activeChild.value.findIndex(item => item.id === ele.id)
|
||||
if (index !== -1) {
|
||||
activeChild.value.splice(index, 1)
|
||||
return
|
||||
}
|
||||
activeChild.value.push(ele)
|
||||
} else {
|
||||
const startItx = dataArr.value.findIndex(
|
||||
item => item.id === dimensionDataActiveChild[dimensionDataActiveChild.length - 1].id
|
||||
)
|
||||
const endItx = dataArr.value.findIndex(item => item.id === ele.id)
|
||||
if (startItx === endItx) return
|
||||
if (startItx > endItx) {
|
||||
activeChild.value = [...activeChild.value, ...dataArr.value.slice(endItx, startItx)]
|
||||
}
|
||||
if (startItx < endItx) {
|
||||
activeChild.value = [...activeChild.value, ...dataArr.value.slice(startItx + 1, endItx + 1)]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const isDrag = ref(false)
|
||||
|
||||
const dragStartD = (e: DragEvent) => {
|
||||
isDrag.value = true
|
||||
setTimeout(() => {
|
||||
isDraggingItem.value = true
|
||||
}, 0)
|
||||
}
|
||||
|
||||
const singleDragStartD = (e: DragEvent, ele, type) => {
|
||||
const activeChild = type === 'dimension' ? activeDimension : activeQuota
|
||||
const deactivateChild = type === 'quota' ? activeDimension : activeQuota
|
||||
deactivateChild.value = []
|
||||
if (!activeChild.value.length) {
|
||||
activeChild.value = [unref(ele)]
|
||||
}
|
||||
startToMove(e, unref(activeDimension.value))
|
||||
}
|
||||
|
||||
const dragStart = (e: DragEvent) => {
|
||||
isDrag.value = true
|
||||
setTimeout(() => {
|
||||
@ -1313,7 +1366,6 @@ const singleDragStart = (e: DragEvent, ele, type) => {
|
||||
if (!activeChild.value.length) {
|
||||
activeChild.value = [ele]
|
||||
}
|
||||
dragStart(e)
|
||||
}
|
||||
|
||||
const dragEnd = () => {
|
||||
@ -2187,11 +2239,12 @@ const drop = (ev: MouseEvent, type = 'xAxis') => {
|
||||
@click.ctrl="setActiveCtrl(element)"
|
||||
@click.meta="setActiveCtrl(element)"
|
||||
@click.exact="setActive(element)"
|
||||
@dragstart="$event => singleDragStart($event, element, 'dimension')"
|
||||
@click.shift="setActiveShift(element)"
|
||||
@dragstart="$event => singleDragStartD($event, element, 'dimension')"
|
||||
:draggable="true"
|
||||
@dragend="singleDragEnd"
|
||||
class="item"
|
||||
v-for="element in state.dimensionData"
|
||||
v-for="element in dimensionData"
|
||||
:key="element.id"
|
||||
>
|
||||
<div
|
||||
@ -2241,7 +2294,7 @@ const drop = (ev: MouseEvent, type = 'xAxis') => {
|
||||
</el-dropdown>
|
||||
</div>
|
||||
<div
|
||||
@dragstart="dragStart"
|
||||
@dragstart="dragStartD"
|
||||
:draggable="true"
|
||||
@dragend="dragEnd"
|
||||
v-if="activeDimension.map(itx => itx.id).includes(element.id)"
|
||||
@ -2314,6 +2367,7 @@ const drop = (ev: MouseEvent, type = 'xAxis') => {
|
||||
@click.ctrl="setActiveCtrl(element, 'quota')"
|
||||
@click.meta="setActiveCtrl(element, 'quota')"
|
||||
@click.exact="setActive(element, 'quota')"
|
||||
@click.shift="setActiveShift(element, 'quota')"
|
||||
class="item"
|
||||
@dragstart="$event => singleDragStart($event, element, 'quota')"
|
||||
:draggable="true"
|
||||
|
Loading…
Reference in New Issue
Block a user