refactor: 数据同步编辑页调整、echarts组件重构

This commit is contained in:
meilin.huang
2024-01-10 23:41:55 +08:00
parent 25b0ae4d2f
commit 3857d674ba
13 changed files with 619 additions and 408 deletions

View File

@@ -17,7 +17,7 @@
"countup.js": "^2.7.0",
"cropperjs": "^1.5.11",
"echarts": "^5.4.3",
"element-plus": "^2.4.4",
"element-plus": "^2.5.0",
"js-base64": "^3.7.5",
"jsencrypt": "^3.3.2",
"lodash": "^4.17.21",
@@ -33,7 +33,7 @@
"splitpanes": "^3.1.5",
"sql-formatter": "^14.0.0",
"uuid": "^9.0.1",
"vue": "^3.4.6",
"vue": "^3.4.7",
"vue-router": "^4.2.5",
"xterm": "^5.3.0",
"xterm-addon-fit": "^0.8.0",
@@ -48,7 +48,7 @@
"@typescript-eslint/eslint-plugin": "^6.7.4",
"@typescript-eslint/parser": "^6.7.4",
"@vitejs/plugin-vue": "^5.0.2",
"@vue/compiler-sfc": "^3.4.6",
"@vue/compiler-sfc": "^3.4.7",
"dotenv": "^16.3.1",
"eslint": "^8.35.0",
"eslint-plugin-vue": "^9.19.2",

View File

@@ -1,176 +0,0 @@
{
"seriesCnt": "4",
"backgroundColor": "rgba(0,0,0,0)",
"titleColor": "#008acd",
"subtitleColor": "#aaaaaa",
"textColorShow": false,
"textColor": "#333",
"markTextColor": "#eeeeee",
"color": [
"#2ec7c9",
"#b6a2de",
"#5ab1ef",
"#ffb980",
"#d87a80",
"#8d98b3",
"#e5cf0d",
"#97b552",
"#95706d",
"#dc69aa",
"#07a2a4",
"#9a7fd1",
"#588dd5",
"#f5994e",
"#c05050",
"#59678c",
"#c9ab00",
"#7eb00a",
"#6f5553",
"#c14089"
],
"borderColor": "#ccc",
"borderWidth": 0,
"visualMapColor": [
"#5ab1ef",
"#e0ffff"
],
"legendTextColor": "#333333",
"kColor": "#d87a80",
"kColor0": "#2ec7c9",
"kBorderColor": "#d87a80",
"kBorderColor0": "#2ec7c9",
"kBorderWidth": 1,
"lineWidth": 2,
"symbolSize": 3,
"symbol": "emptyCircle",
"symbolBorderWidth": 1,
"lineSmooth": true,
"graphLineWidth": 1,
"graphLineColor": "#aaaaaa",
"mapLabelColor": "#d87a80",
"mapLabelColorE": "rgb(100,0,0)",
"mapBorderColor": "#eeeeee",
"mapBorderColorE": "#444",
"mapBorderWidth": 0.5,
"mapBorderWidthE": 1,
"mapAreaColor": "#dddddd",
"mapAreaColorE": "rgba(254,153,78,1)",
"axes": [
{
"type": "all",
"name": "通用坐标轴",
"axisLineShow": true,
"axisLineColor": "#eeeeee",
"axisTickShow": true,
"axisTickColor": "#eeeeee",
"axisLabelShow": true,
"axisLabelColor": "#eeeeee",
"splitLineShow": true,
"splitLineColor": [
"#aaaaaa"
],
"splitAreaShow": false,
"splitAreaColor": [
"#eeeeee"
]
},
{
"type": "category",
"name": "类目坐标轴",
"axisLineShow": true,
"axisLineColor": "#008acd",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": false,
"splitLineColor": [
"#eee"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "value",
"name": "数值坐标轴",
"axisLineShow": true,
"axisLineColor": "#008acd",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#eee"
],
"splitAreaShow": true,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "log",
"name": "对数坐标轴",
"axisLineShow": true,
"axisLineColor": "#008acd",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#eee"
],
"splitAreaShow": true,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
},
{
"type": "time",
"name": "时间坐标轴",
"axisLineShow": true,
"axisLineColor": "#008acd",
"axisTickShow": true,
"axisTickColor": "#333",
"axisLabelShow": true,
"axisLabelColor": "#333",
"splitLineShow": true,
"splitLineColor": [
"#eee"
],
"splitAreaShow": false,
"splitAreaColor": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
}
],
"axisSeperateSetting": true,
"toolboxColor": "#2ec7c9",
"toolboxEmphasisColor": "#18a4a6",
"tooltipAxisColor": "#008acd",
"tooltipAxisWidth": "1",
"timelineLineColor": "#008acd",
"timelineLineWidth": 1,
"timelineItemColor": "#008acd",
"timelineItemColorE": "#a9334c",
"timelineCheckColor": "#2ec7c9",
"timelineCheckBorderColor": "#2ec7c9",
"timelineItemBorderWidth": 1,
"timelineControlColor": "#008acd",
"timelineControlBorderColor": "#008acd",
"timelineControlBorderWidth": 0.5,
"timelineLabelColor": "#008acd",
"datazoomBackgroundColor": "rgba(47,69,84,0)",
"datazoomDataColor": "#efefff",
"datazoomFillColor": "rgba(182,162,222,0.2)",
"datazoomHandleColor": "#008acd",
"datazoomHandleWidth": "100",
"datazoomLabelColor": "#333333"
}

