feat: 新增动态接口过滤器功能

This commit is contained in:
奔跑的面条
2022-07-05 21:44:16 +08:00
parent 2ee2783a9c
commit 47f6fc87c7
20 changed files with 541 additions and 107 deletions
@@ -1,10 +1,7 @@
<template>
<div class="go-chart-configurations-data-ajax">
<setting-item-box name="类型" :alone="true">
<n-select
v-model:value="targetData.data.requestHttpType"
:options="selectOptions"
/>
<n-select v-model:value="targetData.data.requestHttpType" :options="selectOptions" />
</setting-item-box>
<setting-item-box name="源地址:" :alone="true">
@@ -23,19 +20,26 @@
<ul class="go-pl-0">
开发环境使用 mock 数据请输入
<li v-for="item in apiList" :key="item.value">
<n-text type="info"> {{item.value}} </n-text>
<n-text type="info"> {{ item.value }} </n-text>
</li>
</ul>
</n-tooltip>
</template>
<n-input
v-model:value.trim="targetData.data.requestUrl"
:min="1"
placeholder="请输入地址(去除源)"
/>
<n-input v-model:value.trim="targetData.data.requestUrl" :min="1" placeholder="请输入地址(去除源)" />
</setting-item-box>
<setting-item-box name="测试" :alone="true">
<setting-item-box :alone="true">
<template #name>
测试
<n-tooltip trigger="hover">
<template #trigger>
<n-icon size="21" :depth="3">
<help-outline-icon></help-outline-icon>
</n-icon>
</template>
默认赋值给 dataset 字段
</n-tooltip>
</template>
<n-space>
<n-button @click="sendHandle">
<template #icon>
@@ -48,17 +52,13 @@
</n-space>
</setting-item-box>
<chart-data-matching-and-show :show="showMatching && !loading" :ajax="true"></chart-data-matching-and-show>
<go-skeleton :load="loading" :repeat="3"></go-skeleton>
<chart-data-matching-and-show
v-show="showMatching && !loading"
:ajax="true"
></chart-data-matching-and-show>
</div>
</template>
<script setup lang="ts">
import { ref, toRefs } from 'vue'
import { ref, toRefs, onBeforeUnmount, watchEffect } from 'vue'
import { icon } from '@/plugins'
import { SettingItemBox } from '@/components/Pages/ChartItemSetting'
import { RequestHttpEnum, ResultEnum } from '@/enums/httpEnum'
@@ -67,56 +67,56 @@ import { http } from '@/api/http'
import { SelectHttpType } from '../../index.d'
import { ChartDataMatchingAndShow } from '../ChartDataMatchingAndShow'
import { useTargetData } from '../../../hooks/useTargetData.hook'
import { isDev } from '@/utils'
import { isDev, newFunctionHandle } from '@/utils'
const { HelpOutlineIcon, FlashIcon } = icon.ionicons5
const { targetData, chartEditStore } = useTargetData()
const { requestOriginUrl } = toRefs(chartEditStore.getRequestGlobalConfig)
// 是否展示数据分析
const loading = ref(false)
const showMatching = ref(false)
let lastFilter: any = undefined
const apiList = [
{
value: `【图表】${ chartDataUrl }`
value: `【图表】${chartDataUrl}`
},
{
value: `【文本】${ textUrl }`
value: `【文本】${textUrl}`
},
{
value: `【0~100 整数】${ numberIntUrl }`
value: `【0~100 整数】${numberIntUrl}`
},
{
value: `【0~1小数】${ numberFloatUrl }`
value: `【0~1小数】${numberFloatUrl}`
},
{
value: `【图片地址】${ imageUrl }`
value: `【图片地址】${imageUrl}`
},
{
value: `【排名列表】${ rankListUrl }`
value: `【排名列表】${rankListUrl}`
},
{
value: `【滚动表格】${ scrollBoardUrl }`
},
value: `【滚动表格】${scrollBoardUrl}`
}
]
// 选项
const selectOptions: SelectHttpType[] = [
{
label: RequestHttpEnum.GET,
value: RequestHttpEnum.GET,
value: RequestHttpEnum.GET
},
{
label: RequestHttpEnum.POST,
value: RequestHttpEnum.POST,
},
value: RequestHttpEnum.POST
}
]
// 发送请求
const sendHandle = async () => {
loading.value = true
if(!targetData.value) return
const { requestUrl, requestHttpType } = targetData.value.data
if (!requestUrl) {
window['$message'].warning('请求参数不正确,请检查!')
@@ -124,17 +124,26 @@ const sendHandle = async () => {
}
const completePath = requestOriginUrl && requestOriginUrl.value + requestUrl
const res = await http(requestHttpType)(completePath || '', {})
setTimeout(() => {
loading.value = false
if (res.status === ResultEnum.SUCCESS) {
// @ts-ignore
targetData.value.option.dataset = res.data
showMatching.value = true
return
}
window['$message'].warning('数据异常,请检查接口数据!')
}, 500)
loading.value = false
if (res.status === ResultEnum.SUCCESS) {
targetData.value.option.dataset = newFunctionHandle(res.data, targetData.value.filter)
showMatching.value = true
return
}
window['$message'].warning('数据异常,请检查接口数据!')
}
watchEffect(() => {
const filter = targetData.value?.filter
if (lastFilter !== filter) {
lastFilter = filter
sendHandle()
}
})
onBeforeUnmount(() => {
lastFilter = null
})
</script>
<style lang="scss" scoped>
@@ -1,6 +1,6 @@
<template>
<n-timeline class="go-chart-configurations-timeline">
<n-timeline-item v-if="isCharts && dimensionsAndSource" type="info" :title="TimelineTitleEnum.MAPPING">
<n-timeline-item v-show="isCharts && dimensionsAndSource" type="info" :title="TimelineTitleEnum.MAPPING">
<n-table striped>
<thead>
<tr>
@@ -25,6 +25,12 @@
</tbody>
</n-table>
</n-timeline-item>
<n-timeline-item v-show="filterShow" color="#97846c" :title="TimelineTitleEnum.FILTER">
<n-space vertical>
<n-text depth="3">点击{{ targetData.filter ? '「编辑」' : '「新增」' }}查看过滤规则</n-text>
<chart-data-monaco-editor></chart-data-monaco-editor>
</n-space>
</n-timeline-item>
<n-timeline-item type="success" :title="TimelineTitleEnum.CONTENT">
<n-space vertical>
<n-text depth="3">ECharts 图表需符合 ECharts-setdata 数据规范</n-text>
@@ -55,7 +61,7 @@
</template>
下载
</n-button>
<n-tooltip trigger="hover">
<n-tooltip trigger="hover">
<template #trigger>
<n-icon class="go-ml-1" size="21" :depth="3">
<help-outline-icon></help-outline-icon>
@@ -65,8 +71,8 @@
</n-tooltip>
</div>
</n-space>
<n-card>
<n-code :code="getSource" language="json"></n-code>
<n-card size="small">
<n-code :code="filterRes(getSource)" language="json"></n-code>
</n-card>
</n-space>
</n-timeline-item>
@@ -76,14 +82,21 @@
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import { CreateComponentType, PackagesCategoryEnum } from '@/packages/index.d'
import { RequestDataTypeEnum } from '@/enums/httpEnum'
import { icon } from '@/plugins'
import { DataResultEnum, TimelineTitleEnum } from '../../index.d'
import { ChartDataMonacoEditor } from '../ChartDataMonacoEditor'
import { useFile } from '../../hooks/useFile.hooks'
import { useTargetData } from '../../../hooks/useTargetData.hook'
import isObject from 'lodash/isObject'
const { targetData } = useTargetData()
import cloneDeep from 'lodash/cloneDeep'
const { targetData } = useTargetData()
const props = defineProps({
show: {
type: Boolean,
required: false
},
ajax: {
type: Boolean,
required: true
@@ -102,6 +115,16 @@ const dimensionsAndSource = ref()
const { uploadFileListRef, customRequest, beforeUpload, download } = useFile(targetData)
// 是否展示过滤器
const filterShow = computed(() => {
return targetData.value.data.requestDataType === RequestDataTypeEnum.AJAX
})
// 字符串处理
const dataToString = (str: any) => {
return isObject(str) ? JSON.stringify(str) : str
}
// 是图表类型
const isCharts = computed(() => {
return targetData.value.chartConfig.package === PackagesCategoryEnum.CHARTS
@@ -129,47 +152,68 @@ const dimensionsAndSourceHandle = () => {
try {
// 去除首项数据轴标识
return dimensions.value.map((dimensionsItem: string, index: number) => {
return index === 0 ?
{
// 字段
field: '通用标识',
// 映射
mapping: dimensionsItem,
// 结果
result: DataResultEnum.NULL
} : {
field: `数据项-${index}`,
mapping: dimensionsItem,
result: matchingHandle(dimensionsItem)
}
return index === 0
? {
// 字段
field: '通用标识',
// 映射
mapping: dimensionsItem,
// 结果
result: DataResultEnum.NULL
}
: {
field: `数据项-${index}`,
mapping: dimensionsItem,
result: matchingHandle(dimensionsItem)
}
})
} catch (error) {
return []
}
}
watch(() => targetData.value?.option?.dataset, (newData: {
source: any,
dimensions: any
} | null) => {
if (newData && isObject(newData)) {
// 只有 Echarts 数据才有对应的格式
source.value = isCharts.value ? newData.source : newData
if (isCharts.value) {
dimensions.value = newData.dimensions
dimensionsAndSource.value = dimensionsAndSourceHandle()
// 过滤结果
const filterRes = (data: any) => {
try {
if(targetData.value.filter) {
const fn = new Function('data', targetData.value.filter)
const res = fn(cloneDeep(data))
return dataToString(res)
}
} else {
dimensionsAndSource.value = null
source.value = newData
return data
} catch (error) {
return '过滤函数错误'
}
}, {
immediate: true
})
}
watch(
() => targetData.value?.option?.dataset,
(
newData: {
source: any
dimensions: any
} | null
) => {
if (newData && isObject(newData)) {
// 只有 Echarts 数据才有对应的格式
source.value = newData
if (isCharts.value) {
dimensions.value = newData.dimensions
dimensionsAndSource.value = dimensionsAndSourceHandle()
}
} else {
dimensionsAndSource.value = null
source.value = newData
}
},
{
immediate: true
}
)
</script>
<style lang="scss" scoped>
@include go("chart-configurations-timeline") {
@include go('chart-configurations-timeline') {
@include deep() {
pre {
white-space: pre-wrap;
@@ -0,0 +1,3 @@
import ChartDataMonacoEditor from './index.vue'
export { ChartDataMonacoEditor }
@@ -0,0 +1,195 @@
<template>
<template v-if="targetData.filter">
<n-card>
<p><span class="func-keyword">function</span>&nbsp;&nbsp;filter(data)&nbsp;&nbsp;{</p>
<!-- 函数体 -->
<div class="go-ml-4">
<n-code :code="targetData.filter" language="typescript"></n-code>
</div>
<p>}</p>
<template #footer>
<n-space justify="end">
<n-button tertiary size="small" @click="delFilter">
<template #icon>
<n-icon>
<trash-icon />
</n-icon>
</template>
删除
</n-button>
<n-button type="info" tertiary size="small" @click="addFilter">
<template #icon>
<n-icon>
<pencil-icon />
</n-icon>
</template>
编辑
</n-button>
</n-space>
</template>
</n-card>
</template>
<template v-else>
<n-space justify="space-around">
<n-button @click="addFilter">
<template #icon>
<n-icon>
<filter-icon />
</n-icon>
</template>
新增过滤器
</n-button>
</n-space>
</template>
<!-- 弹窗 -->
<n-modal class="go-chart-data-monaco-editor" v-model:show="showModal" :mask-closable="false">
<n-card :bordered="false" role="dialog" size="small" aria-modal="true" style="width: 1000px; height: 600px">
<template #header>
<n-space>
<n-text>过滤器函数编辑器</n-text>
</n-space>
</template>
<template #header-extra> </template>
<n-space size="small" vertical>
<n-space justify="space-between">
<div>
<n-space vertical>
<n-text depth="3">// 数据参数 => data </n-text>
<p><span class="func-keyword">function</span>&nbsp;&nbsp;filter(data)&nbsp;&nbsp;{</p>
<monaco-editor width="460px" height="380px" v-model:modelValue="filter" language="json" />
<p>}</p>
</n-space>
</div>
<n-divider vertical style="height: 480px" />
<n-scrollbar style="max-height: 480px">
<n-space :size="15" vertical>
<div class="editor-data-show">
<n-space>
<n-text depth="3">目标数据</n-text>
<n-code
:code="dataToString(targetData.option.dataset)"
language="typescript"
:word-wrap="true"
></n-code>
</n-space>
</div>
<div class="editor-data-show">
<n-space>
<n-text depth="3">过滤器结果</n-text>
<n-code :code="filterRes" language="typescript" :word-wrap="true"></n-code>
</n-space>
</div>
</n-space>
</n-scrollbar>
</n-space>
</n-space>
<template #action>
<n-space justify="space-between">
<div class="go-flex-items-center">
<n-tag :bordered="false" type="success">
<template #icon>
<n-icon :component="DocumentTextIcon" />
</template>
规则
</n-tag>
<n-text class="go-ml-2" depth="3">过滤器将对接口返回值的data字段进行处理</n-text>
</div>
<n-space>
<n-button size="medium" @click="closeFilter">取消</n-button>
<n-button size="medium" type="primary" @click="saveFilter">保存</n-button>
</n-space>
</n-space>
</template>
</n-card>
</n-modal>
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue'
import { MonacoEditor } from '@/components/Pages/MonacoEditor'
import { useTargetData } from '../../../hooks/useTargetData.hook'
import { icon } from '@/plugins'
import { goDialog, isString } from '@/utils'
import cloneDeep from 'lodash/cloneDeep'
const { PencilIcon, TrashIcon, FilterIcon, DocumentTextIcon } = icon.ionicons5
const { targetData, chartEditStore } = useTargetData()
// 受控弹窗
const showModal = ref(false)
// filter 函数模板
const filter = ref(targetData.value.filter || `return data`)
// 过滤错误标识
const errorFlag = ref(false)
// 字符串处理
const dataToString = (str: any) => {
return isString(str) ? str : JSON.stringify(str)
}
// 过滤结果
const filterRes = computed(() => {
try {
const fn = new Function('data', filter.value)
const res = fn(cloneDeep(targetData.value.option.dataset))
errorFlag.value = false
return JSON.stringify(res)
} catch (error) {
errorFlag.value = true
return '过滤函数错误'
}
})
// 新增过滤器
const addFilter = () => {
showModal.value = true
}
// 删除过滤器
const delFilter = () => {
goDialog({
message: '是否删除过滤器',
onPositiveCallback: () => {
targetData.value.filter = undefined
}
})
}
// 关闭过滤器
const closeFilter = () => {
showModal.value = false
}
// 新增过滤器
const saveFilter = () => {
if (errorFlag.value) {
window['$message'].error('过滤函数错误,无法进行保存')
return
}
targetData.value.filter = filter.value
closeFilter()
}
// 执行过滤处理
const filterData = (data: any) => {}
</script>
<style lang="scss" scoped>
.func-keyword {
color: #b478cf;
}
@include go('chart-data-monaco-editor') {
&.n-card.n-modal,
.n-card {
@extend .go-background-filter;
}
.editor-data-show {
@include fetch-bg-color('filter-color');
width: 420px;
padding: 20px;
border-radius: 5px;
}
}
</style>
@@ -1,6 +1,6 @@
<template>
<div class="go-chart-configurations-data-static">
<chart-data-matching-and-show :ajax="false"></chart-data-matching-and-show>
<chart-data-matching-and-show :show="false" :ajax="false"></chart-data-matching-and-show>
</div>
</template>
@@ -8,6 +8,7 @@ export enum DataResultEnum {
}
export enum TimelineTitleEnum {
FILTER = '数据过滤',
MAPPING = '数据映射',
CONTENT = '数据内容',
}