Showing
44 changed files
with
825 additions
and
289 deletions
| 1 | -import axios, { AxiosResponse, AxiosRequestConfig } from 'axios' | |
| 1 | +import axios, { AxiosResponse, InternalAxiosRequestConfig, AxiosError } from 'axios' | |
| 2 | 2 | import { ResultEnum } from "@/enums/httpEnum" |
| 3 | 3 | import { ErrorPageNameMap } from "@/enums/pageEnum" |
| 4 | 4 | import { redirectErrorPage } from '@/utils' |
| ... | ... | @@ -9,10 +9,10 @@ const axiosInstance = axios.create({ |
| 9 | 9 | }) |
| 10 | 10 | |
| 11 | 11 | axiosInstance.interceptors.request.use( |
| 12 | - (config: AxiosRequestConfig) => { | |
| 12 | + (config: InternalAxiosRequestConfig) => { | |
| 13 | 13 | return config |
| 14 | 14 | }, |
| 15 | - (error: AxiosRequestConfig) => { | |
| 15 | + (error: AxiosError) => { | |
| 16 | 16 | Promise.reject(error) |
| 17 | 17 | } |
| 18 | 18 | ) |
| ... | ... | @@ -21,7 +21,7 @@ axiosInstance.interceptors.request.use( |
| 21 | 21 | axiosInstance.interceptors.response.use( |
| 22 | 22 | (res: AxiosResponse) => { |
| 23 | 23 | const { code } = res.data as { code: number } |
| 24 | - if (code === undefined || code === null) return Promise.resolve(res) | |
| 24 | + if (code === undefined || code === null) return Promise.resolve(res.data) | |
| 25 | 25 | if (code === ResultEnum.DATA_SUCCESS) return Promise.resolve(res.data) |
| 26 | 26 | // 重定向 |
| 27 | 27 | if (ErrorPageNameMap.get(code)) redirectErrorPage(code) | ... | ... |
| ... | ... | @@ -391,8 +391,12 @@ const visualMap = computed(() => { |
| 391 | 391 | // 监听legend color颜色改变type = scroll的颜色 |
| 392 | 392 | watch(() => legend.value && legend.value.textStyle.color, (newVal) => { |
| 393 | 393 | if (legend.value && newVal) { |
| 394 | - legend.value.pageTextStyle.color = newVal | |
| 395 | - } | |
| 394 | + if (!legend.value.pageTextStyle) { | |
| 395 | + legend.value.pageTextStyle = { color: newVal } | |
| 396 | + } else { | |
| 397 | + legend.value.pageTextStyle.color = newVal | |
| 398 | + } | |
| 399 | + } | |
| 396 | 400 | }, { |
| 397 | 401 | immediate: true, |
| 398 | 402 | deep: true, | ... | ... |
| ... | ... | @@ -69,6 +69,22 @@ |
| 69 | 69 | </setting-item> |
| 70 | 70 | </setting-item-box> |
| 71 | 71 | |
| 72 | + <!-- 预设滤镜 --> | |
| 73 | + <div v-if="presetImageList.length" class="preset-filter"> | |
| 74 | + <n-image | |
| 75 | + class="preset-img" | |
| 76 | + width="46" | |
| 77 | + preview-disabled | |
| 78 | + object-fit="scale-down" | |
| 79 | + v-for="(item, index) in presetImageList" | |
| 80 | + :key="index" | |
| 81 | + :class="{ 'active-preset': item.hueRotate === chartStyles.hueRotate }" | |
| 82 | + :style="{ filter: `hue-rotate(${item.hueRotate}deg)` }" | |
| 83 | + :src="item.src" | |
| 84 | + @click="() => (chartStyles.hueRotate = item.hueRotate)" | |
| 85 | + ></n-image> | |
| 86 | + </div> | |
| 87 | + | |
| 72 | 88 | <!-- 混合模式 --> |
| 73 | 89 | <setting-item-box v-if="!isCanvas" :alone="true"> |
| 74 | 90 | <template #name> |
| ... | ... | @@ -149,10 +165,12 @@ |
| 149 | 165 | </template> |
| 150 | 166 | |
| 151 | 167 | <script setup lang="ts"> |
| 152 | -import { PropType } from 'vue' | |
| 168 | +import { ref, PropType } from 'vue' | |
| 153 | 169 | import { PickCreateComponentType, BlendModeEnumList } from '@/packages/index.d' |
| 154 | 170 | import { SettingItemBox, SettingItem, CollapseItem } from '@/components/Pages/ChartItemSetting' |
| 155 | 171 | import { icon } from '@/plugins' |
| 172 | +import logoImg from '@/assets/logo.png' | |
| 173 | +import { useDesignStore } from '@/store/modules/designStore/designStore' | |
| 156 | 174 | |
| 157 | 175 | const props = defineProps({ |
| 158 | 176 | isGroup: { |
| ... | ... | @@ -175,10 +193,48 @@ const { HelpOutlineIcon } = icon.ionicons5 |
| 175 | 193 | const sliderFormatTooltip = (v: string) => { |
| 176 | 194 | return `${(parseFloat(v) * 100).toFixed(0)}%` |
| 177 | 195 | } |
| 196 | + | |
| 178 | 197 | // 角度格式化 |
| 179 | 198 | const degFormatTooltip = (v: string) => { |
| 180 | 199 | return `${v}deg` |
| 181 | 200 | } |
| 201 | + | |
| 202 | +// 预设滤镜 | |
| 203 | +interface presetImageData { | |
| 204 | + index: number | |
| 205 | + src: string | |
| 206 | + hueRotate: number | |
| 207 | +} | |
| 208 | + | |
| 209 | +const presetImageList = ref([] as presetImageData[]) | |
| 210 | +for (let i = 1; i <= 12; i++) { | |
| 211 | + presetImageList.value.push({ | |
| 212 | + index: i, | |
| 213 | + src: logoImg, | |
| 214 | + hueRotate: i * 30 | |
| 215 | + }) | |
| 216 | +} | |
| 182 | 217 | </script> |
| 183 | 218 | |
| 184 | -<style lang="scss" scoped></style> | |
| 219 | +<style lang="scss" scoped> | |
| 220 | +// 预设滤镜 | |
| 221 | +.preset-filter { | |
| 222 | + margin: 20px 0 10px 0; | |
| 223 | + display: flex; | |
| 224 | + flex-wrap: wrap; | |
| 225 | + justify-content: space-between; | |
| 226 | + .preset-img { | |
| 227 | + margin-bottom: 10px; | |
| 228 | + padding: 2px; | |
| 229 | + border-radius: 6px; | |
| 230 | + transition: 0.2s all; | |
| 231 | + cursor: pointer; | |
| 232 | + &:hover { | |
| 233 | + box-shadow: 0 0 0 2px #66a9c9; | |
| 234 | + } | |
| 235 | + } | |
| 236 | + .active-preset { | |
| 237 | + box-shadow: 0 0 0 2px #66a9c9; | |
| 238 | + } | |
| 239 | +} | |
| 240 | +</style> | ... | ... |
| ... | ... | @@ -209,7 +209,7 @@ $lineColor: #4a9ef8; |
| 209 | 209 | &.down.go .front:before { |
| 210 | 210 | transform-origin: 50% 100%; |
| 211 | 211 | animation: frontFlipDown $speed ease-in-out both; |
| 212 | - box-shadow: 0 -2px 6px rgba($color: $lineColor, $alpha: 0.3); | |
| 212 | + box-shadow: 0 -2px $borderWidth 0 $frontColor; | |
| 213 | 213 | backface-visibility: hidden; |
| 214 | 214 | } |
| 215 | 215 | &.down.go .back:after { |
| ... | ... | @@ -233,7 +233,7 @@ $lineColor: #4a9ef8; |
| 233 | 233 | &.up.go .front:after { |
| 234 | 234 | transform-origin: 50% 0; |
| 235 | 235 | animation: frontFlipUp $speed ease-in-out both; |
| 236 | - box-shadow: 0 2px 6px rgba($color: $lineColor, $alpha: 0.3); | |
| 236 | + box-shadow: 0 2px $borderWidth 0 $frontColor; | |
| 237 | 237 | backface-visibility: hidden; |
| 238 | 238 | } |
| 239 | 239 | &.up.go .back:before { | ... | ... |
src/enums/external/interfaceEnum.ts
0 → 100644
| ... | ... | @@ -21,7 +21,7 @@ type ChartEditStoreType = typeof useChartEditStore |
| 21 | 21 | * @param useChartEditStore 若直接引会报错,只能动态传递 |
| 22 | 22 | * @param updateCallback 自定义更新函数 |
| 23 | 23 | */ |
| 24 | -export const originUseChartDataFetch = ( | |
| 24 | +export const originUseChartDataFetch = ( | |
| 25 | 25 | targetComponent: CreateComponentType, |
| 26 | 26 | useChartEditStore: ChartEditStoreType, |
| 27 | 27 | updateCallback?: (...args: any) => any |
| ... | ... | @@ -80,11 +80,11 @@ export const originUseChartDataFetch = ( |
| 80 | 80 | if (res) { |
| 81 | 81 | try { |
| 82 | 82 | const filter = targetComponent.filter |
| 83 | - const { data } = res | |
| 84 | - echartsUpdateHandle(newFunctionHandle(data, res, filter)) | |
| 83 | + const { data } = res | |
| 84 | + echartsUpdateHandle(newFunctionHandle(data, res, filter)) | |
| 85 | 85 | // 更新回调函数 |
| 86 | 86 | if (updateCallback) { |
| 87 | - updateCallback(newFunctionHandle(data, res, filter)) | |
| 87 | + updateCallback(newFunctionHandle(data, res, filter)) | |
| 88 | 88 | } |
| 89 | 89 | } catch (error) { |
| 90 | 90 | console.error(error) |
| ... | ... | @@ -94,12 +94,12 @@ export const originUseChartDataFetch = ( |
| 94 | 94 | |
| 95 | 95 | // 普通初始化与组件交互处理监听 |
| 96 | 96 | watch( |
| 97 | - () => targetComponent.request, | |
| 97 | + () => targetComponent.request.requestParams, | |
| 98 | 98 | () => { |
| 99 | 99 | fetchFn() |
| 100 | 100 | }, |
| 101 | 101 | { |
| 102 | - immediate: true, | |
| 102 | + immediate: false, | |
| 103 | 103 | deep: true |
| 104 | 104 | } |
| 105 | 105 | ) |
| ... | ... | @@ -109,7 +109,11 @@ export const originUseChartDataFetch = ( |
| 109 | 109 | // 单位 |
| 110 | 110 | const unit = targetInterval && targetInterval.value ? targetUnit.value : globalUnit.value |
| 111 | 111 | // 开启轮询 |
| 112 | - if (time) fetchInterval = setInterval(fetchFn, intervalUnitHandle(time, unit)) | |
| 112 | + if (time) { | |
| 113 | + fetchInterval = setInterval(fetchFn, intervalUnitHandle(time, unit)) | |
| 114 | + } else { | |
| 115 | + fetchFn() | |
| 116 | + } | |
| 113 | 117 | } |
| 114 | 118 | // eslint-disable-next-line no-empty |
| 115 | 119 | } catch (error) { |
| ... | ... | @@ -118,10 +122,11 @@ export const originUseChartDataFetch = ( |
| 118 | 122 | } |
| 119 | 123 | |
| 120 | 124 | if (isPreview()) { |
| 121 | - // 判断是否是数据池类型 | |
| 122 | 125 | targetComponent.request.requestDataType === RequestDataTypeEnum.Pond |
| 123 | 126 | ? addGlobalDataInterface(targetComponent, useChartEditStore, updateCallback || echartsUpdateHandle) |
| 124 | 127 | : requestIntervalFn() |
| 128 | + } else { | |
| 129 | + requestIntervalFn() | |
| 125 | 130 | } |
| 126 | 131 | return { vChartRef } |
| 127 | 132 | } | ... | ... |
| 1 | -import { toRaw } from 'vue' | |
| 1 | +import { toRaw, watch, computed, ComputedRef } from 'vue' | |
| 2 | 2 | import { customizeHttp } from '@/api/http' |
| 3 | 3 | import { CreateComponentType } from '@/packages/index.d' |
| 4 | 4 | import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' |
| ... | ... | @@ -20,7 +20,7 @@ const mittDataPondMap = new Map<string, DataPondMapType[]>() |
| 20 | 20 | // 创建单个数据项轮询接口 |
| 21 | 21 | const newPondItemInterval = ( |
| 22 | 22 | requestGlobalConfig: RequestGlobalConfigType, |
| 23 | - requestDataPondItem: RequestDataPondItemType, | |
| 23 | + requestDataPondItem: ComputedRef<RequestDataPondItemType>, | |
| 24 | 24 | dataPondMapItem?: DataPondMapType[] |
| 25 | 25 | ) => { |
| 26 | 26 | if (!dataPondMapItem) return |
| ... | ... | @@ -31,8 +31,7 @@ const newPondItemInterval = ( |
| 31 | 31 | // 请求 |
| 32 | 32 | const fetchFn = async () => { |
| 33 | 33 | try { |
| 34 | - const res = await customizeHttp(toRaw(requestDataPondItem.dataPondRequestConfig), toRaw(requestGlobalConfig)) | |
| 35 | - | |
| 34 | + const res = await customizeHttp(toRaw(requestDataPondItem.value.dataPondRequestConfig), toRaw(requestGlobalConfig)) | |
| 36 | 35 | if (res) { |
| 37 | 36 | try { |
| 38 | 37 | // 遍历更新回调函数 |
| ... | ... | @@ -49,19 +48,32 @@ const newPondItemInterval = ( |
| 49 | 48 | } |
| 50 | 49 | } |
| 51 | 50 | |
| 51 | + watch( | |
| 52 | + () => requestDataPondItem.value.dataPondRequestConfig.requestParams.Params, | |
| 53 | + () => { | |
| 54 | + fetchFn() | |
| 55 | + }, | |
| 56 | + { | |
| 57 | + immediate: false, | |
| 58 | + deep: true | |
| 59 | + } | |
| 60 | + ) | |
| 61 | + | |
| 62 | + | |
| 52 | 63 | // 立即调用 |
| 53 | 64 | fetchFn() |
| 54 | 65 | |
| 55 | - const targetInterval = requestDataPondItem.dataPondRequestConfig.requestInterval | |
| 56 | - const targetUnit = requestDataPondItem.dataPondRequestConfig.requestIntervalUnit | |
| 66 | + | |
| 67 | + const targetInterval = requestDataPondItem.value.dataPondRequestConfig.requestInterval | |
| 68 | + const targetUnit = requestDataPondItem.value.dataPondRequestConfig.requestIntervalUnit | |
| 57 | 69 | |
| 58 | 70 | const globalRequestInterval = requestGlobalConfig.requestInterval |
| 59 | 71 | const globalUnit = requestGlobalConfig.requestIntervalUnit |
| 60 | 72 | |
| 61 | 73 | // 定时时间 |
| 62 | - const time = targetInterval ? targetInterval : globalRequestInterval | |
| 74 | + const time = targetInterval ? targetInterval : globalRequestInterval | |
| 63 | 75 | // 单位 |
| 64 | - const unit = targetInterval ? targetUnit : globalUnit | |
| 76 | + const unit = targetInterval ? targetUnit : globalUnit | |
| 65 | 77 | // 开启轮询 |
| 66 | 78 | if (time) fetchInterval = setInterval(fetchFn, intervalUnitHandle(time, unit)) |
| 67 | 79 | } |
| ... | ... | @@ -96,13 +108,16 @@ export const useChartDataPondFetch = () => { |
| 96 | 108 | } |
| 97 | 109 | |
| 98 | 110 | // 初始化数据池 |
| 99 | - const initDataPond = (requestGlobalConfig: RequestGlobalConfigType) => { | |
| 100 | - const { requestDataPond } = requestGlobalConfig | |
| 111 | + const initDataPond = (useChartEditStore: ChartEditStoreType) => { | |
| 112 | + const { requestGlobalConfig } = useChartEditStore() | |
| 113 | + const chartEditStore = useChartEditStore() | |
| 101 | 114 | // 根据 mapId 查找对应的数据池配置 |
| 102 | 115 | for (let pondKey of mittDataPondMap.keys()) { |
| 103 | - const requestDataPondItem = requestDataPond.find(item => item.dataPondId === pondKey) | |
| 116 | + const requestDataPondItem = computed(() => { | |
| 117 | + return requestGlobalConfig.requestDataPond.find(item => item.dataPondId === pondKey) | |
| 118 | + }) as ComputedRef<RequestDataPondItemType> | |
| 104 | 119 | if (requestDataPondItem) { |
| 105 | - newPondItemInterval(requestGlobalConfig, requestDataPondItem, mittDataPondMap.get(pondKey)) | |
| 120 | + newPondItemInterval(chartEditStore.requestGlobalConfig, requestDataPondItem, mittDataPondMap.get(pondKey)) | |
| 106 | 121 | } |
| 107 | 122 | } |
| 108 | 123 | } | ... | ... |
| 1 | 1 | import { toRefs } from 'vue' |
| 2 | +import { isPreview } from '@/utils' | |
| 2 | 3 | import { CreateComponentType } from '@/packages/index.d' |
| 3 | 4 | import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' |
| 4 | 5 | |
| ... | ... | @@ -12,6 +13,7 @@ export const useChartInteract = ( |
| 12 | 13 | param: { [T: string]: any }, |
| 13 | 14 | interactEventOn: string |
| 14 | 15 | ) => { |
| 16 | + if (!isPreview()) return | |
| 15 | 17 | const chartEditStore = useChartEditStore() |
| 16 | 18 | const { interactEvents } = chartConfig.events |
| 17 | 19 | const fnOnEvent = interactEvents.filter(item => { |
| ... | ... | @@ -20,20 +22,37 @@ export const useChartInteract = ( |
| 20 | 22 | |
| 21 | 23 | if (fnOnEvent.length === 0) return |
| 22 | 24 | fnOnEvent.forEach(item => { |
| 23 | - const index = chartEditStore.fetchTargetIndex(item.interactComponentId) | |
| 24 | - if (index === -1) return | |
| 25 | - const { Params, Header } = toRefs(chartEditStore.componentList[index].request.requestParams) | |
| 26 | - Object.keys(item.interactFn).forEach(key => { | |
| 27 | - if (Params.value[key]) { | |
| 28 | - Params.value[key] = param[item.interactFn[key]] | |
| 29 | - } | |
| 30 | - if (Header.value[key]) { | |
| 31 | - Header.value[key] = param[item.interactFn[key]] | |
| 32 | - } | |
| 33 | - }) | |
| 25 | + | |
| 26 | + const globalConfigPindAprndex = chartEditStore.requestGlobalConfig.requestDataPond.findIndex(cItem => | |
| 27 | + cItem.dataPondId === item.interactComponentId | |
| 28 | + ) | |
| 29 | + if (globalConfigPindAprndex !== -1) { | |
| 30 | + const { Params, Header } = toRefs(chartEditStore.requestGlobalConfig.requestDataPond[globalConfigPindAprndex].dataPondRequestConfig.requestParams) | |
| 31 | + | |
| 32 | + Object.keys(item.interactFn).forEach(key => { | |
| 33 | + if (key in Params.value) { | |
| 34 | + Params.value[key] = param[item.interactFn[key]] | |
| 35 | + } | |
| 36 | + if (key in Header.value) { | |
| 37 | + Header.value[key] = param[item.interactFn[key]] | |
| 38 | + } | |
| 39 | + }) | |
| 40 | + } else { | |
| 41 | + const index = chartEditStore.fetchTargetIndex(item.interactComponentId) | |
| 42 | + if (index === -1) return | |
| 43 | + const { Params, Header } = toRefs(chartEditStore.componentList[index].request.requestParams) | |
| 44 | + | |
| 45 | + Object.keys(item.interactFn).forEach(key => { | |
| 46 | + if (key in Params.value) { | |
| 47 | + Params.value[key] = param[item.interactFn[key]] | |
| 48 | + } | |
| 49 | + if (key in Header.value) { | |
| 50 | + Header.value[key] = param[item.interactFn[key]] | |
| 51 | + } | |
| 52 | + }) | |
| 53 | + } | |
| 34 | 54 | }) |
| 35 | 55 | } |
| 36 | - | |
| 37 | 56 | // 联动事件触发的 type 变更时,清除当前绑定内容 |
| 38 | 57 | export const clearInteractEvent = (chartConfig: CreateComponentType) => { |
| 39 | 58 | ... | ... |
| ... | ... | @@ -54,7 +54,7 @@ export const usePreviewFitScale = ( |
| 54 | 54 | window.addEventListener('resize', resize) |
| 55 | 55 | } |
| 56 | 56 | |
| 57 | - // * 改变窗口大小重新绘制 | |
| 57 | + // * 卸载监听 | |
| 58 | 58 | const unWindowResize = () => { |
| 59 | 59 | window.removeEventListener('resize', resize) |
| 60 | 60 | } |
| ... | ... | @@ -106,7 +106,7 @@ export const usePreviewScrollYScale = ( |
| 106 | 106 | window.addEventListener('resize', resize) |
| 107 | 107 | } |
| 108 | 108 | |
| 109 | - // * 改变窗口大小重新绘制 | |
| 109 | + // * 卸载监听 | |
| 110 | 110 | const unWindowResize = () => { |
| 111 | 111 | window.removeEventListener('resize', resize) |
| 112 | 112 | } |
| ... | ... | @@ -158,7 +158,7 @@ export const usePreviewScrollXScale = ( |
| 158 | 158 | window.addEventListener('resize', resize) |
| 159 | 159 | } |
| 160 | 160 | |
| 161 | - // * 改变窗口大小重新绘制 | |
| 161 | + // * 卸载监听 | |
| 162 | 162 | const unWindowResize = () => { |
| 163 | 163 | window.removeEventListener('resize', resize) |
| 164 | 164 | } |
| ... | ... | @@ -205,7 +205,7 @@ export const usePreviewFullScale = ( |
| 205 | 205 | window.addEventListener('resize', resize) |
| 206 | 206 | } |
| 207 | 207 | |
| 208 | - // * 改变窗口大小重新绘制 | |
| 208 | + // * 卸载监听 | |
| 209 | 209 | const unWindowResize = () => { |
| 210 | 210 | window.removeEventListener('resize', resize) |
| 211 | 211 | } | ... | ... |
| 1 | 1 | <template> |
| 2 | 2 | <router-view> |
| 3 | 3 | <template #default="{ Component, route }"> |
| 4 | - <component | |
| 5 | - v-if="route.noKeepAlive" | |
| 6 | - :is="Component" | |
| 7 | - :key="route.fullPath" | |
| 8 | - ></component> | |
| 4 | + <component v-if="route.meta.noKeepAlive" :is="Component"></component> | |
| 9 | 5 | <keep-alive v-else> |
| 10 | - <component :is="Component" :key="route.fullPath"></component> | |
| 6 | + <component :is="Component"></component> | |
| 11 | 7 | </keep-alive> |
| 12 | 8 | </template> |
| 13 | 9 | </router-view> | ... | ... |
| ... | ... | @@ -7,6 +7,22 @@ |
| 7 | 7 | </n-input-number> |
| 8 | 8 | </SettingItem> |
| 9 | 9 | </SettingItemBox> |
| 10 | + <!-- 中心标题 --> | |
| 11 | + <SettingItemBox v-if="config.title" name="标题"> | |
| 12 | + <SettingItem name="颜色"> | |
| 13 | + <n-color-picker size="small" :modes="['hex']" v-model:value="config.title.textStyle.color"></n-color-picker> | |
| 14 | + </SettingItem> | |
| 15 | + <SettingItem name="字体大小"> | |
| 16 | + <n-input-number | |
| 17 | + v-model:value="config.title.textStyle.fontSize" | |
| 18 | + :min="0" | |
| 19 | + :step="1" | |
| 20 | + size="small" | |
| 21 | + placeholder="字体大小" | |
| 22 | + > | |
| 23 | + </n-input-number> | |
| 24 | + </SettingItem> | |
| 25 | + </SettingItemBox> | |
| 10 | 26 | <!-- Echarts 全局设置 --> |
| 11 | 27 | <SettingItemBox name="进度条"> |
| 12 | 28 | <SettingItem name="颜色"> |
| ... | ... | @@ -31,24 +47,8 @@ |
| 31 | 47 | ></n-color-picker> |
| 32 | 48 | </SettingItem> |
| 33 | 49 | </SettingItemBox> |
| 34 | - <!-- 中心标题 --> | |
| 35 | - <SettingItemBox v-if="config.title" name="标题"> | |
| 36 | - <SettingItem name="颜色"> | |
| 37 | - <n-color-picker size="small" :modes="['hex']" v-model:value="config.title.textStyle.color"></n-color-picker> | |
| 38 | - </SettingItem> | |
| 39 | - <SettingItem name="字体大小"> | |
| 40 | - <n-input-number | |
| 41 | - v-model:value="config.title.textStyle.fontSize" | |
| 42 | - :min="0" | |
| 43 | - :step="1" | |
| 44 | - size="small" | |
| 45 | - placeholder="字体大小" | |
| 46 | - > | |
| 47 | - </n-input-number> | |
| 48 | - </SettingItem> | |
| 49 | - </SettingItemBox> | |
| 50 | 50 | <!-- 其他样式 --> |
| 51 | - <SettingItemBox name="轨道样式"> | |
| 51 | + <SettingItemBox name="轨道"> | |
| 52 | 52 | <SettingItem name="颜色"> |
| 53 | 53 | <n-color-picker size="small" :modes="['hex']" v-model:value="item.data[1].itemStyle.color"></n-color-picker> |
| 54 | 54 | </SettingItem> |
| ... | ... | @@ -69,6 +69,18 @@ |
| 69 | 69 | v-model:value="item.data[1].itemStyle.shadowColor" |
| 70 | 70 | ></n-color-picker> |
| 71 | 71 | </SettingItem> |
| 72 | + <SettingItem name="轨道宽度"> | |
| 73 | + <n-select | |
| 74 | + v-model:value="item.radius[0]" | |
| 75 | + size="small" | |
| 76 | + :options="[ | |
| 77 | + { label: '窄', value: '75%' }, | |
| 78 | + { label: '中', value: '60%' }, | |
| 79 | + { label: '宽', value: '45%' }, | |
| 80 | + { label: '更宽', value: '30%' } | |
| 81 | + ]" | |
| 82 | + /> | |
| 83 | + </SettingItem> | |
| 72 | 84 | </SettingItemBox> |
| 73 | 85 | </CollapseItem> |
| 74 | 86 | </template> | ... | ... |
| ... | ... | @@ -41,7 +41,7 @@ const option = reactive({ |
| 41 | 41 | const dataHandle = (newData: any) => { |
| 42 | 42 | const d = parseFloat(`${newData}`) * 100 |
| 43 | 43 | let config = props.chartConfig.option |
| 44 | - config.title.text = d.toFixed(2) + '%' | |
| 44 | + config.title.text = `${+d.toFixed(2)}%` | |
| 45 | 45 | config.series[0].data[0].value[0] = d |
| 46 | 46 | config.series[0].data[1].value[0] = 100 - d |
| 47 | 47 | option.value = mergeTheme(props.chartConfig.option, props.themeSetting, includes) |
| ... | ... | @@ -68,7 +68,7 @@ watch( |
| 68 | 68 | useChartDataFetch(props.chartConfig, useChartEditStore, (resData: number) => { |
| 69 | 69 | let d = parseFloat(`${resData}`) * 100 |
| 70 | 70 | // @ts-ignore |
| 71 | - option.value.title.text = d.toFixed(2) + '%' | |
| 71 | + option.value.title.text = `${+d.toFixed(2)}%` | |
| 72 | 72 | // @ts-ignore |
| 73 | 73 | option.value.series[0].data[0].value[0] = d |
| 74 | 74 | // @ts-ignore | ... | ... |
| 1 | 1 | <template> |
| 2 | - <v-chart ref="vChartRef" autoresize :init-options="initOptions" :theme="themeColor" :option="option" | |
| 3 | - :manual-update="isPreview()" @mouseover="handleHighlight" @mouseout="handleDownplay"></v-chart> | |
| 2 | + <v-chart | |
| 3 | + ref="vChartRef" | |
| 4 | + autoresize | |
| 5 | + :init-options="initOptions" | |
| 6 | + :theme="themeColor" | |
| 7 | + :option="option" | |
| 8 | + :manual-update="isPreview()" | |
| 9 | + @mouseover="handleHighlight" | |
| 10 | + @mouseout="handleDownplay" | |
| 11 | + ></v-chart> | |
| 4 | 12 | </template> |
| 5 | 13 | |
| 6 | 14 | <script setup lang="ts"> |
| 7 | -import { computed, onMounted, PropType, reactive, watch } from 'vue' | |
| 15 | +import { computed, PropType, onMounted, watch } from 'vue' | |
| 8 | 16 | import VChart from 'vue-echarts' |
| 9 | 17 | import { useCanvasInitOptions } from '@/hooks/useCanvasInitOptions.hook' |
| 10 | 18 | import { use } from 'echarts/core' |
| ... | ... | @@ -125,9 +133,9 @@ watch( |
| 125 | 133 | } |
| 126 | 134 | ) |
| 127 | 135 | |
| 128 | -const { vChartRef } = useChartDataFetch(props.chartConfig, useChartEditStore) | |
| 129 | - | |
| 130 | - | |
| 136 | +const { vChartRef } = useChartDataFetch(props.chartConfig, useChartEditStore, (newData: typeof dataJson) => { | |
| 137 | + addPieInterval(newData) | |
| 138 | +}) | |
| 131 | 139 | |
| 132 | 140 | onMounted(() => { |
| 133 | 141 | seriesDataMaxLength = dataJson.source.length | ... | ... |
| ... | ... | @@ -15,11 +15,11 @@ export interface IResources { |
| 15 | 15 | const fileSuffix = ['earth', 'gradient', 'redCircle', 'label', 'aperture', 'glow', 'light_column', 'aircraft'] |
| 16 | 16 | const textures: ITextures[] = [] |
| 17 | 17 | |
| 18 | -const modules: Record<string, { default: string }> = import.meta.glob("../../images/earth/*", { eager: true }); | |
| 19 | - | |
| 20 | -for (const item in modules) { | |
| 21 | - const n = item.split('/').pop() | |
| 22 | - if (n) { | |
| 18 | +const modules: Record<string, { default: string }> = import.meta.glob("../../images/earth/*", { eager: true }) | |
| 19 | + | |
| 20 | +for(let item in modules) { | |
| 21 | + const n = item.split('/').pop() | |
| 22 | + if(n) { | |
| 23 | 23 | textures.push({ |
| 24 | 24 | name: n.split('.')[0], |
| 25 | 25 | url: modules[item].default | ... | ... |
| ... | ... | @@ -4,7 +4,7 @@ import { PublicConfigClass } from '@/packages/public' |
| 4 | 4 | import { CreateComponentType } from '@/packages/index.d' |
| 5 | 5 | import { chartInitConfig } from '@/settings/designSetting' |
| 6 | 6 | import { COMPONENT_INTERACT_EVENT_KET } from '@/enums/eventEnum' |
| 7 | -import { interactActions, ComponentInteractEventEnum } from './interact' | |
| 7 | +import { interactActions, ComponentInteractEventEnum, DefaultTypeEnum, DifferUnitEnum } from './interact' | |
| 8 | 8 | import { InputsDateConfig } from './index' |
| 9 | 9 | |
| 10 | 10 | export const option = { |
| ... | ... | @@ -12,9 +12,14 @@ export const option = { |
| 12 | 12 | [COMPONENT_INTERACT_EVENT_KET]: ComponentInteractEventEnum.DATE, |
| 13 | 13 | // 下拉展示 |
| 14 | 14 | isPanel: 0, |
| 15 | - dataset: dayjs().valueOf(), | |
| 16 | - differValue: 0 | |
| 17 | - | |
| 15 | + // 默认值 | |
| 16 | + dataset: dayjs().valueOf() as number | number[] | null, | |
| 17 | + // 默认值类型 | |
| 18 | + defaultType: DefaultTypeEnum.STATIC, | |
| 19 | + // 动态默认值偏移单位 | |
| 20 | + differUnit: [DifferUnitEnum.DAY, DifferUnitEnum.DAY], | |
| 21 | + // 动态默认值偏移值 | |
| 22 | + differValue: [0, 0] | |
| 18 | 23 | } |
| 19 | 24 | |
| 20 | 25 | export default class Config extends PublicConfigClass implements CreateComponentType { | ... | ... |
| ... | ... | @@ -8,39 +8,67 @@ |
| 8 | 8 | <collapse-item name="时间配置" :expanded="true"> |
| 9 | 9 | <setting-item-box name="基础"> |
| 10 | 10 | <setting-item name="类型"> |
| 11 | - <n-select v-model:value="optionData.componentInteractEventKey" size="small" :options="datePickerTypeOptions" /> | |
| 11 | + <n-select v-model:value="optionData.componentInteractEventKey" size="small" :options="datePickerTypeOptions" | |
| 12 | + @update:value="datePickerTypeUpdate"/> | |
| 12 | 13 | </setting-item> |
| 13 | 14 | </setting-item-box> |
| 14 | 15 | |
| 15 | - <setting-item-box name="默认值" :alone="true"> | |
| 16 | - <n-date-picker size="small" v-model:value="optionData.dataset" :type="optionData.componentInteractEventKey" /> | |
| 17 | - </setting-item-box> | |
| 16 | + <setting-item-box name="默认值"> | |
| 17 | + <setting-item name="类型"> | |
| 18 | + <n-select v-model:value="optionData.defaultType" size="small" :options="defaultTypeOptions" | |
| 19 | + @update:value="defaultTypeUpdate" /> | |
| 20 | + </setting-item> | |
| 18 | 21 | |
| 19 | - <setting-item-box :alone="true"> | |
| 22 | + </setting-item-box> | |
| 23 | + <setting-item-box v-if="optionData.defaultType === DefaultTypeEnum.STATIC" :alone="true"> | |
| 24 | + <setting-item name="静态默认值"> | |
| 25 | + <n-date-picker size="small" clearable v-model:value="optionData.dataset" :type="optionData.componentInteractEventKey" /> | |
| 26 | + </setting-item> | |
| 27 | + </setting-item-box> | |
| 28 | + <setting-item-box v-if="optionData.defaultType === DefaultTypeEnum.DYNAMIC" > | |
| 20 | 29 | <template #name> |
| 21 | - <n-text>动态</n-text> | |
| 30 | + <n-text></n-text> | |
| 22 | 31 | <n-tooltip trigger="hover"> |
| 23 | 32 | <template #trigger> |
| 24 | 33 | <n-icon size="21" :depth="3"> |
| 25 | 34 | <help-outline-icon></help-outline-icon> |
| 26 | 35 | </n-icon> |
| 27 | 36 | </template> |
| 28 | - <n-text>动态值不为0时,默认值:取当天时间相加当前值</n-text> | |
| 37 | + <span>打开页面时浏览器操作系统的系统时间+偏移量(单位)</span> | |
| 29 | 38 | </n-tooltip> |
| 30 | 39 | </template> |
| 31 | - <n-input-number v-model:value="optionData.differValue" class="input-num-width" size="small" :min="-40" :max="40"> | |
| 32 | - <template #suffix> 天 </template> | |
| 33 | - </n-input-number> | |
| 40 | + <setting-item :name="differValueName"> | |
| 41 | + <n-input-number v-model:value="optionData.differValue[0]" class="input-num-width" size="small"> | |
| 42 | + <template #suffix> | |
| 43 | + {{DifferUnitObject[optionData.differUnit[0]]}} | |
| 44 | + </template> | |
| 45 | + </n-input-number> | |
| 46 | + </setting-item> | |
| 47 | + <setting-item :name="differUnitName"> | |
| 48 | + <n-select v-model:value="optionData.differUnit[0]" size="small" :options="differUnitOptions" /> | |
| 49 | + </setting-item> | |
| 50 | + <setting-item v-if="isRange" name="结束值动态偏移量"> | |
| 51 | + <n-input-number v-model:value="optionData.differValue[1]" class="input-num-width" size="small"> | |
| 52 | + <template #suffix> | |
| 53 | + {{DifferUnitObject[optionData.differUnit[1]]}} | |
| 54 | + </template> | |
| 55 | + </n-input-number> | |
| 56 | + </setting-item> | |
| 57 | + <setting-item v-if="isRange" name="结束值偏移单位"> | |
| 58 | + <n-select v-model:value="optionData.differUnit[1]" size="small" :options="differUnitOptions" /> | |
| 59 | + </setting-item> | |
| 34 | 60 | </setting-item-box> |
| 61 | + | |
| 35 | 62 | </collapse-item> |
| 36 | 63 | </template> |
| 37 | 64 | |
| 38 | 65 | <script lang="ts" setup> |
| 39 | -import { PropType } from 'vue' | |
| 66 | +import { PropType, computed } from 'vue' | |
| 40 | 67 | import { icon } from '@/plugins' |
| 41 | 68 | import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting' |
| 42 | 69 | import { option } from './config' |
| 43 | -import { ComponentInteractEventEnum } from './interact' | |
| 70 | +import { ComponentInteractEventEnum, DefaultTypeEnum, DifferUnitEnum, DifferUnitObject } from './interact' | |
| 71 | +import dayjs from "dayjs"; | |
| 44 | 72 | |
| 45 | 73 | const { HelpOutlineIcon } = icon.ionicons5 |
| 46 | 74 | |
| ... | ... | @@ -100,4 +128,87 @@ const datePickerTypeOptions = [ |
| 100 | 128 | value: ComponentInteractEventEnum.QUARTER_RANGE |
| 101 | 129 | } |
| 102 | 130 | ] |
| 131 | + | |
| 132 | +const defaultTypeOptions = [ | |
| 133 | + { | |
| 134 | + label: '静态', | |
| 135 | + value: DefaultTypeEnum.STATIC | |
| 136 | + }, | |
| 137 | + { | |
| 138 | + label: '动态', | |
| 139 | + value: DefaultTypeEnum.DYNAMIC | |
| 140 | + }, | |
| 141 | + { | |
| 142 | + label: '无', | |
| 143 | + value: DefaultTypeEnum.NONE | |
| 144 | + } | |
| 145 | +] | |
| 146 | + | |
| 147 | + | |
| 148 | +const differUnitOptions = [ | |
| 149 | + // ManipulateType | |
| 150 | + { | |
| 151 | + value: DifferUnitEnum.DAY, | |
| 152 | + label: DifferUnitObject[DifferUnitEnum.DAY] | |
| 153 | + }, | |
| 154 | + { | |
| 155 | + value: DifferUnitEnum.WEEK, | |
| 156 | + label: DifferUnitObject[DifferUnitEnum.WEEK] | |
| 157 | + }, | |
| 158 | + { | |
| 159 | + value: DifferUnitEnum.MONTH, | |
| 160 | + label: DifferUnitObject[DifferUnitEnum.MONTH] | |
| 161 | + }, | |
| 162 | + { | |
| 163 | + value: DifferUnitEnum.QUARTER, | |
| 164 | + label: DifferUnitObject[DifferUnitEnum.QUARTER] | |
| 165 | + }, | |
| 166 | + { | |
| 167 | + value: DifferUnitEnum.YEAR, | |
| 168 | + label: DifferUnitObject[DifferUnitEnum.YEAR] | |
| 169 | + }, | |
| 170 | + { | |
| 171 | + value: DifferUnitEnum.HOUR, | |
| 172 | + label: DifferUnitObject[DifferUnitEnum.HOUR] | |
| 173 | + }, | |
| 174 | + { | |
| 175 | + value: DifferUnitEnum.MINUTE, | |
| 176 | + label: DifferUnitObject[DifferUnitEnum.MINUTE] | |
| 177 | + }, | |
| 178 | + { | |
| 179 | + value: DifferUnitEnum.SECOND, | |
| 180 | + label: DifferUnitObject[DifferUnitEnum.SECOND] | |
| 181 | + }, | |
| 182 | + { | |
| 183 | + value: DifferUnitEnum.MILLISECOND, | |
| 184 | + label: DifferUnitObject[DifferUnitEnum.MILLISECOND] | |
| 185 | + } | |
| 186 | +] | |
| 187 | + | |
| 188 | + | |
| 189 | +const isRange = computed(() => { | |
| 190 | + return props.optionData.componentInteractEventKey.endsWith('range') | |
| 191 | +}) | |
| 192 | + | |
| 193 | +const differValueName = computed(() => { | |
| 194 | + return isRange.value ? '开始值动态偏移量' : '动态偏移量' | |
| 195 | +}) | |
| 196 | + | |
| 197 | +const differUnitName = computed(() => { | |
| 198 | + return isRange.value ? '开始值偏移单位' : '偏移单位' | |
| 199 | +}) | |
| 200 | + | |
| 201 | +const datePickerTypeUpdate = () => { | |
| 202 | + props.optionData.dataset = isRange.value ? [dayjs().valueOf(), dayjs().valueOf()] : dayjs().valueOf() | |
| 203 | +} | |
| 204 | + | |
| 205 | +const defaultTypeUpdate = (v: string) => { | |
| 206 | + if (v === DefaultTypeEnum.STATIC) { | |
| 207 | + datePickerTypeUpdate() | |
| 208 | + } else { | |
| 209 | + // DefaultTypeEnum. | |
| 210 | + props.optionData.dataset = null | |
| 211 | + } | |
| 212 | +} | |
| 213 | + | |
| 103 | 214 | </script> | ... | ... |
| 1 | 1 | <template> |
| 2 | 2 | <n-date-picker |
| 3 | 3 | v-model:value="option.dataset" |
| 4 | + clearable | |
| 4 | 5 | :panel="!!chartConfig.option.isPanel" |
| 5 | 6 | :type="chartConfig.option.componentInteractEventKey" |
| 6 | 7 | :style="`width:${w}px;`" |
| ... | ... | @@ -9,13 +10,15 @@ |
| 9 | 10 | </template> |
| 10 | 11 | |
| 11 | 12 | <script setup lang="ts"> |
| 12 | -import { PropType, toRefs, ref, shallowReactive, watch } from 'vue' | |
| 13 | -import dayjs from 'dayjs' | |
| 13 | +import { computed, PropType, ref, shallowReactive, toRefs, watch } from 'vue' | |
| 14 | 14 | import { CreateComponentType } from '@/packages/index.d' |
| 15 | 15 | import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' |
| 16 | 16 | import { useChartInteract } from '@/hooks' |
| 17 | 17 | import { InteractEventOn } from '@/enums/eventEnum' |
| 18 | -import { ComponentInteractParamsEnum } from './interact' | |
| 18 | +import {ComponentInteractEventEnum, ComponentInteractParamsEnum, DefaultTypeEnum} from './interact' | |
| 19 | +import dayjs, {ManipulateType} from 'dayjs' | |
| 20 | +import quarterOfYear from 'dayjs/plugin/quarterOfYear'; | |
| 21 | + | |
| 19 | 22 | |
| 20 | 23 | const props = defineProps({ |
| 21 | 24 | chartConfig: { |
| ... | ... | @@ -31,62 +34,107 @@ const option = shallowReactive({ |
| 31 | 34 | dataset: props.chartConfig.option.dataset |
| 32 | 35 | }) |
| 33 | 36 | |
| 37 | +const isRange = computed(() => { | |
| 38 | + return props.chartConfig.option.componentInteractEventKey.endsWith('range') | |
| 39 | +}) | |
| 40 | + | |
| 34 | 41 | // 监听事件改变 |
| 35 | -const onChange = (v: number | number[]) => { | |
| 36 | - if (v instanceof Array) { | |
| 42 | +const onChange = (v: number | number[] | null) => { | |
| 43 | + if (isRange.value) { | |
| 44 | + let dateStart = null | |
| 45 | + let dateEnd = null | |
| 46 | + let daterange = null | |
| 47 | + if(v instanceof Array){ | |
| 48 | + dateStart = v[0] | |
| 49 | + dateEnd = v[1] | |
| 50 | + daterange = `${v[0]}-${v[1]}` | |
| 51 | + } | |
| 37 | 52 | // 存储到联动数据 |
| 38 | 53 | useChartInteract( |
| 39 | - props.chartConfig, | |
| 40 | - useChartEditStore, | |
| 41 | - { | |
| 42 | - [ComponentInteractParamsEnum.DATE_START]: v[0] || dayjs().valueOf(), | |
| 43 | - [ComponentInteractParamsEnum.DATE_END]: v[1] || dayjs().valueOf(), | |
| 44 | - [ComponentInteractParamsEnum.DATE_RANGE]: `${v[0] || dayjs().valueOf()}-${v[1] || dayjs().valueOf()}` | |
| 45 | - }, | |
| 46 | - InteractEventOn.CHANGE | |
| 54 | + props.chartConfig, | |
| 55 | + useChartEditStore, | |
| 56 | + { | |
| 57 | + [ComponentInteractParamsEnum.DATE_START]: dateStart, | |
| 58 | + [ComponentInteractParamsEnum.DATE_END]: dateEnd, | |
| 59 | + [ComponentInteractParamsEnum.DATE_RANGE]: daterange | |
| 60 | + }, | |
| 61 | + InteractEventOn.CHANGE | |
| 47 | 62 | ) |
| 48 | 63 | } else { |
| 49 | 64 | // 存储到联动数据 |
| 50 | 65 | useChartInteract( |
| 51 | - props.chartConfig, | |
| 52 | - useChartEditStore, | |
| 53 | - { [ComponentInteractParamsEnum.DATE]: v || dayjs().valueOf() }, | |
| 54 | - InteractEventOn.CHANGE | |
| 66 | + props.chartConfig, | |
| 67 | + useChartEditStore, | |
| 68 | + { [ComponentInteractParamsEnum.DATE]: v }, | |
| 69 | + InteractEventOn.CHANGE | |
| 55 | 70 | ) |
| 56 | 71 | } |
| 57 | 72 | } |
| 58 | 73 | |
| 59 | -// 手动更新 | |
| 60 | -watch( | |
| 61 | - () => props.chartConfig.option.dataset, | |
| 62 | - (newData: number | number[]) => { | |
| 63 | - option.dataset = newData | |
| 64 | - // 关联目标组件首次请求带上默认内容 | |
| 65 | - onChange(newData) | |
| 66 | - }, | |
| 67 | - { | |
| 68 | - immediate: true | |
| 74 | +const getDiffDate = (type: ComponentInteractEventEnum, date: dayjs.Dayjs) => { | |
| 75 | + // 注册 quarterOfYear 插件 | |
| 76 | + dayjs.extend(quarterOfYear) | |
| 77 | + switch (type) { | |
| 78 | + case ComponentInteractEventEnum.DATE: | |
| 79 | + case ComponentInteractEventEnum.DATE_RANGE: | |
| 80 | + date = date.startOf('day') | |
| 81 | + break | |
| 82 | + case ComponentInteractEventEnum.MONTH: | |
| 83 | + case ComponentInteractEventEnum.MONTH_RANGE: | |
| 84 | + date = date.startOf('month') | |
| 85 | + break | |
| 86 | + case ComponentInteractEventEnum.YEAR: | |
| 87 | + case ComponentInteractEventEnum.YEAR_RANGE: | |
| 88 | + date = date.startOf('year') | |
| 89 | + break | |
| 90 | + case ComponentInteractEventEnum.QUARTER: | |
| 91 | + case ComponentInteractEventEnum.QUARTER_RANGE: | |
| 92 | + date = date.startOf('quarter') | |
| 93 | + break | |
| 94 | + default: | |
| 95 | + break | |
| 69 | 96 | } |
| 70 | -) | |
| 97 | + return date | |
| 98 | +} | |
| 71 | 99 | |
| 72 | -// 手动更新 | |
| 73 | 100 | watch( |
| 74 | - () => props.chartConfig.option.differValue, | |
| 75 | - (newData: number) => { | |
| 76 | - if (props.chartConfig.option.differValue === 0) return | |
| 77 | - if (typeof option.dataset === 'object') { | |
| 78 | - option.dataset[0] = dayjs().add(newData, 'day').valueOf() | |
| 79 | - option.dataset[1] = dayjs().add(newData, 'day').valueOf() | |
| 80 | - } else { | |
| 81 | - option.dataset = dayjs().add(newData, 'day').valueOf() | |
| 101 | + () => { | |
| 102 | + return { | |
| 103 | + type: props.chartConfig.option.componentInteractEventKey as ComponentInteractEventEnum, | |
| 104 | + defaultType: props.chartConfig.option.defaultType as string, | |
| 105 | + differValue: props.chartConfig.option.differValue as number[], | |
| 106 | + differUnit: props.chartConfig.option.differUnit as ManipulateType[], | |
| 107 | + dataset: props.chartConfig.option.dataset as number | number[] | null, | |
| 108 | + }; | |
| 109 | + }, | |
| 110 | + (newData, oldData) => { | |
| 111 | + const hasTypeChanged = newData.type !== oldData?.type; | |
| 112 | + const hasDefaultTypeChanged = newData.defaultType !== oldData?.defaultType; | |
| 113 | + const hasDifferValueChanged = newData.differValue !== oldData?.differValue; | |
| 114 | + const hasDifferUnitChanged = newData.differUnit !== oldData?.differUnit; | |
| 115 | + | |
| 116 | + if (hasTypeChanged || hasDefaultTypeChanged || hasDifferValueChanged || hasDifferUnitChanged) { | |
| 117 | + if (newData.defaultType === DefaultTypeEnum.NONE) { | |
| 118 | + props.chartConfig.option.dataset = null; | |
| 119 | + } else if (newData.defaultType === DefaultTypeEnum.DYNAMIC) { | |
| 120 | + let date = dayjs(); | |
| 121 | + if (isRange.value) { | |
| 122 | + props.chartConfig.option.dataset = [ | |
| 123 | + getDiffDate(newData.type,date.add(newData.differValue[0], newData.differUnit[0])).valueOf(), | |
| 124 | + getDiffDate(newData.type,date.add(newData.differValue[1], newData.differUnit[1])).valueOf(), | |
| 125 | + ]; | |
| 126 | + } else { | |
| 127 | + props.chartConfig.option.dataset = getDiffDate(newData.type,date.add(newData.differValue[0], newData.differUnit[0])).valueOf() | |
| 128 | + } | |
| 129 | + } | |
| 130 | + } | |
| 131 | + option.dataset = props.chartConfig.option.dataset; | |
| 132 | + onChange(option.dataset); | |
| 133 | + }, | |
| 134 | + { | |
| 135 | + immediate: true, | |
| 82 | 136 | } |
| 83 | - // 关联目标组件首次请求带上默认内容 | |
| 84 | - onChange(newData) | |
| 85 | - }, | |
| 86 | - { | |
| 87 | - immediate: true | |
| 88 | - } | |
| 89 | -) | |
| 137 | +); | |
| 90 | 138 | </script> |
| 91 | 139 | |
| 92 | 140 | <style lang="scss" scoped> | ... | ... |
| ... | ... | @@ -22,6 +22,37 @@ export enum ComponentInteractParamsEnum { |
| 22 | 22 | DATE_RANGE = 'daterange' |
| 23 | 23 | } |
| 24 | 24 | |
| 25 | +export enum DefaultTypeEnum { | |
| 26 | + NONE = "none", | |
| 27 | + STATIC = "static", | |
| 28 | + DYNAMIC = "dynamic" | |
| 29 | +} | |
| 30 | + | |
| 31 | +export enum DifferUnitEnum { | |
| 32 | + DAY = 'd', | |
| 33 | + WEEK = 'w', | |
| 34 | + MONTH = 'M', | |
| 35 | + QUARTER = 'Q', | |
| 36 | + YEAR = 'y', | |
| 37 | + HOUR = 'h', | |
| 38 | + MINUTE = 'm', | |
| 39 | + SECOND = 's', | |
| 40 | + MILLISECOND = 'ms', | |
| 41 | +} | |
| 42 | + | |
| 43 | +export const DifferUnitObject = { | |
| 44 | + // https://day.js.org/docs/en/manipulate/add | |
| 45 | + [DifferUnitEnum.DAY]: '天', | |
| 46 | + [DifferUnitEnum.WEEK]: '周', | |
| 47 | + [DifferUnitEnum.MONTH]: '月', | |
| 48 | + [DifferUnitEnum.QUARTER]: '季度', | |
| 49 | + [DifferUnitEnum.YEAR]: '年', | |
| 50 | + [DifferUnitEnum.HOUR]: '小时', | |
| 51 | + [DifferUnitEnum.MINUTE]: '分钟', | |
| 52 | + [DifferUnitEnum.SECOND]: '秒', | |
| 53 | + [DifferUnitEnum.MILLISECOND]: '毫秒', | |
| 54 | +} | |
| 55 | + | |
| 25 | 56 | const time = [ |
| 26 | 57 | { |
| 27 | 58 | value: ComponentInteractParamsEnum.DATE, | ... | ... |
| 1 | 1 | <template> |
| 2 | 2 | <collapse-item name="标签页配置" :expanded="true"> |
| 3 | - <setting-item-box name="默认值" :alone="true"> | |
| 3 | + <setting-item-box name="标签类型" :alone="true"> | |
| 4 | 4 | <n-select size="small" v-model:value="optionData.tabType" :options="tabTypeOptions" /> |
| 5 | 5 | </setting-item-box> |
| 6 | + <setting-item-box name="默认值" :alone="true"> | |
| 7 | + <n-select size="small" v-model:value="optionData.tabLabel" value-field="label" :options="optionData.dataset" /> | |
| 8 | + </setting-item-box> | |
| 6 | 9 | </collapse-item> |
| 7 | 10 | </template> |
| 8 | 11 | ... | ... |
| 1 | 1 | <template> |
| 2 | - <n-tabs :type="option.value.tabType" @update:value="onChange"> | |
| 2 | + <n-tabs :type="option.value.tabType" @update:value="onChange" :default-value="option.value.tabLabel"> | |
| 3 | 3 | <n-tab v-for="(item, index) in option.value.dataset" :name="item.label" :key="index"> {{ item.label }} </n-tab> |
| 4 | 4 | </n-tabs> |
| 5 | 5 | </template> | ... | ... |
| ... | ... | @@ -516,7 +516,7 @@ export const useChartEditStore = defineStore({ |
| 516 | 516 | return e |
| 517 | 517 | } |
| 518 | 518 | const isCut = recordCharts.type === HistoryActionTypeEnum.CUT |
| 519 | - const targetList = Array.isArray(recordCharts.charts) ? recordCharts.charts : [recordCharts.charts] | |
| 519 | + const targetList = Array.isArray(recordCharts.charts) ? recordCharts.charts : [ recordCharts.charts ] | |
| 520 | 520 | // 多项 |
| 521 | 521 | targetList.forEach((e: CreateComponentType | CreateComponentGroupType) => { |
| 522 | 522 | this.addComponentList(parseHandle(e), undefined, true) | ... | ... |
| ... | ... | @@ -338,12 +338,12 @@ export const JSONParseOriginal = (data: string) => { |
| 338 | 338 | } |
| 339 | 339 | // 还原函数值 |
| 340 | 340 | if (typeof v === 'string' && v.indexOf && (v.indexOf('function') > -1 || v.indexOf('=>') > -1)) { |
| 341 | - return eval(`(function(){return ${v}})()`) | |
| 341 | + return evalFn(`(function(){return ${v}})()`) | |
| 342 | 342 | } else if (typeof v === 'string' && v.indexOf && v.indexOf('return ') > -1) { |
| 343 | 343 | const baseLeftIndex = v.indexOf('(') |
| 344 | 344 | if (baseLeftIndex > -1) { |
| 345 | 345 | const newFn = `function ${v.substring(baseLeftIndex)}` |
| 346 | - return eval(`(function(){return ${newFn}})()`) | |
| 346 | + return evalFn(`(function(){return ${newFn}})()`) | |
| 347 | 347 | } |
| 348 | 348 | } |
| 349 | 349 | return v | ... | ... |
| ... | ... | @@ -130,9 +130,13 @@ const sendHandle = async () => { |
| 130 | 130 | const res = await customizeHttp(toRaw(targetData.value.request), toRaw(chartEditStore.getRequestGlobalConfig)) |
| 131 | 131 | loading.value = false |
| 132 | 132 | if (res) { |
| 133 | - const { data } = res | |
| 134 | - if (!data && !targetData.value.filter) window['$message'].warning('您的数据不符合默认格式,请配置过滤器!') | |
| 135 | - targetData.value.option.dataset = newFunctionHandle(data, res, targetData.value.filter) | |
| 133 | + const { data } = res | |
| 134 | + if (!data && !targetData.value.filter) { | |
| 135 | + window['$message'].warning('您的数据不符合默认格式,请配置过滤器!') | |
| 136 | + showMatching.value = true | |
| 137 | + return | |
| 138 | + } | |
| 139 | + targetData.value.option.dataset = newFunctionHandle(data, res, targetData.value.filter) | |
| 136 | 140 | showMatching.value = true |
| 137 | 141 | return |
| 138 | 142 | } | ... | ... |
| ... | ... | @@ -117,7 +117,11 @@ const sendHandle = async () => { |
| 117 | 117 | const res = await customizeHttp(toRaw(targetData.value.request), toRaw(chartEditStore.getRequestGlobalConfig)) |
| 118 | 118 | loading.value = false |
| 119 | 119 | if (res) { |
| 120 | - if (!res?.data && !targetData.value.filter) window['$message'].warning('您的数据不符合默认格式,请配置过滤器!') | |
| 120 | + if (!res?.data && !targetData.value.filter) { | |
| 121 | + window['$message'].warning('您的数据不符合默认格式,请配置过滤器!') | |
| 122 | + showMatching.value = true | |
| 123 | + return | |
| 124 | + } | |
| 121 | 125 | targetData.value.option.dataset = newFunctionHandle(res?.data, res, targetData.value.filter) |
| 122 | 126 | showMatching.value = true |
| 123 | 127 | return | ... | ... |
| 1 | 1 | <template> |
| 2 | 2 | <!-- 组件配置 --> |
| 3 | 3 | <n-divider class="go-my-3" title-placement="left"></n-divider> |
| 4 | - <setting-item-box :itemRightStyle="{ | |
| 5 | - gridTemplateColumns: '6fr 2fr' | |
| 6 | - }" style="padding-right: 25px"> | |
| 4 | + <setting-item-box | |
| 5 | + :itemRightStyle="{ | |
| 6 | + gridTemplateColumns: '6fr 2fr' | |
| 7 | + }" | |
| 8 | + style="padding-right: 25px" | |
| 9 | + > | |
| 7 | 10 | <template #name> |
| 8 | 11 | 地址 |
| 9 | 12 | <n-tooltip trigger="hover" v-if="isDev()"> |
| ... | ... | @@ -34,8 +37,13 @@ |
| 34 | 37 | </setting-item> |
| 35 | 38 | <setting-item name="更新间隔,为 0 只会初始化"> |
| 36 | 39 | <n-input-group> |
| 37 | - <n-input-number v-model:value.trim="requestInterval" class="select-time-number" min="0" :show-button="false" | |
| 38 | - placeholder="默认使用全局数据"> | |
| 40 | + <n-input-number | |
| 41 | + v-model:value.trim="requestInterval" | |
| 42 | + class="select-time-number" | |
| 43 | + min="0" | |
| 44 | + :show-button="false" | |
| 45 | + placeholder="默认使用全局数据" | |
| 46 | + > | |
| 39 | 47 | </n-input-number> |
| 40 | 48 | <!-- 单位 --> |
| 41 | 49 | <n-select class="select-time-options" v-model:value="requestIntervalUnit" :options="selectTimeOptions" /> |
| ... | ... | @@ -150,11 +158,9 @@ const apiList = [ |
| 150 | 158 | .select-time-number { |
| 151 | 159 | width: 100%; |
| 152 | 160 | } |
| 153 | - | |
| 154 | 161 | .select-time-options { |
| 155 | 162 | width: 100px; |
| 156 | 163 | } |
| 157 | - | |
| 158 | 164 | .select-type-options { |
| 159 | 165 | width: 120px; |
| 160 | 166 | } | ... | ... |
| 1 | 1 | <script lang="ts" setup> |
| 2 | 2 | import { getAllPublicInterface } from '@/api/external/dynamicRequest' |
| 3 | -import { ParamsItemType, PublicInterfaceRecord, PublicInterfaceRequestParams, PublicInterfaceStateEnum } from '@/api/external/dynamicRequest/model'; | |
| 3 | +import { ParamsItemType, PublicInterfaceRecord, PublicInterfaceRequestParams } from '@/api/external/dynamicRequest/model'; | |
| 4 | 4 | import { SettingItem, SettingItemBox } from '@/components/Pages/ChartItemSetting'; |
| 5 | 5 | import { RequestContentTypeEnum, RequestContentTypeNameEnum, RequestEnum } from '@/enums/external/httpEnum'; |
| 6 | 6 | import { RequestBodyEnum, RequestHttpEnum, RequestHttpIntervalEnum, RequestParams, RequestParamsTypeEnum } from '@/enums/httpEnum'; |
| 7 | -import { NCard, NEllipsis, NEmpty, NInputGroup, NInputNumber, NScrollbar, NSelect, NSpace, NTabPane, NTabs, SelectOption } from 'naive-ui'; | |
| 7 | +import { NCard, NEmpty, NInputGroup, NInputNumber, NScrollbar, NSelect, NTabPane, NTabs, SelectOption } from 'naive-ui'; | |
| 8 | 8 | import { ref, computed, unref, nextTick, h } from 'vue' |
| 9 | 9 | import { selectTimeOptions, selectTypeOptions } from '../../../index.d'; |
| 10 | 10 | import ParamsTable from '../RequestModal/ParamsTable.vue'; |
| ... | ... | @@ -14,6 +14,7 @@ import { DynamicForm } from '../DynamicForm'; |
| 14 | 14 | import { extraPublicInterfaceInfo } from '../DynamicForm/utils'; |
| 15 | 15 | import { isArray } from '@/utils'; |
| 16 | 16 | import { useTargetData } from '../../../../hooks/useTargetData.hook'; |
| 17 | +import { InterfaceTypeEnum, InterfaceTypeNameEnum } from '@/enums/external/interfaceEnum'; | |
| 17 | 18 | |
| 18 | 19 | const publicInterfaceList = ref<PublicInterfaceRecord[]>([]) |
| 19 | 20 | |
| ... | ... | @@ -44,15 +45,20 @@ const getSelectedInterfaceParams = computed(() => { |
| 44 | 45 | |
| 45 | 46 | const getPublicInterfaceList = async () => { |
| 46 | 47 | if (unref(publicInterfaceList).length) return |
| 47 | - /** | |
| 48 | - * ft 更换接口 | |
| 49 | - * 修改后的代码在注释之间,并标注好源代码和修改后代码,方便回溯 | |
| 50 | - * 源代码 const result = await getAllPublicInterface({ state: PublicInterfaceStateEnum.PUBLISH }) | |
| 51 | - * 修改后代码 const result = await getAllPublicInterface() | |
| 52 | - */ | |
| 48 | + /** | |
| 49 | + * ft 更换接口 | |
| 50 | + * 修改后的代码在注释之间,并标注好源代码和修改后代码,方便回溯 | |
| 51 | + * 源代码 const result = await getAllPublicInterface({ state: PublicInterfaceStateEnum.PUBLISH }) | |
| 52 | + * 修改后代码 const result = await getAllPublicInterface() | |
| 53 | + */ | |
| 53 | 54 | const result = await getAllPublicInterface() |
| 54 | 55 | //ft |
| 55 | - publicInterfaceList.value = result | |
| 56 | + publicInterfaceList.value = result.map(item => { | |
| 57 | + return { | |
| 58 | + ...item, | |
| 59 | + class: 'interface-option' | |
| 60 | + } | |
| 61 | + }) | |
| 56 | 62 | } |
| 57 | 63 | |
| 58 | 64 | const handleFilter = (query: string, option: SelectOption) => { |
| ... | ... | @@ -116,14 +122,14 @@ const setDynamicFormValue = (request: ExtraRequestConfigType) => { |
| 116 | 122 | /** |
| 117 | 123 | * ft 优化动态表单包含有entityType,则自动回填DEVICE |
| 118 | 124 | */ |
| 119 | - if(typeof request.requestParams ==='string' && request.requestParams) { | |
| 120 | - const DEVICE='DEVICE' | |
| 121 | - const {Params:includeEntityTypeByParams}=JSON.parse(request.requestParams as unknown as string) | |
| 122 | - const findEntityType = includeEntityTypeByParams.find((item:Record<string,string>)=>item.key==='scope' && item.value==='entityType') | |
| 123 | - if(!findEntityType) return | |
| 124 | - if (unref(paramsDynamicFormEl)) unref(paramsDynamicFormEl)?.setConfigurationData({ | |
| 125 | - [findEntityType?.value]:DEVICE | |
| 126 | - }) | |
| 125 | + if (typeof request.requestParams === 'string' && request.requestParams) { | |
| 126 | + const DEVICE = 'DEVICE' | |
| 127 | + const { Params: includeEntityTypeByParams } = JSON.parse(request.requestParams as unknown as string) | |
| 128 | + const findEntityType = includeEntityTypeByParams.find((item: Record<string, string>) => item.key === 'scope' && item.value === 'entityType') | |
| 129 | + if (!findEntityType) return | |
| 130 | + if (unref(paramsDynamicFormEl)) unref(paramsDynamicFormEl)?.setConfigurationData({ | |
| 131 | + [findEntityType?.value]: DEVICE | |
| 132 | + }) | |
| 127 | 133 | } |
| 128 | 134 | //ft |
| 129 | 135 | } |
| ... | ... | @@ -156,7 +162,7 @@ const setConfigurationData = async (request: ExtraRequestConfigType) => { |
| 156 | 162 | * 源代码 selectedPublicInterface.value = requestDataPondId |
| 157 | 163 | * 修改后代码 selectedPublicInterface.value = publicInterfaceList.value.find(it=>it.id === requestDataPondId)?.id||'' |
| 158 | 164 | */ |
| 159 | - selectedPublicInterface.value = publicInterfaceList.value.find(it=>it.id === requestDataPondId)?.id||'' | |
| 165 | + selectedPublicInterface.value = publicInterfaceList.value.find(it => it.id === requestDataPondId)?.id || '' | |
| 160 | 166 | //ft |
| 161 | 167 | requestContentTypeRef.value = requestContentType |
| 162 | 168 | requestHttpTypeRef.value = requestHttpType |
| ... | ... | @@ -169,26 +175,27 @@ const setConfigurationData = async (request: ExtraRequestConfigType) => { |
| 169 | 175 | setDynamicFormValue(request) |
| 170 | 176 | } |
| 171 | 177 | |
| 172 | - /** | |
| 173 | - * ft 修改在公共接口下拉框里加上接口类型 | |
| 174 | - * 修改后的代码在注释之间,并标注好源代码和修改后代码,方便回溯 | |
| 175 | - * 源代码 属于新增代码,源代码无 | |
| 176 | - * 修改后代码在//ft之间 | |
| 177 | - */ | |
| 178 | +/** | |
| 179 | + * ft 修改在公共接口下拉框里加上接口类型 | |
| 180 | + * 修改后的代码在注释之间,并标注好源代码和修改后代码,方便回溯 | |
| 181 | + * 源代码 属于新增代码,源代码无 | |
| 182 | + * 修改后代码在//ft之间 | |
| 183 | + */ | |
| 178 | 184 | |
| 179 | -const getHttpType = (httpType:RequestEnum, contentType:number) => { | |
| 180 | - if(contentType===0){ | |
| 181 | - if (httpType === RequestEnum[httpType]) return RequestEnum[httpType] | |
| 185 | +const getHttpType = (httpType: RequestEnum, contentType: number) => { | |
| 186 | + if (contentType === 0) { | |
| 187 | + if (httpType === RequestEnum[httpType]) return RequestEnum[httpType] | |
| 182 | 188 | } |
| 183 | - else if (contentType===2) return 'WebSocket' | |
| 189 | + else if (contentType === 2) return 'WebSocket' | |
| 184 | 190 | } |
| 185 | 191 | |
| 186 | 192 | const renderOption = (option: SelectOption) => { |
| 187 | - const httpType = getHttpType(option?.requestHttpType as RequestEnum, option?.requestContentType as number) | |
| 188 | - const interfaceTypeName = option?.interfaceType === 'SYSTEM' ? '系统默认' :option?.interfaceType === 'CUSTOM'? '自定义':'' | |
| 189 | - return h(NSpace, { justify: 'space-between', wrap: false, style: 'padding: 0 15px; height: 28px; line-height: 28px; width:100%' }, () => [ | |
| 190 | - h(NEllipsis, null, () => `${!httpType?'':httpType+'/'}${interfaceTypeName}`), | |
| 191 | - h(NEllipsis, null, () => option.interfaceName), | |
| 193 | + const interfaceType = option?.interfaceType as InterfaceTypeEnum | |
| 194 | + const interfaceTypeName = interfaceType ? InterfaceTypeNameEnum[interfaceType] : '' | |
| 195 | + const interfaceName = option.interfaceName as string | |
| 196 | + return h('div', { style: 'display: flex; justify-content: space-between;' }, [ | |
| 197 | + h('div', { style: 'flex: 0 0 100px' }, interfaceTypeName), | |
| 198 | + h('div', { style: 'flex: 1 1 auto; text-align: right; text-overflow: ellipsis; overflow: hidden;' }, interfaceName) | |
| 192 | 199 | ]) |
| 193 | 200 | } |
| 194 | 201 | //ft |
| ... | ... | @@ -206,7 +213,7 @@ defineExpose({ |
| 206 | 213 | <SettingItemBox name="公共接口" :alone="true" :item-right-style="{ gridTemplateColumns: '5fr 2fr 1fr' }"> |
| 207 | 214 | <SettingItem name="请求方式 & URL地址"> |
| 208 | 215 | <NInputGroup> |
| 209 | - <!-- | |
| 216 | + <!-- | |
| 210 | 217 | /** |
| 211 | 218 | * ft 修改在公共接口下拉框里加上接口类型 |
| 212 | 219 | * 修改后的代码在注释之间,并标注好源代码和修改后代码,方便回溯 |
| ... | ... | @@ -269,7 +276,7 @@ defineExpose({ |
| 269 | 276 | :disabled="true" /> |
| 270 | 277 | |
| 271 | 278 | </SettingItemBox> |
| 272 | - <!-- | |
| 279 | + <!-- | |
| 273 | 280 | /** |
| 274 | 281 | * ft 修改 |
| 275 | 282 | * 修改后的代码在注释之间,并标注好源代码和修改后代码,方便回溯 |
| ... | ... | @@ -278,7 +285,8 @@ defineExpose({ |
| 278 | 285 | * 优化普通和websocket表格长度不一样样式问题 |
| 279 | 286 | */ |
| 280 | 287 | --> |
| 281 | - <SettingItemBox v-if="requestContentTypeRef === RequestContentTypeEnum.WEB_SOCKET" :item-right-style="{ gridTemplateColumns: '7fr 1fr' }"> | |
| 288 | + <SettingItemBox v-if="requestContentTypeRef === RequestContentTypeEnum.WEB_SOCKET" | |
| 289 | + :item-right-style="{ gridTemplateColumns: '7fr 1fr' }"> | |
| 282 | 290 | <NCard v-if="requestParamsTypeRef === RequestParamsTypeEnum.PARAMS" class="dynamic-form"> |
| 283 | 291 | <NScrollbar style="max-height: 400px; box-sizing: border-box;"> |
| 284 | 292 | <DynamicForm ref="socketDynamicFormEl" :paramsItemList="getSelectedInterfaceParams" /> |
| ... | ... | @@ -304,3 +312,9 @@ defineExpose({ |
| 304 | 312 | } |
| 305 | 313 | } |
| 306 | 314 | </style> |
| 315 | + | |
| 316 | +<style lang="scss"> | |
| 317 | +.interface-option> div { | |
| 318 | + width: 100%; | |
| 319 | +} | |
| 320 | +</style> | ... | ... |
| ... | ... | @@ -58,7 +58,7 @@ |
| 58 | 58 | <help-outline-icon></help-outline-icon> |
| 59 | 59 | </n-icon> |
| 60 | 60 | </template> |
| 61 | - <n-text>不支持「静态组件」</n-text> | |
| 61 | + <n-text>不支持「静态组件」支持「组件」「公共APi」</n-text> | |
| 62 | 62 | </n-tooltip> |
| 63 | 63 | </template> |
| 64 | 64 | <n-select |
| ... | ... | @@ -89,7 +89,7 @@ |
| 89 | 89 | </n-table> |
| 90 | 90 | </setting-item-box> |
| 91 | 91 | |
| 92 | - <n-tag :bordered="false" type="primary"> 关联目标组件请求参数 </n-tag> | |
| 92 | + <n-tag :bordered="false" type="primary"> 关联目标请求参数 </n-tag> | |
| 93 | 93 | |
| 94 | 94 | <setting-item-box |
| 95 | 95 | :name="requestParamsItem" |
| ... | ... | @@ -125,7 +125,7 @@ import { VNodeChild, computed } from 'vue' |
| 125 | 125 | import { SelectOption, SelectGroupOption } from 'naive-ui' |
| 126 | 126 | import { SettingItemBox, SettingItem, CollapseItem } from '@/components/Pages/ChartItemSetting' |
| 127 | 127 | import { CreateComponentType, CreateComponentGroupType, ChartFrameEnum } from '@/packages/index.d' |
| 128 | -import { RequestParamsTypeEnum } from '@/enums/httpEnum' | |
| 128 | +import { RequestParamsTypeEnum, RequestDataTypeEnum } from '@/enums/httpEnum' | |
| 129 | 129 | import { InteractEventOn, COMPONENT_INTERACT_EVENT_KET } from '@/enums/eventEnum' |
| 130 | 130 | import { icon } from '@/plugins' |
| 131 | 131 | import noData from '@/assets/images/canvas/noData.png' |
| ... | ... | @@ -154,6 +154,11 @@ const option = computed(() => { |
| 154 | 154 | // 绑定组件数据 request |
| 155 | 155 | const fnGetRequest = (id: string | undefined, key: RequestParamsTypeEnum) => { |
| 156 | 156 | if (!id) return {} |
| 157 | + const globalConfigPindApr = chartEditStore.requestGlobalConfig.requestDataPond.find(item => { | |
| 158 | + return item.dataPondId === id | |
| 159 | + })?.dataPondRequestConfig.requestParams | |
| 160 | + | |
| 161 | + if (globalConfigPindApr) return globalConfigPindApr[key] | |
| 157 | 162 | return chartEditStore.componentList[chartEditStore.fetchTargetIndex(id)]?.request.requestParams[key] |
| 158 | 163 | } |
| 159 | 164 | |
| ... | ... | @@ -178,12 +183,10 @@ const fnEventsOptions = (): Array<SelectOption | SelectGroupOption> => { |
| 178 | 183 | iter: Array<CreateComponentType | CreateComponentGroupType>, |
| 179 | 184 | val: CreateComponentType | CreateComponentGroupType |
| 180 | 185 | ) => { |
| 181 | - if (val.groupList && val.groupList.length > 0) { | |
| 182 | - iter.push(val) | |
| 183 | - } else { | |
| 186 | + if (!val.groupList && val.request.requestDataType === RequestDataTypeEnum.AJAX && val.request.requestUrl) { | |
| 184 | 187 | iter.push(val) |
| 185 | 188 | } |
| 186 | - return val.groupList ? [...iter, ...fnFlattern(val.groupList)] : iter | |
| 189 | + return val.groupList && val.groupList.length > 0 ? [...iter, ...fnFlattern(val.groupList)] : iter | |
| 187 | 190 | }, |
| 188 | 191 | [] |
| 189 | 192 | ) |
| ... | ... | @@ -203,9 +206,17 @@ const fnEventsOptions = (): Array<SelectOption | SelectGroupOption> => { |
| 203 | 206 | const mapOptionList = filterOptionList.map(item => ({ |
| 204 | 207 | id: item.id, |
| 205 | 208 | title: item.chartConfig.title, |
| 206 | - disabled: false | |
| 209 | + disabled: false, | |
| 210 | + type: 'componentList' | |
| 207 | 211 | })) |
| 208 | 212 | |
| 213 | + const requestDataPond = chartEditStore.requestGlobalConfig.requestDataPond.map(item => ({ | |
| 214 | + id: item.dataPondId, | |
| 215 | + title: item.dataPondName, | |
| 216 | + disabled: false, | |
| 217 | + type: 'requestDataPond' | |
| 218 | + })) | |
| 219 | + const tarArr = requestDataPond.concat(mapOptionList) | |
| 209 | 220 | targetData.value.events.interactEvents?.forEach(iaItem => { |
| 210 | 221 | mapOptionList.forEach(optionItem => { |
| 211 | 222 | if (optionItem.id === iaItem.interactComponentId) { |
| ... | ... | @@ -214,7 +225,7 @@ const fnEventsOptions = (): Array<SelectOption | SelectGroupOption> => { |
| 214 | 225 | }) |
| 215 | 226 | }) |
| 216 | 227 | |
| 217 | - return mapOptionList | |
| 228 | + return tarArr | |
| 218 | 229 | } |
| 219 | 230 | |
| 220 | 231 | // 新增模块 | ... | ... |
| ... | ... | @@ -159,9 +159,11 @@ const dragCanvas = (e: any) => { |
| 159 | 159 | const canvasBox = () => { |
| 160 | 160 | const layoutDom = document.getElementById('go-chart-edit-layout') |
| 161 | 161 | if (layoutDom) { |
| 162 | + // 此处减去滚动条的宽度和高度 | |
| 163 | + const scrollW = 20 | |
| 162 | 164 | return { |
| 163 | - height: layoutDom.clientHeight - 25, | |
| 164 | - width: layoutDom.clientWidth | |
| 165 | + height: layoutDom.clientHeight - scrollW, | |
| 166 | + width: layoutDom.clientWidth - scrollW | |
| 165 | 167 | } |
| 166 | 168 | } |
| 167 | 169 | return { | ... | ... |
| ... | ... | @@ -66,7 +66,7 @@ const useSyncUpdateHandle = () => { |
| 66 | 66 | // 关闭侦听 |
| 67 | 67 | const unUse = () => { |
| 68 | 68 | // clearInterval(timer) |
| 69 | - // removeEventListener('blur', syncData) | |
| 69 | + removeEventListener('blur', syncDataToPreview) | |
| 70 | 70 | removeEventListener(SavePageEnum.JSON, updateFn) |
| 71 | 71 | } |
| 72 | 72 | ... | ... |
| ... | ... | @@ -33,8 +33,7 @@ export const dragHandle = async (e: DragEvent) => { |
| 33 | 33 | if (dropData.disabled) return |
| 34 | 34 | |
| 35 | 35 | // 创建新图表组件 |
| 36 | - const newComponent: CreateComponentType = await createComponent(dropData) | |
| 37 | - console.log(newComponent) | |
| 36 | + let newComponent: CreateComponentType = await createComponent(dropData) | |
| 38 | 37 | if (dropData.redirectComponent) { |
| 39 | 38 | dropData.dataset && (newComponent.option.dataset = dropData.dataset) |
| 40 | 39 | newComponent.chartConfig.title = dropData.title | ... | ... |
| ... | ... | @@ -6,7 +6,7 @@ const chartEditStore = useChartEditStore() |
| 6 | 6 | |
| 7 | 7 | // 布局处理 |
| 8 | 8 | export const useLayout = (fn: () => Promise<void>) => { |
| 9 | - const removeScale: Function = () => { } | |
| 9 | + let removeScale: Function = () => { } | |
| 10 | 10 | onMounted(async () => { |
| 11 | 11 | // 设置 Dom 值(ref 不生效先用 document) |
| 12 | 12 | chartEditStore.setEditCanvas( |
| ... | ... | @@ -21,7 +21,7 @@ export const useLayout = (fn: () => Promise<void>) => { |
| 21 | 21 | // 获取数据 |
| 22 | 22 | await fn() |
| 23 | 23 | // 监听初始化 |
| 24 | - const removeScale = chartEditStore.listenerScale() | |
| 24 | + removeScale = chartEditStore.listenerScale() | |
| 25 | 25 | |
| 26 | 26 | }) |
| 27 | 27 | ... | ... |
| ... | ... | @@ -12,7 +12,42 @@ export const useScale = (localStorageInfo: ChartEditStorageType) => { |
| 12 | 12 | const height = ref(localStorageInfo.editCanvasConfig.height) |
| 13 | 13 | const scaleRef = ref({ width: 1, height: 1 }) |
| 14 | 14 | |
| 15 | - provide(SCALE_KEY, scaleRef); | |
| 15 | + provide(SCALE_KEY, scaleRef) | |
| 16 | + | |
| 17 | + // 监听鼠标滚轮 +ctrl 键 | |
| 18 | + const useAddWheelHandle = (removeEvent: Function) => { | |
| 19 | + addEventListener( | |
| 20 | + 'wheel', | |
| 21 | + (e: any) => { | |
| 22 | + if (window?.$KeyboardActive?.ctrl) { | |
| 23 | + e.preventDefault() | |
| 24 | + e.stopPropagation() | |
| 25 | + removeEvent() | |
| 26 | + const transform = previewRef.value?.style.transform | |
| 27 | + const regRes = transform.match(/scale\((\d+\.?\d*)*/) as RegExpMatchArray | |
| 28 | + const width = regRes[1] | |
| 29 | + const previewBoxDom = document.querySelector('.go-preview') as HTMLElement | |
| 30 | + const entityDom = document.querySelector('.go-preview-entity') as HTMLElement | |
| 31 | + if (previewBoxDom) { | |
| 32 | + previewBoxDom.style.overflow = 'unset' | |
| 33 | + previewBoxDom.style.position = 'absolute' | |
| 34 | + } | |
| 35 | + if (entityDom) { | |
| 36 | + entityDom.style.overflow = 'unset' | |
| 37 | + } | |
| 38 | + | |
| 39 | + if (e.wheelDelta > 0) { | |
| 40 | + const resNum = parseFloat(Number(width).toFixed(2)) | |
| 41 | + previewRef.value.style.transform = `scale(${resNum > 5 ? 5 : resNum + 0.1})` | |
| 42 | + } else { | |
| 43 | + const resNum = parseFloat(Number(width).toFixed(2)) | |
| 44 | + previewRef.value.style.transform = `scale(${resNum < 0.2 ? 0.2 : resNum - 0.1})` | |
| 45 | + } | |
| 46 | + } | |
| 47 | + }, | |
| 48 | + { passive: false } | |
| 49 | + ) | |
| 50 | + } | |
| 16 | 51 | |
| 17 | 52 | const updateScaleRef = (scale: { width: number; height: number }) => { |
| 18 | 53 | // 这里需要解构,保证赋值给scaleRef的为一个新对象 |
| ... | ... | @@ -23,74 +58,82 @@ export const useScale = (localStorageInfo: ChartEditStorageType) => { |
| 23 | 58 | // 屏幕适配 |
| 24 | 59 | onMounted(() => { |
| 25 | 60 | switch (localStorageInfo.editCanvasConfig.previewScaleType) { |
| 26 | - case PreviewScaleEnum.FIT: (() => { | |
| 27 | - const { calcRate, windowResize, unWindowResize } = usePreviewFitScale( | |
| 28 | - width.value as number, | |
| 29 | - height.value as number, | |
| 30 | - previewRef.value, | |
| 31 | - updateScaleRef | |
| 32 | - ) | |
| 33 | - calcRate() | |
| 34 | - windowResize() | |
| 35 | - onUnmounted(() => { | |
| 36 | - unWindowResize() | |
| 37 | - }) | |
| 38 | - })() | |
| 39 | - break; | |
| 40 | - case PreviewScaleEnum.SCROLL_Y: (() => { | |
| 41 | - const { calcRate, windowResize, unWindowResize } = usePreviewScrollYScale( | |
| 42 | - width.value as number, | |
| 43 | - height.value as number, | |
| 44 | - previewRef.value, | |
| 45 | - (scale) => { | |
| 46 | - const dom = entityRef.value | |
| 47 | - dom.style.width = `${width.value * scale.width}px` | |
| 48 | - dom.style.height = `${height.value * scale.height}px` | |
| 49 | - updateScaleRef(scale) | |
| 50 | - } | |
| 51 | - ) | |
| 52 | - calcRate() | |
| 53 | - windowResize() | |
| 54 | - onUnmounted(() => { | |
| 55 | - unWindowResize() | |
| 56 | - }) | |
| 57 | - })() | |
| 61 | + case PreviewScaleEnum.FIT: | |
| 62 | + ;(() => { | |
| 63 | + const { calcRate, windowResize, unWindowResize } = usePreviewFitScale( | |
| 64 | + width.value as number, | |
| 65 | + height.value as number, | |
| 66 | + previewRef.value, | |
| 67 | + updateScaleRef | |
| 68 | + ) | |
| 69 | + calcRate() | |
| 70 | + windowResize() | |
| 71 | + useAddWheelHandle(unWindowResize) | |
| 72 | + onUnmounted(() => { | |
| 73 | + unWindowResize() | |
| 74 | + }) | |
| 75 | + })() | |
| 76 | + break | |
| 77 | + case PreviewScaleEnum.SCROLL_Y: | |
| 78 | + ;(() => { | |
| 79 | + const { calcRate, windowResize, unWindowResize } = usePreviewScrollYScale( | |
| 80 | + width.value as number, | |
| 81 | + height.value as number, | |
| 82 | + previewRef.value, | |
| 83 | + scale => { | |
| 84 | + const dom = entityRef.value | |
| 85 | + dom.style.width = `${width.value * scale.width}px` | |
| 86 | + dom.style.height = `${height.value * scale.height}px` | |
| 87 | + updateScaleRef(scale) | |
| 88 | + } | |
| 89 | + ) | |
| 90 | + calcRate() | |
| 91 | + windowResize() | |
| 92 | + useAddWheelHandle(unWindowResize) | |
| 93 | + onUnmounted(() => { | |
| 94 | + unWindowResize() | |
| 95 | + }) | |
| 96 | + })() | |
| 58 | 97 | |
| 59 | - break; | |
| 60 | - case PreviewScaleEnum.SCROLL_X: (() => { | |
| 61 | - const { calcRate, windowResize, unWindowResize } = usePreviewScrollXScale( | |
| 62 | - width.value as number, | |
| 63 | - height.value as number, | |
| 64 | - previewRef.value, | |
| 65 | - (scale) => { | |
| 66 | - const dom = entityRef.value | |
| 67 | - dom.style.width = `${width.value * scale.width}px` | |
| 68 | - dom.style.height = `${height.value * scale.height}px` | |
| 69 | - updateScaleRef(scale) | |
| 70 | - } | |
| 71 | - ) | |
| 72 | - calcRate() | |
| 73 | - windowResize() | |
| 74 | - onUnmounted(() => { | |
| 75 | - unWindowResize() | |
| 76 | - }) | |
| 77 | - })() | |
| 98 | + break | |
| 99 | + case PreviewScaleEnum.SCROLL_X: | |
| 100 | + ;(() => { | |
| 101 | + const { calcRate, windowResize, unWindowResize } = usePreviewScrollXScale( | |
| 102 | + width.value as number, | |
| 103 | + height.value as number, | |
| 104 | + previewRef.value, | |
| 105 | + scale => { | |
| 106 | + const dom = entityRef.value | |
| 107 | + dom.style.width = `${width.value * scale.width}px` | |
| 108 | + dom.style.height = `${height.value * scale.height}px` | |
| 109 | + updateScaleRef(scale) | |
| 110 | + } | |
| 111 | + ) | |
| 112 | + calcRate() | |
| 113 | + windowResize() | |
| 114 | + useAddWheelHandle(unWindowResize) | |
| 115 | + onUnmounted(() => { | |
| 116 | + unWindowResize() | |
| 117 | + }) | |
| 118 | + })() | |
| 78 | 119 | |
| 79 | - break; | |
| 80 | - case PreviewScaleEnum.FULL: (() => { | |
| 81 | - const { calcRate, windowResize, unWindowResize } = usePreviewFullScale( | |
| 82 | - width.value as number, | |
| 83 | - height.value as number, | |
| 84 | - previewRef.value, | |
| 85 | - updateScaleRef | |
| 86 | - ) | |
| 87 | - calcRate() | |
| 88 | - windowResize() | |
| 89 | - onUnmounted(() => { | |
| 90 | - unWindowResize() | |
| 91 | - }) | |
| 92 | - })() | |
| 93 | - break; | |
| 120 | + break | |
| 121 | + case PreviewScaleEnum.FULL: | |
| 122 | + ;(() => { | |
| 123 | + const { calcRate, windowResize, unWindowResize } = usePreviewFullScale( | |
| 124 | + width.value as number, | |
| 125 | + height.value as number, | |
| 126 | + previewRef.value, | |
| 127 | + updateScaleRef | |
| 128 | + ) | |
| 129 | + calcRate() | |
| 130 | + windowResize() | |
| 131 | + useAddWheelHandle(unWindowResize) | |
| 132 | + onUnmounted(() => { | |
| 133 | + unWindowResize() | |
| 134 | + }) | |
| 135 | + })() | |
| 136 | + break | |
| 94 | 137 | } |
| 95 | 138 | }) |
| 96 | 139 | ... | ... |
| 1 | 1 | <template> |
| 2 | - <div :class="`go-preview ${chartEditStore.editCanvasConfig.previewScaleType}`"> | |
| 2 | + <div :class="`go-preview ${chartEditStore.editCanvasConfig.previewScaleType}`" @mousedown="dragCanvas"> | |
| 3 | 3 | <template v-if="showEntity"> |
| 4 | 4 | <!-- 实体区域 --> |
| 5 | 5 | <div ref="entityRef" class="go-preview-entity"> |
| ... | ... | @@ -32,7 +32,7 @@ import { PreviewRenderList } from './components/PreviewRenderList' |
| 32 | 32 | import { getFilterStyle, setTitle } from '@/utils' |
| 33 | 33 | |
| 34 | 34 | // THINGS_KIT 重写预览逻辑,调用接口 |
| 35 | -// import { getEditCanvasConfigStyle, getSessionStorageInfo } from './utils' | |
| 35 | +// import { getEditCanvasConfigStyle, getSessionStorageInfo, keyRecordHandle, dragCanvas } from './utils' | |
| 36 | 36 | import { getEditCanvasConfigStyle } from './utils' |
| 37 | 37 | import { getSessionStorageInfo } from './external/usePreview' |
| 38 | 38 | |
| ... | ... | @@ -65,6 +65,9 @@ const showEntity = computed(() => { |
| 65 | 65 | useStore(chartEditStore) |
| 66 | 66 | const { entityRef, previewRef } = useScale(chartEditStore) |
| 67 | 67 | const { show } = useComInstall(chartEditStore) |
| 68 | + | |
| 69 | +// 开启键盘监听 | |
| 70 | +keyRecordHandle() | |
| 68 | 71 | </script> |
| 69 | 72 | |
| 70 | 73 | <style lang="scss" scoped> | ... | ... |
src/views/preview/utils/drag.ts
0 → 100644
| 1 | +import { listen } from 'dom-helpers' | |
| 2 | +import throttle from 'lodash/throttle' | |
| 3 | + | |
| 4 | +let prevMoveXValue = [0, 0] | |
| 5 | +let prevMoveYValue = [0, 0] | |
| 6 | + | |
| 7 | +// 拖拽处理 | |
| 8 | +export const dragCanvas = (e: MouseEvent) => { | |
| 9 | + const previewBoxDom = document.querySelector('.go-preview') as HTMLElement | |
| 10 | + if (!previewBoxDom || previewBoxDom.style.position !== 'absolute') return | |
| 11 | + if (!window.$KeyboardActive?.space) return | |
| 12 | + | |
| 13 | + e.preventDefault() | |
| 14 | + e.stopPropagation() | |
| 15 | + // @ts-ignore | |
| 16 | + document.activeElement?.blur() | |
| 17 | + const startX = e.screenX | |
| 18 | + const startY = e.screenY | |
| 19 | + | |
| 20 | + const listenMousemove = listen( | |
| 21 | + window, | |
| 22 | + 'mousemove', | |
| 23 | + throttle((moveEvent: MouseEvent) => { | |
| 24 | + const nx = moveEvent.screenX - startX | |
| 25 | + const ny = moveEvent.screenY - startY | |
| 26 | + | |
| 27 | + const [prevMoveX1, prevMoveX2] = prevMoveXValue | |
| 28 | + const [prevMoveY1, prevMoveY2] = prevMoveYValue | |
| 29 | + | |
| 30 | + prevMoveXValue = [prevMoveX2, nx] | |
| 31 | + prevMoveYValue = [prevMoveY2, ny] | |
| 32 | + | |
| 33 | + // 去除小数部分 | |
| 34 | + if (previewBoxDom) { | |
| 35 | + const oldLeft = previewBoxDom.style.left ? Number(previewBoxDom.style.left.split('px')[0]) : 0 | |
| 36 | + const oldTop = previewBoxDom.style.top ? Number(previewBoxDom.style.top.split('px')[0]) : 0 | |
| 37 | + previewBoxDom.style.left = | |
| 38 | + oldLeft + | |
| 39 | + (prevMoveX2 > prevMoveX1 ? Math.abs(prevMoveX2 - prevMoveX1) : -Math.abs(prevMoveX2 - prevMoveX1)) + | |
| 40 | + 'px' | |
| 41 | + previewBoxDom.style.top = | |
| 42 | + oldTop + | |
| 43 | + (prevMoveY2 > prevMoveY1 ? Math.abs(prevMoveY2 - prevMoveY1) : -Math.abs(prevMoveY2 - prevMoveY1)) + | |
| 44 | + 'px' | |
| 45 | + } | |
| 46 | + }, 20) | |
| 47 | + ) | |
| 48 | + | |
| 49 | + const listenMouseup = listen(window, 'mouseup', () => { | |
| 50 | + prevMoveXValue = [0, 0] | |
| 51 | + prevMoveYValue = [0, 0] | |
| 52 | + listenMousemove() | |
| 53 | + listenMouseup() | |
| 54 | + }) | |
| 55 | +} | ... | ... |
src/views/preview/utils/keyboard.ts
0 → 100644
| 1 | +// 处理键盘记录 | |
| 2 | +export const keyRecordHandle = () => { | |
| 3 | + // 默认赋值 | |
| 4 | + window.$KeyboardActive = { | |
| 5 | + ctrl: false, | |
| 6 | + space: false | |
| 7 | + } | |
| 8 | + | |
| 9 | + document.onkeydown = (e: KeyboardEvent) => { | |
| 10 | + const { keyCode } = e | |
| 11 | + if (keyCode == 32 && e.target == document.body) e.preventDefault() | |
| 12 | + | |
| 13 | + if ([17, 32].includes(keyCode) && window.$KeyboardActive) { | |
| 14 | + switch (keyCode) { | |
| 15 | + case 17: | |
| 16 | + window.$KeyboardActive.ctrl = true | |
| 17 | + break | |
| 18 | + case 32: | |
| 19 | + window.$KeyboardActive.space = true | |
| 20 | + const previewBoxDom = document.querySelector('.go-preview') as HTMLElement | |
| 21 | + if (previewBoxDom && previewBoxDom.style.position === 'absolute') { | |
| 22 | + previewBoxDom.style.cursor = 'move' | |
| 23 | + } | |
| 24 | + break | |
| 25 | + } | |
| 26 | + } | |
| 27 | + } | |
| 28 | + | |
| 29 | + document.onkeyup = (e: KeyboardEvent) => { | |
| 30 | + const { keyCode } = e | |
| 31 | + if (keyCode == 32 && e.target == document.body) e.preventDefault() | |
| 32 | + | |
| 33 | + if ([17, 32].includes(keyCode) && window.$KeyboardActive) { | |
| 34 | + switch (keyCode) { | |
| 35 | + case 17: | |
| 36 | + window.$KeyboardActive.ctrl = false | |
| 37 | + break | |
| 38 | + case 32: | |
| 39 | + window.$KeyboardActive.space = false | |
| 40 | + break | |
| 41 | + } | |
| 42 | + } | |
| 43 | + | |
| 44 | + const previewBoxDom = document.querySelector('.go-preview') as HTMLElement | |
| 45 | + if (previewBoxDom) { | |
| 46 | + previewBoxDom.style.cursor = 'default' | |
| 47 | + } | |
| 48 | + } | |
| 49 | +} | ... | ... |
| ... | ... | @@ -44,6 +44,8 @@ import { useUserStore } from '@/store/external/modules/user' |
| 44 | 44 | import { checkSharePageNeedAccessToken, getPublicToken, getShareContentData } from '@/api/external/sys/share' |
| 45 | 45 | import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' |
| 46 | 46 | import { fetchChartComponent } from '@/packages' |
| 47 | +import { CreateComponentType } from '@/packages/index.d' | |
| 48 | +import { CreateComponentGroupType } from '@/packages/index.d' | |
| 47 | 49 | |
| 48 | 50 | const allowLoadPreviewPage = ref(true) |
| 49 | 51 | const showModal = ref(false) |
| ... | ... | @@ -78,6 +80,19 @@ const sharePageHandlerProcess = async () => { |
| 78 | 80 | } |
| 79 | 81 | } |
| 80 | 82 | |
| 83 | +const handleRegisterComponent = (record: (CreateComponentType | CreateComponentGroupType)[]) => { | |
| 84 | + const fn = (params: (CreateComponentType | CreateComponentGroupType)[]) => { | |
| 85 | + for (const item of params) { | |
| 86 | + if (item.isGroup) { | |
| 87 | + fn(item.groupList || []) | |
| 88 | + continue | |
| 89 | + } | |
| 90 | + componentInstall(item.chartConfig.chartKey, fetchChartComponent(item.chartConfig)) | |
| 91 | + } | |
| 92 | + } | |
| 93 | + fn(record) | |
| 94 | +} | |
| 95 | + | |
| 81 | 96 | const isEmpty = ref(false) |
| 82 | 97 | const getSharePageContentData = async () => { |
| 83 | 98 | try { |
| ... | ... | @@ -93,11 +108,7 @@ const getSharePageContentData = async () => { |
| 93 | 108 | chartEditStore.editCanvasConfig = editCanvasConfig |
| 94 | 109 | chartEditStore.requestGlobalConfig = requestGlobalConfig |
| 95 | 110 | chartEditStore.componentList = componentList |
| 96 | - | |
| 97 | - // register 组件 | |
| 98 | - componentList.forEach(item => { | |
| 99 | - componentInstall(item.chartConfig.chartKey, fetchChartComponent(item.chartConfig)) | |
| 100 | - }) | |
| 111 | + handleRegisterComponent(componentList) | |
| 101 | 112 | } |
| 102 | 113 | setTitle(dataViewName || '') |
| 103 | 114 | showModal.value = false | ... | ... |