View File

@@ -1,38 +0,0 @@
// import * as echarts from 'echarts'
// 引入 echarts 核心模块,核心模块提供了 echarts 使用必须要的接口。
import * as echarts from 'echarts/core';
/** 图表后缀都为 Chart */
import { PieChart } from 'echarts/charts';
// 引入提示框,标题,直角坐标系,数据集,内置数据转换器组件,组件后缀都为 Component
import { TitleComponent, TooltipComponent, GridComponent, DatasetComponent, TransformComponent, LegendComponent } from 'echarts/components';
// 标签自动布局,全局过渡动画等特性
import { LabelLayout, UniversalTransition } from 'echarts/features';
// 引入 Canvas 渲染器,注意引入 CanvasRenderer 或者 SVGRenderer 是必须的一步
import { CanvasRenderer } from 'echarts/renderers';
// 注册必须的组件
echarts.use([
TitleComponent,
TooltipComponent,
GridComponent,
DatasetComponent,
TransformComponent,
LegendComponent,
// BarChart,
LabelLayout,
UniversalTransition,
CanvasRenderer,
// LineChart,
PieChart,
]);
export default function (dom: any, theme: any = null, option: any) {
let chart = echarts.init(dom, theme);
chart.setOption(option);
return chart;
}

View File

@@ -0,0 +1,86 @@
<template>
<div id="echarts" ref="chartRef" :style="echartsStyle" />
</template>
<script setup lang="ts" name="ECharts">
import { ref, onMounted, onBeforeUnmount, watch, computed, markRaw, nextTick } from 'vue';
import { EChartsType, ECElementEvent } from 'echarts/core';
import echarts, { ECOption } from './config';
import { useDebounceFn, useEventListener } from '@vueuse/core';
import { light } from './config/theme';
// import { useThemeConfig } from '@/store/themeConfig';
// import { storeToRefs } from 'pinia';
interface Props {
option: ECOption;
renderer?: 'canvas' | 'svg';
resize?: boolean;
theme?: Object | string;
width?: number | string;
height?: number | string;
onClick?: (event: ECElementEvent) => any;
}
const props = withDefaults(defineProps<Props>(), {
renderer: 'canvas',
theme: light as any,
resize: true,
});
const echartsStyle = computed(() => {
return props.width || props.height ? { height: props.height + 'px', width: props.width + 'px' } : { height: '100%', width: '100%' };
});
const chartRef = ref<HTMLDivElement | HTMLCanvasElement>();
const chartInstance = ref<EChartsType>();
const draw = () => {
if (chartInstance.value) {
chartInstance.value.setOption(props.option, { notMerge: true });
}
};
watch(props, () => {
draw();
});
const handleClick = (event: ECElementEvent) => props.onClick && props.onClick(event);
const init = () => {
if (!chartRef.value) return;
chartInstance.value = echarts.getInstanceByDom(chartRef.value);
if (!chartInstance.value) {
chartInstance.value = markRaw(
echarts.init(chartRef.value, props.theme, {
renderer: props.renderer,
})
);
chartInstance.value.on('click', handleClick);
draw();
}
};
const resize = () => {
if (chartInstance.value && props.resize) {
chartInstance.value.resize({ animation: { duration: 300 } });
}
};
const debouncedResize = useDebounceFn(resize, 300, { maxWait: 800 });
onMounted(() => {
nextTick(() => init());
useEventListener('resize', debouncedResize);
});
onBeforeUnmount(() => {
chartInstance.value?.dispose();
});
defineExpose({
getInstance: () => chartInstance.value,
resize,
draw,
});
</script>

View File

