fix: machine file & i18n & icon

This commit is contained in:
meilin.huang
2025-01-07 21:02:27 +08:00
parent f30841209c
commit 1be7a0ec79
20 changed files with 268 additions and 243 deletions

View File

@@ -11,15 +11,15 @@
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"@vueuse/core": "^12.0.0",
"@vueuse/core": "^12.3.0",
"asciinema-player": "^3.8.1",
"axios": "^1.6.2",
"clipboard": "^2.0.11",
"cropperjs": "^1.6.1",
"crypto-js": "^4.2.0",
"dayjs": "^1.11.13",
"echarts": "^5.5.1",
"element-plus": "^2.9.1",
"echarts": "^5.6.0",
"element-plus": "^2.9.2",
"js-base64": "^3.7.7",
"jsencrypt": "^3.3.2",
"lodash": "^4.17.21",
@@ -37,7 +37,7 @@
"trzsz": "^1.1.5",
"uuid": "^9.0.1",
"vue": "^3.5.13",
"vue-i18n": "^10.0.5",
"vue-i18n": "^11.0.1",
"vue-router": "^4.5.0",
"vuedraggable": "^4.1.0",
"xterm": "^5.3.0",
@@ -60,9 +60,9 @@
"eslint": "^8.35.0",
"eslint-plugin-vue": "^9.31.0",
"prettier": "^3.2.5",
"sass": "^1.82.0",
"sass": "^1.83.1",
"typescript": "^5.7.2",
"vite": "^6.0.6",
"vite": "^6.0.7",
"vue-eslint-parser": "^9.4.3"
},
"browserslist": [

View File

@@ -1,4 +1,4 @@
const allSvgIcons = import.meta.glob('./**/*.svg', { eager: true, as: 'raw' });
const allSvgIcons = import.meta.glob('./**/*.svg', { eager: true, query: 'raw' });
const iconNames = [];
@@ -45,7 +45,7 @@ function convertSvgToSymbol(svgString, symbolId) {
// 转为 df/input
const name = path.replace('.svg', '').replace(/^\.\//, '');
iconNames.push(`icon ${name}`);
svgsymbols += convertSvgToSymbol(allSvgIcons[path], name);
svgsymbols += convertSvgToSymbol(allSvgIcons[path].default, name);
}
svgsymbols += '</svg>';

View File

@@ -13,8 +13,8 @@ export const I18nEnum = {
export const ResourceTypeEnum = {
Machine: EnumValue.of(1, '机器').setExtra({ icon: 'Monitor', iconColor: 'var(--el-color-primary)' }).tagTypeSuccess(),
Db: EnumValue.of(2, '数据库实例').setExtra({ icon: 'Coin', iconColor: 'var(--el-color-warning)' }).tagTypeWarning(),
Redis: EnumValue.of(3, 'redis').setExtra({ icon: 'iconfont icon-redis', iconColor: 'var(--el-color-danger)' }).tagTypeInfo(),
Mongo: EnumValue.of(4, 'mongo').setExtra({ icon: 'iconfont icon-mongo', iconColor: 'var(--el-color-success)' }).tagTypeDanger(),
Redis: EnumValue.of(3, 'redis').setExtra({ icon: 'icon redis/redis', iconColor: 'var(--el-color-danger)' }).tagTypeInfo(),
Mongo: EnumValue.of(4, 'mongo').setExtra({ icon: 'icon mongo/mongo', iconColor: 'var(--el-color-success)' }).tagTypeDanger(),
};
// 标签关联的资源类型

View File

@@ -25,8 +25,8 @@
virtual-triggering
>
<template #default>
<div class="ml5 mt5">{{ $t(title) }}</div>
<div class="icon-selector-warp">
<div class="ml10 mt10">{{ title }}</div>
<el-tabs v-model="state.fontIconTabActive" @tab-click="onIconClick">
<el-tab-pane lazy label="ele" name="ele">
<IconList :list="fontIconSheetsFilterList" :empty="emptyDescription" :prefix="state.fontIconPrefix" @get-icon="onColClick" />
@@ -57,7 +57,7 @@ const props = defineProps({
// 输入框占位文本
placeholder: {
type: String,
default: () => '请输入内容搜索图标或者选择图标',
default: () => 'components.iconSelector.placeholder',
},
// 输入框占位文本
size: {
@@ -67,7 +67,7 @@ const props = defineProps({
// 弹窗标题
title: {
type: String,
default: () => '请选择图标',
default: () => 'components.iconSelector.title',
},
// 禁用
disabled: {

View File

@@ -1,6 +1,6 @@
<template>
<div class="monaco-editor" style="border: 1px solid var(--el-border-color-light, #ebeef5); height: 100%">
<div class="monaco-editor-content" ref="monacoTextarea" :style="{ height: height }"></div>
<div class="monaco-editor-content" ref="monacoTextareaRef" :style="{ height: height }"></div>
<el-select v-if="canChangeMode" class="code-mode-select" v-model="languageMode" @change="changeLanguage" filterable>
<el-option v-for="mode in languageArr" :key="mode.value" :label="mode.label" :value="mode.value"> </el-option>
</el-select>
@@ -8,7 +8,7 @@
</template>
<script lang="ts" setup>
import { ref, watch, toRefs, reactive, onMounted, onBeforeUnmount } from 'vue';
import { watch, toRefs, reactive, onMounted, onBeforeUnmount, useTemplateRef, Ref } from 'vue';
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
// 相关语言
import 'monaco-editor/esm/vs/basic-languages/shell/shell.contribution.js';
@@ -45,6 +45,7 @@ import JsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker';
// import Dracula from 'monaco-themes/themes/Dracula.json'
import SolarizedLight from 'monaco-themes/themes/Solarized-light.json';
import { language as shellLan } from 'monaco-editor/esm/vs/basic-languages/shell/shell.js';
import { ElOption, ElSelect } from 'element-plus';
import { storeToRefs } from 'pinia';
@@ -53,9 +54,6 @@ import { useThemeConfig } from '@/store/themeConfig';
const { themeConfig } = storeToRefs(useThemeConfig());
const props = defineProps({
modelValue: {
type: String,
},
language: {
type: String,
default: null,
@@ -74,12 +72,11 @@ const props = defineProps({
},
options: {
type: Object,
default: null,
default: () => {},
},
});
//定义事件
const emit = defineEmits(['update:modelValue']);
const modelValue = defineModel<any>('modelValue', { required: true });
const languageArr = [
{
@@ -154,7 +151,7 @@ const defaultOptions = {
},
};
const monacoTextarea: any = ref();
const monacoTextareaRef: Ref<any> = useTemplateRef('monacoTextareaRef');
let monacoEditorIns: editor.IStandaloneCodeEditor = null as any;
let completionItemProvider: any = null;
@@ -177,7 +174,7 @@ const { languageMode } = toRefs(state);
onMounted(() => {
state.languageMode = props.language;
initMonacoEditorIns();
setEditorValue(props.modelValue);
setEditorValue(modelValue.value);
registerCompletionItemProvider();
});
@@ -191,18 +188,15 @@ onBeforeUnmount(() => {
}
});
watch(
() => props.modelValue,
(newValue: any) => {
if (!monacoEditorIns.hasTextFocus()) {
state.languageMode = props.language;
if (newValue == null) {
newValue = '';
}
monacoEditorIns?.setValue(newValue);
watch(modelValue, (newValue: any) => {
if (!monacoEditorIns.hasTextFocus()) {
state.languageMode = props.language;
if (newValue == null) {
newValue = '';
}
monacoEditorIns?.setValue(newValue);
}
);
});
watch(
() => props.language,
@@ -227,11 +221,11 @@ const initMonacoEditorIns = () => {
monaco.editor.defineTheme('SolarizedLight', SolarizedLight);
defaultOptions.language = state.languageMode;
defaultOptions.theme = themeConfig.value.editorTheme;
monacoEditorIns = monaco.editor.create(monacoTextarea.value, Object.assign(defaultOptions, props.options as any));
monacoEditorIns = monaco.editor.create(monacoTextareaRef.value, Object.assign(defaultOptions, props.options as any));
// 监听内容改变,双向绑定
monacoEditorIns.onDidChangeModelContent(() => {
emit('update:modelValue', monacoEditorIns.getModel()?.getValue());
modelValue.value = monacoEditorIns.getModel()?.getValue();
});
};

View File

@@ -0,0 +1,66 @@
import { VNode, h, render } from 'vue';
import MonacoEditorDialog from './MonacoEditorDialog.vue';
import * as monaco from 'monaco-editor';
export type MonacoEditorDialogProps = {
content: string;
title: string;
language: string;
height?: string;
width?: string;
options?: any; // 可选项,如字体大小等
canChangeLang?: boolean; // 是否可以切换语言
showConfirmButton?: boolean;
confirmFn?: Function; // 点击确认的回调函数入参editor value
closeFn?: Function; // 点击取消 或 关闭弹窗的回调函数
completionItemProvider?: monaco.languages.CompletionItemProvider; // 自定义补全项
};
const MonacoEditorBox = (props: MonacoEditorDialogProps): void => {
const boxId = 'monaco-editor-dialog-id';
let boxInstance: VNode;
const container = document.getElementById(boxId);
if (!container) {
const container = document.createElement('div');
container.id = boxId;
if (props.showConfirmButton === undefined) {
props.showConfirmButton = true;
}
if (props.canChangeLang === undefined) {
props.canChangeLang = true;
}
// 创建 虚拟dom
boxInstance = h(MonacoEditorDialog, {
...props,
modelValue: props.content,
'onUpdate:modelValue': (value: string) => {
props.content = value;
},
// 'onUpdate:visible': (value: boolean) => {},
visible: true,
onClose: () => {
// 卸载组件
if (boxInstance) {
render(null, container);
boxInstance = null as any;
}
// 移除 container DOM 元素
document.body.removeChild(container);
props.closeFn && props.closeFn();
console.log('close editor');
},
onConfirm: () => {
props.confirmFn && props.confirmFn(props.content);
},
});
// 将虚拟dom渲染到 container dom 上
render(boxInstance, container);
// 最后将 container 追加到 body 上
document.body.appendChild(container);
}
};
export default MonacoEditorBox;

View File

@@ -1,39 +0,0 @@
import { VNode, h, render } from 'vue';
import MonacoEditorDialogComp from './MonacoEditorDialogComp.vue';
export type MonacoEditorDialogProps = {
content: string;
title: string;
language: string;
height?: string;
width?: string;
showConfirmButton?: boolean;
confirmFn?: Function; // 点击确认的回调函数入参editor value
cancelFn?: Function; // 点击取消 或 关闭弹窗的回调函数
};
const boxId = 'monaco-editor-dialog-id';
let boxInstance: VNode;
const MonacoEditorDialog = (props: MonacoEditorDialogProps): void => {
const container = document.getElementById(boxId);
if (!container) {
const container = document.createElement('div');
container.id = boxId;
// 创建 虚拟dom
boxInstance = h(MonacoEditorDialogComp);
// 将虚拟dom渲染到 container dom 上
render(boxInstance, container);
// 最后将 container 追加到 body 上
document.body.appendChild(container);
}
const boxVue = boxInstance.component;
if (boxVue) {
// 调用open方法显示弹框注意不能使用boxVue.ctx来调用组件函数build打包后ctx会获取不到
boxVue.exposed?.open(props);
}
};
export default MonacoEditorDialog;

View File

@@ -0,0 +1,133 @@
<template>
<div>
<el-dialog :title="props.title" v-model="dialogVisible" :width="props.width" @close="close">
<monaco-editor
ref="editorRef"
:height="props.height"
class="editor"
:language="props.language"
v-model="modelValue"
:options="props.options"
:can-change-mode="props.canChangeLang"
/>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">{{ i18n.global.t('common.cancel') }}</el-button>
<el-button v-if="props.showConfirmButton" @click="confirm" type="primary">{{ i18n.global.t('common.confirm') }}</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue';
import { ElDialog, ElButton, ElMessage } from 'element-plus';
// import base style
import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
import { MonacoEditorDialogProps } from './MonacoEditorBox';
import { i18n } from '@/i18n';
import { registerCompletionItemProvider } from './completionItemProvider';
const editorRef: any = ref(null);
const props = defineProps<MonacoEditorDialogProps>();
const modelValue = defineModel<string>('modelValue', {
type: String,
default: '',
});
const dialogVisible = defineModel<boolean>('visible', {
type: Boolean,
default: false,
});
const emit = defineEmits(['close', 'confirm']);
watch(
() => props.language,
() => {
// 格式化输出html;
const language = props.language;
if (language === 'html' || language == 'xml') {
modelValue.value = formatXML(modelValue.value);
}
if (props.completionItemProvider) {
registerCompletionItemProvider(language, props.completionItemProvider);
}
setTimeout(() => {
editorRef.value?.focus();
editorRef.value?.format();
}, 300);
},
{ immediate: true }
);
/**
* 确认按钮
*/
const confirm = async () => {
let value = modelValue.value;
if (props.language === 'json') {
let val;
try {
val = JSON.parse(value);
if (typeof val !== 'object') {
ElMessage.error('请输入正确的json');
return;
}
} catch (e) {
ElMessage.error('请输入正确的json');
return;
}
// 压缩json字符串
value = JSON.stringify(val);
} else if (props.language === 'html') {
// 压缩html字符串
value = compressHTML(value);
}
emit('confirm', value);
close();
};
const close = () => {
dialogVisible.value = false;
emit('close');
setTimeout(() => {
modelValue.value = '';
}, 200);
};
const formatXML = function (xml: string, tab?: string) {
let formatted = '',
indent = '';
tab = tab || ' ';
xml.split(/>\s*</).forEach(function (node) {
if (node.match(/^\/\w/)) indent = indent.substring(tab!.length);
formatted += indent + '<' + node + '>\r\n';
if (node.match(/^<?\w[^>]*[^\/]$/)) indent += tab;
});
return formatted.substring(1, formatted.length - 3);
};
function compressHTML(html: string) {
return (
html
.replace(/[\r\n\t]+/g, ' ') // 移除换行符和制表符
// .replace(/<!--[\s\S]*?-->/g, '') // 移除注释
.replace(/\s{2,}/g, ' ') // 合并多个空格为一个空格
.replace(/>\s+</g, '><')
); // 移除标签之间的空格
}
</script>
<style lang="scss" scoped>
.editor {
font-size: 9pt;
font-weight: 600;
}
</style>

View File

@@ -1,144 +0,0 @@
<template>
<div>
<el-dialog :title="state.title" v-model="state.dialogVisible" :width="state.width" @close="cancel">
<monaco-editor ref="editorRef" :height="state.height" class="editor" :language="state.language" v-model="contentValue" can-change-mode />
<template #footer>
<span class="dialog-footer">
<el-button @click="cancel">{{ i18n.global.t('common.cancel') }}</el-button>
<el-button v-if="state.showConfirmButton" @click="confirm" type="primary">{{ i18n.global.t('common.confirm') }}</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { toRefs, ref, reactive } from 'vue';
import { ElDialog, ElButton, ElMessage } from 'element-plus';
// import base style
import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
import { MonacoEditorDialogProps } from './MonacoEditorDialog';
import { i18n } from '@/i18n';
const editorRef: any = ref(null);
const state = reactive({
dialogVisible: false,
height: '450px',
width: '800px',
contentValue: '',
title: '',
language: '',
showConfirmButton: true,
});
let confirmFn: any;
let cancelFn: any;
const { contentValue } = toRefs(state);
function compressHTML(html: string) {
return (
html
.replace(/[\r\n\t]+/g, ' ') // 移除换行符和制表符
// .replace(/<!--[\s\S]*?-->/g, '') // 移除注释
.replace(/\s{2,}/g, ' ') // 合并多个空格为一个空格
.replace(/>\s+</g, '><')
); // 移除标签之间的空格
}
/**
* 确认按钮
*/
const confirm = async () => {
if (confirmFn) {
if (state.language === 'json') {
let val;
try {
val = JSON.parse(contentValue.value);
if (typeof val !== 'object') {
ElMessage.error('请输入正确的json');
return;
}
} catch (e) {
ElMessage.error('请输入正确的json');
return;
}
// 压缩json字符串
confirmFn(JSON.stringify(val));
} else if (state.language === 'html') {
// 压缩html字符串
confirmFn(compressHTML(contentValue.value));
} else {
confirmFn(contentValue.value);
}
}
state.dialogVisible = false;
setTimeout(() => {
state.contentValue = '';
state.title = '';
}, 200);
};
const cancel = () => {
state.dialogVisible = false;
// 没有执行成功,并且取消回调函数存在,则执行
cancelFn && cancelFn();
setTimeout(() => {
state.contentValue = '';
state.title = '';
}, 200);
};
const formatXML = function (xml: string, tab?: string) {
let formatted = '',
indent = '';
tab = tab || ' ';
xml.split(/>\s*</).forEach(function (node) {
if (node.match(/^\/\w/)) indent = indent.substring(tab!.length);
formatted += indent + '<' + node + '>\r\n';
if (node.match(/^<?\w[^>]*[^\/]$/)) indent += tab;
});
return formatted.substring(1, formatted.length - 3);
};
const open = (optionProps: MonacoEditorDialogProps) => {
confirmFn = optionProps.confirmFn;
cancelFn = optionProps.cancelFn;
if (optionProps.showConfirmButton === undefined) {
state.showConfirmButton = true;
} else {
state.showConfirmButton = optionProps.showConfirmButton;
}
const language = optionProps.language;
state.language = language;
state.title = optionProps.title;
if (optionProps.height) {
state.height = optionProps.height;
}
state.contentValue = optionProps.content;
// 格式化输出html;
if (language === 'html' || language == 'xml') {
state.contentValue = formatXML(optionProps.content);
}
setTimeout(() => {
editorRef.value?.focus();
editorRef.value?.format();
}, 300);
state.dialogVisible = true;
};
defineExpose({ open });
</script>
<style lang="scss" scoped>
.editor {
font-size: 9pt;
font-weight: 600;
}
</style>

View File

@@ -444,5 +444,9 @@ export default {
last5runTimes: 'Last 5 running times',
calculationing: 'In the calculation result',
},
iconSelector: {
title: 'please select the icon',
placeholder: 'please enter content search icon or select icon',
},
},
};

View File

@@ -452,5 +452,9 @@ export default {
last5runTimes: '最近5次运行时间',
calculationing: '计算结果中',
},
iconSelector: {
title: '请选择图标',
placeholder: '请输入内容搜索图标或者选择图标',
},
},
};

View File

@@ -88,8 +88,8 @@
}
// 第三方图标字体间距/大小设置
.el-menu-item .iconfont,
.el-sub-menu .iconfont,
.el-menu-item .icon,
.el-sub-menu .icon,
.el-menu-item .fa,
.el-sub-menu .fa {
@include mixins.generalIcon;

View File

@@ -58,7 +58,7 @@
</el-form-item>
<el-form-item prop="dataSql" :label="$t('db.srcDataSql')" required>
<monaco-editor height="150px" class="task-sql" language="sql" v-model="form.dataSql" />
<monaco-editor height="200px" class="task-sql" language="sql" v-model="form.dataSql" />
</el-form-item>
<el-form-item>
@@ -90,8 +90,8 @@
</el-form-item>
<el-form-item>
<el-row>
<el-col :span="8">
<el-row class="w100">
<el-col :span="12">
<el-form-item class="w100" prop="updField">
<template #label>
{{ $t('db.updateField') }}
@@ -103,7 +103,7 @@
</el-form-item>
</el-col>
<el-col :span="8">
<el-col :span="12">
<el-form-item class="w100" prop="updFieldVal">
<template #label>
{{ $t('db.updateFieldValue') }}
@@ -116,8 +116,12 @@
<el-input v-model.trim="form.updFieldVal" :placeholder="$t('db.updateFieldValuePlaceholder')" auto-complete="off" />
</el-form-item>
</el-col>
</el-row>
</el-form-item>
<el-col :span="8">
<el-form-item>
<el-row class="w100">
<el-col :span="12">
<el-form-item class="w100" prop="updFieldSrc">
<template #label>
{{ $t('db.fieldValueSrc') }}
@@ -136,7 +140,7 @@
<el-tab-pane :label="$t('db.fieldMap')" :name="fieldTab" :disabled="!baseFieldCompleted">
<el-form-item prop="fieldMap" :label="$t('db.fieldMap')" required>
<el-table :data="form.fieldMap" :max-height="650" size="small">
<el-table :data="form.fieldMap" :max-height="fieldMapTableHeight" size="small">
<el-table-column prop="src" :label="$t('db.srcField')" :width="200" />
<el-table-column prop="target" :label="$t('db.targetField')">
<template #default="scope">
@@ -159,10 +163,10 @@
<EnumSelect :enums="DbDataSyncDuplicateStrategyEnum" v-model="form.duplicateStrategy" @change="handleDuplicateStrategy" />
</el-form-item>
<el-form-item prop="fieldMap" :label="$t('db.selectSql')">
<el-input type="textarea" v-model="state.previewDataSql" readonly :input-style="{ height: '300px' }" />
<el-input type="textarea" v-model="state.previewDataSql" readonly :rows="10" />
</el-form-item>
<el-form-item prop="fieldMap" :label="$t('db.insertSql')">
<el-input type="textarea" v-model="state.previewInsertSql" readonly :input-style="{ height: '300px' }" />
<el-input type="textarea" v-model="state.previewInsertSql" readonly :rows="10" />
</el-form-item>
</el-tab-pane>
</el-tabs>
@@ -327,9 +331,10 @@ const state = reactive({
previewDataSql: '',
previewInsertSql: '',
previewFieldArr: [] as string[],
fieldMapTableHeight: window.innerHeight - 50,
});
const { tabActiveName, form, submitForm } = toRefs(state);
const { tabActiveName, form, submitForm, fieldMapTableHeight } = toRefs(state);
const { isFetching: saveBtnLoading, execute: saveExec } = dbApi.saveDatasyncTask.useApi(submitForm);

View File

@@ -89,7 +89,7 @@ import { computed, nextTick, ref, Ref } from 'vue';
import { ElInput, ElMessage } from 'element-plus';
import { DataType } from '../../dialect/index';
import SvgIcon from '@/components/svgIcon/index.vue';
import MonacoEditorDialog from '@/components/monaco/MonacoEditorDialog';
import MonacoEditorBox from '@/components/monaco/MonacoEditorBox';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
@@ -123,7 +123,7 @@ const openEditor = () => {
editorOpening.value = true;
// 编辑器语言json、html、text
let editorLang = getEditorLangByValue(itemValue.value);
MonacoEditorDialog({
MonacoEditorBox({
content: itemValue.value,
title: `${t('db.editField')} [${props.columnName}]`,
language: editorLang,
@@ -131,7 +131,7 @@ const openEditor = () => {
itemValue.value = newVal;
closeEditorDialog();
},
cancelFn: closeEditorDialog,
closeFn: closeEditorDialog,
});
};

View File

@@ -319,7 +319,7 @@ const columns = [
TableColumn.new('name', 'common.name'),
TableColumn.new('ipPort', 'Ip:Port').isSlot().setAddWidth(50),
TableColumn.new('authCerts[0].username', 'machine.acName').isSlot('authCert').setAddWidth(10),
TableColumn.new('status', 'common.status').isSlot(),
TableColumn.new('status', 'common.status').isSlot().setAddWidth(5),
TableColumn.new('stat', 'machine.runningStat').isSlot().setAddWidth(55),
TableColumn.new('fs', 'machine.fs').isSlot().setAddWidth(25),
TableColumn.new('remark', 'common.remark'),

View File

@@ -32,8 +32,8 @@ require (
github.com/tidwall/gjson v1.18.0
github.com/veops/go-ansiterm v0.0.5
go.mongodb.org/mongo-driver v1.16.0 // mongo
golang.org/x/crypto v0.31.0 // ssh
golang.org/x/oauth2 v0.24.0
golang.org/x/crypto v0.32.0 // ssh
golang.org/x/oauth2 v0.25.0
golang.org/x/sync v0.10.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
gopkg.in/yaml.v3 v3.0.1
@@ -93,7 +93,7 @@ require (
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // indirect
golang.org/x/image v0.13.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/text v0.21.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
modernc.org/libc v1.22.5 // indirect

View File

@@ -21,6 +21,7 @@ import (
"time"
"github.com/google/uuid"
"github.com/may-fly/cast"
)
type DataSyncTask interface {
@@ -348,7 +349,7 @@ func (app *dataSyncAppImpl) srcData2TargetDb(srcRes []map[string]any, fieldMap [
updFieldVal = srcRes[len(srcRes)-1][strings.ToLower(field)]
}
task.UpdFieldVal = updFieldType.DataType.SQLValue(updFieldVal)
task.UpdFieldVal = cast.ToString(updFieldVal)
}
// 如果指定了更新字段,则以更新字段取值

View File

@@ -479,7 +479,7 @@ func (d *dbSqlExecAppImpl) doUpdate(ctx context.Context, sqlExecParam *sqlExecPa
nowRec++
res = append(res, row)
if nowRec == maxRec {
return errorx.NewBiz(fmt.Sprintf("update SQL - the maximum number of updated queries is exceeded: %d", maxRec))
return errorx.NewBiz("update SQL - the maximum number of updated queries is exceeded: %d", maxRec)
}
return nil
})
@@ -535,12 +535,13 @@ func (d *dbSqlExecAppImpl) doDelete(ctx context.Context, sqlExecParam *sqlExecPa
}
execRecord.Table = tableName
whereStr := deletestmt.Where.GetText()
if whereStr == "" {
deleteWhere := deletestmt.Where
if deleteWhere == nil {
logx.ErrorContext(ctx, "delete SQL - there is no where condition")
return d.doExec(ctx, dbConn, sqlExecParam.Sql)
}
whereStr := deleteWhere.GetText()
// 查询删除数据
selectSql := fmt.Sprintf("SELECT * FROM %s where %s LIMIT 200", tableName+" "+tableAlias, whereStr)
_, res, _ := dbConn.QueryContext(ctx, selectSql)

View File

@@ -67,13 +67,13 @@ func (dbInfo *DbInfo) Conn(meta Meta) (*DbConn, error) {
conn, err := meta.GetSqlDb(dbInfo)
if err != nil {
logx.Errorf("db connection failed: %s:%d/%s, err:%s", dbInfo.Host, dbInfo.Port, database, err.Error())
return nil, errorx.NewBiz(fmt.Sprintf("db connection failed: %s", err.Error()))
return nil, errorx.NewBiz("db connection failed: %s", err.Error())
}
err = conn.Ping()
if err != nil {
logx.Errorf("db ping failed: %s:%d/%s, err:%s", dbInfo.Host, dbInfo.Port, database, err.Error())
return nil, errorx.NewBiz(fmt.Sprintf("db connection failed: %s", err.Error()))
return nil, errorx.NewBiz("db connection failed: %s", err.Error())
}
dbc := &DbConn{Id: GetDbConnId(dbInfo.Id, database), Info: dbInfo}

View File

@@ -279,7 +279,7 @@ func (m *machineFileAppImpl) WriteFileContent(ctx context.Context, opParam *dto.
return nil, err
}
f, err := sftpCli.OpenFile(path, os.O_RDWR)
f, err := sftpCli.OpenFile(path, os.O_RDWR|os.O_APPEND|os.O_TRUNC)
if err != nil {
return mi, err
}