@@ -0,0 +1,67 @@
import * as echarts from 'echarts/core';
import { BarChart, LineChart, LinesChart, PieChart, ScatterChart, RadarChart, GaugeChart } from 'echarts/charts';
import {
TitleComponent,
TooltipComponent,
GridComponent,
DatasetComponent,
TransformComponent,
LegendComponent,
PolarComponent,
GeoComponent,
ToolboxComponent,
DataZoomComponent,
} from 'echarts/components';
import { LabelLayout, UniversalTransition } from 'echarts/features';
import { CanvasRenderer } from 'echarts/renderers';
import type {
BarSeriesOption,
LineSeriesOption,
LinesSeriesOption,
PieSeriesOption,
ScatterSeriesOption,
RadarSeriesOption,
GaugeSeriesOption,
} from 'echarts/charts';
import type { TitleComponentOption, TooltipComponentOption, GridComponentOption, DatasetComponentOption } from 'echarts/components';
import type { ComposeOption } from 'echarts/core';
// import 'echarts-liquidfill';
export type ECOption = ComposeOption<
| BarSeriesOption
| LineSeriesOption
| LinesSeriesOption
| PieSeriesOption
| RadarSeriesOption
| GaugeSeriesOption
| TitleComponentOption
| TooltipComponentOption
| GridComponentOption
| DatasetComponentOption
| ScatterSeriesOption
>;
echarts.use([
TitleComponent,
TooltipComponent,
GridComponent,
DatasetComponent,
TransformComponent,
LegendComponent,
PolarComponent,
GeoComponent,
ToolboxComponent,
DataZoomComponent,
BarChart,
LineChart,
LinesChart,
PieChart,
ScatterChart,
RadarChart,
GaugeChart,
LabelLayout,
UniversalTransition,
CanvasRenderer,
]);
export default echarts;

View File

@@ -0,0 +1,151 @@
const light = {
seriesCnt: '4',
backgroundColor: 'rgba(0,0,0,0)',
titleColor: '#008acd',
subtitleColor: '#aaaaaa',
textColorShow: false,
textColor: '#333',
markTextColor: '#eeeeee',
color: [
'#2ec7c9',
'#b6a2de',
'#5ab1ef',
'#ffb980',
'#d87a80',
'#8d98b3',
'#e5cf0d',
'#97b552',
'#95706d',
'#dc69aa',
'#07a2a4',
'#9a7fd1',
'#588dd5',
'#f5994e',
'#c05050',
'#59678c',
'#c9ab00',
'#7eb00a',
'#6f5553',
'#c14089',
],
borderColor: '#ccc',
borderWidth: 0,
visualMapColor: ['#5ab1ef', '#e0ffff'],
legendTextColor: '#333333',
kColor: '#d87a80',
kColor0: '#2ec7c9',
kBorderColor: '#d87a80',
kBorderColor0: '#2ec7c9',
kBorderWidth: 1,
lineWidth: 2,
symbolSize: 3,
symbol: 'emptyCircle',
symbolBorderWidth: 1,
lineSmooth: true,
graphLineWidth: 1,
graphLineColor: '#aaaaaa',
mapLabelColor: '#d87a80',
mapLabelColorE: 'rgb(100,0,0)',
mapBorderColor: '#eeeeee',
mapBorderColorE: '#444',
mapBorderWidth: 0.5,
mapBorderWidthE: 1,
mapAreaColor: '#dddddd',
mapAreaColorE: 'rgba(254,153,78,1)',
axes: [
{
type: 'all',
name: '通用坐标轴',
axisLineShow: true,
axisLineColor: '#eeeeee',
axisTickShow: true,
axisTickColor: '#eeeeee',
axisLabelShow: true,
axisLabelColor: '#eeeeee',
splitLineShow: true,
splitLineColor: ['#aaaaaa'],
splitAreaShow: false,
splitAreaColor: ['#eeeeee'],
},
{
type: 'category',
name: '类目坐标轴',
axisLineShow: true,
axisLineColor: '#008acd',
axisTickShow: true,
axisTickColor: '#333',
axisLabelShow: true,
axisLabelColor: '#333',
splitLineShow: false,
splitLineColor: ['#eee'],
splitAreaShow: false,
splitAreaColor: ['rgba(250,250,250,0.3)', 'rgba(200,200,200,0.3)'],
},
{
type: 'value',
name: '数值坐标轴',
axisLineShow: true,
axisLineColor: '#008acd',
axisTickShow: true,
axisTickColor: '#333',
axisLabelShow: true,
axisLabelColor: '#333',
splitLineShow: true,
splitLineColor: ['#eee'],
splitAreaShow: true,
splitAreaColor: ['rgba(250,250,250,0.3)', 'rgba(200,200,200,0.3)'],
},
{
type: 'log',
name: '对数坐标轴',
axisLineShow: true,
axisLineColor: '#008acd',
axisTickShow: true,
axisTickColor: '#333',
axisLabelShow: true,
axisLabelColor: '#333',
splitLineShow: true,
splitLineColor: ['#eee'],
splitAreaShow: true,
splitAreaColor: ['rgba(250,250,250,0.3)', 'rgba(200,200,200,0.3)'],
},
{
type: 'time',
name: '时间坐标轴',
axisLineShow: true,
axisLineColor: '#008acd',
axisTickShow: true,
axisTickColor: '#333',
axisLabelShow: true,
axisLabelColor: '#333',
splitLineShow: true,
splitLineColor: ['#eee'],
splitAreaShow: false,
splitAreaColor: ['rgba(250,250,250,0.3)', 'rgba(200,200,200,0.3)'],
},
],
axisSeperateSetting: true,
toolboxColor: '#2ec7c9',
toolboxEmphasisColor: '#18a4a6',
tooltipAxisColor: '#008acd',
tooltipAxisWidth: '1',
timelineLineColor: '#008acd',
timelineLineWidth: 1,
timelineItemColor: '#008acd',
timelineItemColorE: '#a9334c',
timelineCheckColor: '#2ec7c9',
timelineCheckBorderColor: '#2ec7c9',
timelineItemBorderWidth: 1,
timelineControlColor: '#008acd',
timelineControlBorderColor: '#008acd',
timelineControlBorderWidth: 0.5,
timelineLabelColor: '#008acd',
datazoomBackgroundColor: 'rgba(47,69,84,0)',
datazoomDataColor: '#efefff',
datazoomFillColor: 'rgba(182,162,222,0.2)',
datazoomHandleColor: '#008acd',
datazoomHandleWidth: '100',
datazoomLabelColor: '#333333',
};
export { light };

View File

@@ -0,0 +1,135 @@
<template>
<el-tree-select
v-bind="$attrs"
ref="treeRef"
:highlight-current="true"
:indent="10"
:load="loadNode"
:props="treeProps"
lazy
node-key="key"
:expand-on-click-node="true"
filterable
:filter-node-method="filterNode"
v-model="modelValue"
@change="changeNode"
>
<template #default="{ node, data }">
<span>
<span v-if="data.type.value == TagTreeNode.TagPath">
<tag-info :tag-path="data.label" />
</span>
<slot v-else :node="node" :data="data" name="prefix"></slot>
<span class="ml3" :title="data.labelRemark">
<slot name="label" :data="data"> {{ data.label }}</slot>
</span>
<slot :node="node" :data="data" name="suffix"></slot>
</span>
</template>
</el-tree-select>
</template>
<script lang="ts" setup>
import { onMounted, reactive, ref, watch, toRefs } from 'vue';
import { NodeType, TagTreeNode } from './tag';
import TagInfo from './TagInfo.vue';
import { tagApi } from '../tag/api';
const props = defineProps({
resourceType: {
type: [Number],
required: true,
},
tagPathNodeType: {
type: [NodeType],
required: true,
},
load: {
type: Function,
required: false,
},
});
const treeProps = {
label: 'name',
children: 'zones',
isLeaf: 'isLeaf',
};
const emit = defineEmits(['change']);
const treeRef: any = ref(null);
const modelValue = defineModel('modelValue');
const state = reactive({
height: 600 as any,
filterText: '',
opend: {},
});
const { filterText } = toRefs(state);
onMounted(async () => {});
watch(filterText, (val) => {
treeRef.value?.filter(val);
});
const filterNode = (value: string, data: any) => {
if (!value) return true;
return data.label.includes(value);
};
/**
* 加载标签树节点
*/
const loadTags = async () => {
const tags = await tagApi.getResourceTagPaths.request({ resourceType: props.resourceType });
const tagNodes = [];
for (let tagPath of tags) {
tagNodes.push(new TagTreeNode(tagPath, tagPath, props.tagPathNodeType));
}
return tagNodes;
};
/**
* 加载树节点
* @param { Object } node
* @param { Object } resolve
*/
const loadNode = async (node: any, resolve: any) => {
if (typeof resolve !== 'function') {
return;
}
let nodes = [];
try {
if (node.level == 0) {
nodes = await loadTags();
} else if (props.load) {
nodes = await props.load(node);
} else {
nodes = await node.data.loadChildren();
}
} catch (e: any) {
console.error(e);
}
return resolve(nodes);
};
const getNode = (nodeKey: any) => {
let node = treeRef.value.getNode(nodeKey);
if (!node) {
throw new Error('未找到节点: ' + nodeKey);
}
return node;
};
const changeNode = (val: any) => {
// 触发改变时间,并传递节点数据
emit('change', getNode(val)?.data);
};
</script>
<style lang="scss" scoped></style>

View File

@@ -7,47 +7,62 @@
:close-on-click-modal="false"
:close-on-press-escape="false"
:destroy-on-close="true"
width="700px"
width="850px"
>
<el-form :model="form" ref="dbForm" :rules="rules" label-width="auto">
<el-tabs v-model="tabActiveName" style="height: 450px">
<el-tab-pane label="基本信息" name="basic">
<el-form-item prop="taskName" label="任务名" required>
<el-input v-model.trim="form.taskName" placeholder="请输入数据库别名" auto-complete="off" />
<el-form-item>
<el-row>
<el-col :span="11">
<el-form-item prop="taskName" label="任务名" required>
<el-input v-model.trim="form.taskName" placeholder="请输入数据库别名" auto-complete="off" />
</el-form-item>
</el-col>
<el-col :span="11">
<el-form-item prop="taskCron" label="cron" required>
<template #label>
cron
<el-tooltip effect="dark" content="只支持5位表达式,不支持秒级.如 0/2 * * * * 表示每两分钟执行" placement="top">
<el-icon>
<question-filled />
</el-icon>
</el-tooltip>
</template>
<el-input v-model="form.taskCron" placeholder="支持5位表达式,不支持秒级" auto-complete="off" />
</el-form-item>
</el-col>
<el-col :span="2">
<el-form-item prop="status" label="状态" label-width="60" required>
<el-switch
v-model="form.status"
inline-prompt
active-text="启用"
inactive-text="禁用"
:active-value="1"
:inactive-value="-1"
/>
</el-form-item>
</el-col>
</el-row>
</el-form-item>
<el-form-item prop="taskCron" label="cron" required>
<el-input v-model="form.taskCron" placeholder="只支持5位表达式,不支持秒级.如 0/2 * * * * 表示每两分钟执行" auto-complete="off" />
</el-form-item>
<el-form-item prop="pageSize" label="分页大小" required>
<el-input-number v-model.trim="form.pageSize" placeholder="同步数据时查询的每页数据大小" auto-complete="off" size="small" />
</el-form-item>
<el-form-item prop="updField" label="更新字段" required>
<el-input v-model.trim="form.updField" placeholder="查询数据源的时候会带上这个字段当前最大值" auto-complete="off" />
</el-form-item>
<el-form-item prop="updFieldVal" label="更新值">
<el-input v-model.trim="form.updFieldVal" placeholder="更新字段当前最大值" auto-complete="off" />
</el-form-item>
<el-form-item prop="status" label="状态" required>
<el-switch v-model="form.status" inline-prompt active-text="启用" inactive-text="禁用" :active-value="1" :inactive-value="-1" />
</el-form-item>
</el-tab-pane>
<el-tab-pane label="源数据库配置" name="srcDb">
<el-form-item prop="srcDbId" label="数据源" required>
<el-form-item prop="srcDbId" label="源数据库" required>
<db-select-tree
placeholder="请选择源数据库"
v-model:db-id="form.srcDbId"
v-model:db-name="form.srcDbName"
v-model:tag-path="form.srcTagPath"
@select-db="onSelectSrcDb"
/>
</el-form-item>
<el-form-item prop="dataSql" label="数据sql" required>
<monaco-editor height="200px" class="task-sql" language="sql" v-model="form.dataSql" />
</el-form-item>
</el-tab-pane>
<el-tab-pane label="目标数据库配置" name="targetDb">
<el-form-item prop="targetDbId" label="数据源" required>
<el-form-item prop="targetDbId" label="目标数据库" required>
<db-select-tree
placeholder="请选择目标数据库"
v-model:db-id="form.targetDbId"
v-model:db-name="form.targetDbName"
v-model:tag-path="form.targetTagPath"
@@ -55,7 +70,11 @@
/>
</el-form-item>
<el-form-item prop="targetTableName" label="目标表" required>
<el-form-item prop="dataSql" label="源数据sql" required>
<monaco-editor height="150px" class="task-sql" language="sql" v-model="form.dataSql" />
</el-form-item>
<el-form-item prop="targetTableName" label="目标库表" required>
<el-select v-model="form.targetTableName" filterable placeholder="请选择目标数据库表">
<el-option
v-for="item in state.targetTableList"
@@ -65,7 +84,30 @@
/>
</el-select>
</el-form-item>
<el-form-item>
<el-row>
<el-col :span="8">
<el-form-item prop="pageSize" label="分页大小" required>
<el-input type="number" v-model.trim="form.pageSize" placeholder="同步数据时查询的每页数据大小" auto-complete="off" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item prop="updField" label="更新字段" required>
<el-input v-model.trim="form.updField" placeholder="查询数据源的时候会带上这个字段当前最大值" auto-complete="off" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item prop="updFieldVal" label="更新值">
<el-input v-model.trim="form.updFieldVal" placeholder="更新字段当前最大值" auto-complete="off" />
</el-form-item>
</el-col>
</el-row>
</el-form-item>
</el-tab-pane>
<el-tab-pane label="字段映射" name="field">
<el-form-item prop="fieldMap" label="字段映射" required>
<el-table :data="form.fieldMap" :max-height="400" size="small">
@@ -85,6 +127,7 @@
</el-table>
</el-form-item>
</el-tab-pane>
<el-tab-pane label="sql预览" name="sqlPreview">
<el-form-item prop="fieldMap" label="查询sql">
<el-input type="textarea" v-model="state.previewDataSql" readonly :input-style="{ height: '190px' }" />
@@ -226,48 +269,43 @@ watch(dialogVisible, async (newValue: boolean) => {
}
state.tabActiveName = 'basic';
const propsData = props.data as any;
if (propsData?.id) {
let data = await dbApi.getDatasyncTask.request({ taskId: propsData?.id });
state.form = data;
try {
state.form.fieldMap = JSON.parse(data.fieldMap);
} catch (e) {
state.form.fieldMap = [];
}
let { srcDbId, srcTagPath, srcDbName, targetTagPath, targetDbId } = state.form;
// 初始化src数据源
if (srcTagPath && srcDbId) {
// 通过tagPath查询实例列表
const dbInfoRes = await dbApi.dbs.request({ tagPath: srcTagPath });
dbInfoRes.list.forEach((a: any) => {
if (a.id === srcDbId) {
// 初始化实例
a.databases = a.database?.split(' ').sort() || [];
state.srcDbInst = DbInst.getOrNewInst(a);
}
});
}
// 初始化target数据源
if (targetTagPath && targetDbId) {
// 通过tagPath查询实例列表
const dbInfoRes = await dbApi.dbs.request({ tagPath: targetTagPath });
dbInfoRes.list.forEach((a: any) => {
if (a.id === targetDbId) {
// 初始化实例
a.databases = a.database?.split(' ').sort() || [];
state.targetDbInst = DbInst.getOrNewInst(a);
}
});
}
// 注册sql代码提示
if (srcDbId && srcDbName) {
registerDbCompletionItemProvider(srcDbId, srcDbName, state.srcDbInst.databases, state.srcDbInst.type);
}
} else {
if (!propsData?.id) {
state.form = basicFormData;
return;
}
let data = await dbApi.getDatasyncTask.request({ taskId: propsData?.id });
state.form = data;
try {
state.form.fieldMap = JSON.parse(data.fieldMap);
} catch (e) {
state.form.fieldMap = [];
}
let { srcDbId, srcDbName, targetDbId } = state.form;
// 初始化src数据源
if (srcDbId) {
// 通过tagPath查询实例列表
const dbInfoRes = await dbApi.dbs.request({ id: srcDbId });
const db = dbInfoRes.list[0];
// 初始化实例
db.databases = db.database?.split(' ').sort() || [];
state.srcDbInst = DbInst.getOrNewInst(db);
}
// 初始化target数据源
if (targetDbId) {
// 通过tagPath查询实例列表
const dbInfoRes = await dbApi.dbs.request({ id: targetDbId });
const db = dbInfoRes.list[0];
// 初始化实例
db.databases = db.database?.split(' ').sort() || [];
state.targetDbInst = DbInst.getOrNewInst(db);
}
// 注册sql代码提示
if (srcDbId && srcDbName) {
registerDbCompletionItemProvider(srcDbId, srcDbName, state.srcDbInst.databases, state.srcDbInst.type);
}
});
@@ -277,7 +315,7 @@ watch(tabActiveName, async (newValue: string) => {
await handleGetSrcFields();
await handleGetTargetFields();
break;
case 'targetDb':
case 'dbConf':
await handleGetTargetFields();
if (state.form.targetDbId && state.form.targetDbName) {
await loadDbTables(state.form.targetDbId, state.form.targetDbName);
@@ -418,8 +456,12 @@ const cancel = () => {
<style lang="scss">
.sync-task-edit {
.el-select {
width: 360px;
// width: 360px;
width: 100%;
}
// .el-input__inner {
// width: 100%; /* 将el-select内部输入框的宽度设置为100% */
// }
.task-sql {
width: 100%;
}

View File

@@ -1,25 +1,29 @@
<template>
<div class="db-select-tree">
<div style="color: gray">{{ (tagPath || '') + ' - ' + (dbName || '请选择数据源schema') }}</div>
<tag-tree :resource-type="TagResourceTypeEnum.Db.value" :tag-path-node-type="NodeTypeTagPath" ref="tagTreeRef">
<template #prefix="{ data }">
<SvgIcon v-if="data.type.value == SqlExecNodeType.DbInst" :name="getDbDialect(data.params.type).getInfo().icon" :size="18" />
<SvgIcon v-if="data.icon" :name="data.icon.name" :color="data.icon.color" />
</template>
</tag-tree>
</div>
<TagTreeResourceSelect
v-bind="$attrs"
v-model="selectNode"
@change="changeNode"
:resource-type="TagResourceTypeEnum.Db.value"
:tag-path-node-type="NodeTypeTagPath"
>
<template #prefix="{ data }">
<SvgIcon v-if="data.type.value == SqlExecNodeType.DbInst" :name="getDbDialect(data.params.type).getInfo().icon" :size="18" />
<SvgIcon v-if="data.icon" :name="data.icon.name" :color="data.icon.color" />
</template>
</TagTreeResourceSelect>
</template>
<script setup lang="ts">
import { TagResourceTypeEnum } from '@/common/commonEnum';
import TagTree from '@/views/ops/component/TagTree.vue';
import { NodeType, TagTreeNode } from '@/views/ops/component/tag';
import { dbApi } from '@/views/ops/db/api';
import { sleep } from '@/common/utils/loading';
import SvgIcon from '@/components/svgIcon/index.vue';
import { DbType, getDbDialect } from '@/views/ops/db/dialect';
import TagTreeResourceSelect from '../../component/TagTreeResourceSelect.vue';
import { computed } from 'vue';
defineProps({
const props = defineProps({
dbId: {
type: Number,
},
@@ -47,6 +51,15 @@ class SqlExecNodeType {
static PgSchema = 8;
}
const selectNode = computed({
get: () => {
return props.dbName ? `${props.tagPath} - ${props.dbId} - ${props.dbName}` : '';
},
set: () => {
//
},
});
const DbIcon = {
name: 'Coin',
color: '#67c23a',
@@ -89,7 +102,7 @@ const NodeTypeDbInst = new NodeType(SqlExecNodeType.DbInst).withLoadNodesFunc((p
fn = PgNodeTypes;
}
return dbs.map((x: any) => {
let tagTreeNode = new TagTreeNode(`${parentNode.key}.${x}`, x, fn)
let tagTreeNode = new TagTreeNode(`${parentNode.key}.${x}`, `${x}`, fn)
.withParams({
tagPath: params.tagPath,
id: params.id,
@@ -108,17 +121,6 @@ const NodeTypeDbInst = new NodeType(SqlExecNodeType.DbInst).withLoadNodesFunc((p
});
});
const nodeClickChangeDb = (nodeData: TagTreeNode) => {
const params = nodeData.params;
// postgres
emits('update:dbName', params.db);
emits('update:dbId', params.id);
emits('update:tagPath', params.tagPath);
emits('selectDb', params);
return true;
};
// 数据库节点
const PgNodeTypes = new NodeType(SqlExecNodeType.Db).withLoadNodesFunc(async (parentNode: TagTreeNode) => {
// pg类数据库会多一层schema
@@ -137,23 +139,19 @@ const PgNodeTypes = new NodeType(SqlExecNodeType.Db).withLoadNodesFunc(async (pa
});
});
const MysqlNodeTypes = new NodeType(SqlExecNodeType.Db).withNodeClickFunc(nodeClickChangeDb);
const MysqlNodeTypes = new NodeType(SqlExecNodeType.Db);
// postgres schema模式
const NodeTypePostgresSchema = new NodeType(SqlExecNodeType.PgSchema).withNodeClickFunc(nodeClickChangeDb);
const NodeTypePostgresSchema = new NodeType(SqlExecNodeType.PgSchema);
const changeNode = (nodeData: TagTreeNode) => {
const params = nodeData.params;
// postgres
emits('update:dbName', params.db);
emits('update:dbId', params.id);
emits('update:tagPath', params.tagPath);
emits('selectDb', params);
};
</script>
<style lang="scss">
.db-select-tree {
.tag-tree {
height: auto !important;
overflow-x: hidden;
width: 560px;
.el-tree {
height: 150px;
overflow-y: auto;
overflow-x: hidden;
}
}
}
</style>
<style lang="scss"></style>

View File

@@ -24,11 +24,11 @@
</el-col>
<el-col :lg="6" :md="6">
<div class="card-item-chart" ref="memRef"></div>
<ECharts height="200" :option="state.memOption" />
</el-col>
<el-col :lg="6" :md="6">
<div class="card-item-chart" ref="cpuRef"></div>
<ECharts height="200" :option="state.cpuOption" />
</el-col>
</el-row>
@@ -74,11 +74,11 @@
</template>
<script lang="ts" setup>
import { toRefs, reactive, watch, ref, nextTick } from 'vue';
import useEcharts from '@/common/echarts/useEcharts';
import tdTheme from '@/common/echarts/theme.json';
import { toRefs, reactive, watch, nextTick } from 'vue';
import { formatByteSize } from '@/common/utils/format';
import { machineApi } from './api';
import ECharts from '@/components/echarts/ECharts.vue';
import { ECOption } from '@/components/echarts/config';
const props = defineProps({
visible: {
@@ -94,22 +94,16 @@ const props = defineProps({
const emit = defineEmits(['update:visible', 'cancel', 'update:machineId']);
const cpuRef: any = ref();
const memRef: any = ref();
let cpuChart: any = null;
let memChart: any = null;
const state = reactive({
dialogVisible: false,
stats: {} as any,
netInter: [] as any,
memOption: {},
cpuOption: {},
});
const { dialogVisible, stats, netInter } = toRefs(state);
let charts = [] as any;
watch(props, async (newValue: any) => {
const visible = newValue.visible;
if (visible) {
@@ -139,15 +133,15 @@ const initMemStats = () => {
value: mem.total - mem.available,
},
];
const option = {
const option: ECOption = {
title: {
text: '内存',
x: 'left',
textStyle: { fontSize: 15 },
},
tooltip: {
trigger: 'item',
valueFormatter: formatByteSize,
valueFormatter: (val: any) => formatByteSize(val),
},
legend: {
top: '15%',
@@ -180,13 +174,7 @@ const initMemStats = () => {
},
],
};
if (memChart) {
memChart.setOption(option, true);
return;
}
const chart: any = useEcharts(memRef.value, tdTheme, option);
memChart = chart;
charts.push(chart);
state.memOption = option;
};
const initCpuStats = () => {
@@ -206,10 +194,10 @@ const initCpuStats = () => {
value: cpu.user,
},
];
const option = {
const option: ECOption = {
title: {
text: 'CPU使用率',
x: 'left',
textStyle: { fontSize: 15 },
},
tooltip: {
@@ -247,13 +235,7 @@ const initCpuStats = () => {
},
],
};
if (cpuChart) {
cpuChart.setOption(option, true);
return;
}
const chart: any = useEcharts(cpuRef.value, tdTheme, option);
cpuChart = chart;
charts.push(chart);
state.cpuOption = option;
};
const initCharts = () => {
@@ -262,21 +244,6 @@ const initCharts = () => {
initCpuStats();
});
parseNetInter();
initEchartsResize();
};
const initEchartResizeFun = () => {
nextTick(() => {
for (let i = 0; i < charts.length; i++) {
setTimeout(() => {
charts[i].resize();
}, i * 1000);
}
});
};
const initEchartsResize = () => {
window.addEventListener('resize', initEchartResizeFun);
};
const parseNetInter = () => {
@@ -295,16 +262,6 @@ const parseNetInter = () => {
const cancel = () => {
emit('update:visible', false);
emit('cancel');
setTimeout(() => {
cpuChart = null;
memChart = null;
}, 200);
};
</script>
<style lang="scss">
.card-item-chart {
height: 200px;
width: 100%;
}
</style>
<style lang="scss"></style>

View File

@@ -15,7 +15,7 @@
</el-descriptions>
</el-col>
<el-col :lg="8" :md="8" class="redis-info">
<div class="info-memory-chart" ref="memRef"></div>
<ECharts height="150" width="360" :option="state.memOption" />
</el-col>
</el-row>
@@ -72,10 +72,10 @@
</template>
<script lang="ts" setup>
import { reactive, watch, toRefs, ref, nextTick } from 'vue';
import { reactive, watch, toRefs, nextTick } from 'vue';
import { formatByteSize } from '@/common/utils/format';
import useEcharts from '@/common/echarts/useEcharts';
import tdTheme from '@/common/echarts/theme.json';
import ECharts from '@/components/echarts/ECharts.vue';
import { ECOption } from '@/components/echarts/config';
const props = defineProps({
visible: {
@@ -86,6 +86,7 @@ const props = defineProps({
},
info: {
type: [Boolean, Object],
default: () => {},
},
});
@@ -95,11 +96,9 @@ const state = reactive({
dialogVisible: false,
memInfo: {} as any,
Keyspace: [] as any[],
memOption: {},
});
let memChart: any = null;
let memRef = ref(null);
const { dialogVisible, Keyspace } = toRefs(state);
watch(
@@ -146,15 +145,14 @@ const initMemStats = () => {
value: state.memInfo.used_memory,
},
];
const option = {
const option: ECOption = {
title: {
text: '内存',
x: 'left',
textStyle: { fontSize: 14 },
},
tooltip: {
trigger: 'item',
valueFormatter: formatByteSize,
valueFormatter: (val: any) => formatByteSize(val),
},
legend: {
top: '15%',
@@ -186,11 +184,8 @@ const initMemStats = () => {
},
],
};
if (memChart) {
memChart.setOption(option, true);
return;
}
memChart = useEcharts(memRef.value, tdTheme, option);
state.memOption = option;
};
const close = () => {
@@ -202,11 +197,6 @@ const close = () => {
<style lang="scss">
.redis-info {
margin-top: 12px;
.info-memory-chart {
width: 360px;
height: 150px;
}
}
.row .title {

View File

@@ -1,7 +1,5 @@
package entity
import "mayfly-go/pkg/model"
// InstanceQuery 数据库实例查询
type InstanceQuery struct {
Id uint64 `json:"id" form:"id"`
@@ -19,7 +17,7 @@ type DataSyncLogQuery struct {
// 数据库查询实体,不与数据库表字段一一对应
type DbQuery struct {
model.Model
Id uint64 `form:"id"`
Name string `orm:"column(name)" json:"name"`
Database string `orm:"column(database)" json:"database"`

View File

@@ -22,6 +22,7 @@ func (d *dbRepoImpl) GetDbList(condition *entity.DbQuery, pageParam *model.PageP
Select("db.*, inst.name instance_name, inst.type instance_type, inst.host, inst.port, inst.username ").
Joins("JOIN t_db_instance inst ON db.instance_id = inst.id").
Eq("db.instance_id", condition.InstanceId).
Eq("db.id", condition.Id).
Like("db.database", condition.Database).
In("db.code", condition.Codes).
Eq0("db."+model.DeletedColumn, model.ModelUndeleted).