mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-04 00:10:25 +08:00
feat: dbms新增支持工单流程审批
This commit is contained in:
@@ -33,7 +33,7 @@
|
||||
"splitpanes": "^3.1.5",
|
||||
"sql-formatter": "^15.0.2",
|
||||
"uuid": "^9.0.1",
|
||||
"vue": "^3.4.19",
|
||||
"vue": "^3.4.21",
|
||||
"vue-router": "^4.3.0",
|
||||
"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.4",
|
||||
"@vue/compiler-sfc": "^3.4.19",
|
||||
"@vue/compiler-sfc": "^3.4.21",
|
||||
"code-inspector-plugin": "^0.4.5",
|
||||
"dotenv": "^16.3.1",
|
||||
"eslint": "^8.35.0",
|
||||
|
||||
@@ -15,7 +15,7 @@ const config = {
|
||||
baseWsUrl: `${(window as any).globalConfig.BaseWsUrl || `${location.protocol == 'https:' ? 'wss:' : 'ws:'}//${getBaseApiUrl()}`}/api`,
|
||||
|
||||
// 系统版本
|
||||
version: 'v1.7.3',
|
||||
version: 'v1.7.4',
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
||||
@@ -46,60 +46,6 @@ export function convertToBytes(sizeStr: string) {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化json字符串
|
||||
* @param txt json字符串
|
||||
* @param compress 是否压缩
|
||||
* @returns 格式化后的字符串
|
||||
*/
|
||||
export function formatJsonString(txt: string, compress: boolean) {
|
||||
var indentChar = ' ';
|
||||
if (/^\s*$/.test(txt)) {
|
||||
console.log('数据为空,无法格式化! ');
|
||||
return txt;
|
||||
}
|
||||
try {
|
||||
var data = JSON.parse(txt);
|
||||
} catch (e: any) {
|
||||
console.log('数据源语法错误,格式化失败! 错误信息: ' + e.description, 'err');
|
||||
return txt;
|
||||
}
|
||||
var draw: any = [],
|
||||
line = compress ? '' : '\n',
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
nodeCount: number = 0,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
maxDepth: number = 0;
|
||||
|
||||
var notify = function (name: any, value: any, isLast: any, indent: any, formObj: any) {
|
||||
nodeCount++; /*节点计数*/
|
||||
for (var i = 0, tab = ''; i < indent; i++) tab += indentChar; /* 缩进HTML */
|
||||
tab = compress ? '' : tab; /*压缩模式忽略缩进*/
|
||||
maxDepth = ++indent; /*缩进递增并记录*/
|
||||
if (value && value.constructor == Array) {
|
||||
/*处理数组*/
|
||||
draw.push(tab + (formObj ? '"' + name + '": ' : '') + '[' + line); /*缩进'[' 然后换行*/
|
||||
for (var i = 0; i < value.length; i++) notify(i, value[i], i == value.length - 1, indent, false);
|
||||
draw.push(tab + ']' + (isLast ? line : ',' + line)); /*缩进']'换行,若非尾元素则添加逗号*/
|
||||
} else if (value && typeof value == 'object') {
|
||||
/*处理对象*/
|
||||
draw.push(tab + (formObj ? '"' + name + '": ' : '') + '{' + line); /*缩进'{' 然后换行*/
|
||||
var len = 0,
|
||||
i = 0;
|
||||
for (var key in value) len++;
|
||||
for (var key in value) notify(key, value[key], ++i == len, indent, true);
|
||||
draw.push(tab + '}' + (isLast ? line : ',' + line)); /*缩进'}'换行,若非尾元素则添加逗号*/
|
||||
} else {
|
||||
if (typeof value == 'string') value = '"' + value + '"';
|
||||
draw.push(tab + (formObj ? '"' + name + '": ' : '') + value + (isLast ? '' : ',') + line);
|
||||
}
|
||||
};
|
||||
var isLast = true,
|
||||
indent = 0;
|
||||
notify('', data, isLast, indent, false);
|
||||
return draw.join('');
|
||||
}
|
||||
|
||||
/*
|
||||
* 年(Y) 可用1-4个占位符
|
||||
* 月(m)、日(d)、小时(H)、分(M)、秒(S) 可用1-2个占位符
|
||||
@@ -204,6 +150,45 @@ export function formatPast(param: any, format: string = 'YYYY-mm-dd') {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化指定时间数为人性化可阅读的内容(默认time为秒单位)
|
||||
*
|
||||
* @param time 时间数
|
||||
* @param unit time对应的单位
|
||||
* @returns
|
||||
*/
|
||||
export function formatTime(time: number, unit: string = 's') {
|
||||
const units = {
|
||||
y: 31536000,
|
||||
M: 2592000,
|
||||
d: 86400,
|
||||
h: 3600,
|
||||
m: 60,
|
||||
s: 1,
|
||||
};
|
||||
|
||||
if (!units[unit]) {
|
||||
return 'Invalid unit';
|
||||
}
|
||||
|
||||
let seconds = time * units[unit];
|
||||
let result = '';
|
||||
|
||||
const timeUnits = Object.entries(units).map(([unit, duration]) => {
|
||||
const value = Math.floor(seconds / duration);
|
||||
seconds %= duration;
|
||||
return { value, unit };
|
||||
});
|
||||
|
||||
timeUnits.forEach(({ value, unit }) => {
|
||||
if (value > 0) {
|
||||
result += `${value}${unit} `;
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* formatAxis(new Date()) // 上午好
|
||||
*/
|
||||
|
||||
27
mayfly_go_web/src/common/utils/object.ts
Normal file
27
mayfly_go_web/src/common/utils/object.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
// 根据对象访问路径,获取对应的值
|
||||
export function getValueByPath(obj: any, path: string) {
|
||||
const keys = path.split('.');
|
||||
let result = obj;
|
||||
for (let key of keys) {
|
||||
if (!result || typeof result !== 'object') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (key.includes('[') && key.includes(']')) {
|
||||
// 处理包含数组索引的情况
|
||||
const arrayKey = key.substring(0, key.indexOf('['));
|
||||
const matchIndex = key.match(/\[(.*?)\]/);
|
||||
|
||||
if (!matchIndex) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const index = parseInt(matchIndex[1]);
|
||||
result = Array.isArray(result[arrayKey]) ? result[arrayKey][index] : undefined;
|
||||
} else {
|
||||
result = result[key];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
33
mayfly_go_web/src/components/drawer-header/DrawerHeader.vue
Normal file
33
mayfly_go_web/src/components/drawer-header/DrawerHeader.vue
Normal file
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<el-page-header @back="props.back">
|
||||
<template #content>
|
||||
<span>{{ header }}</span>
|
||||
<span v-if="resource && !hideResource">
|
||||
-
|
||||
<el-tooltip v-if="resource.length > 25" :content="resource" placement="bottom">
|
||||
<el-tag effect="dark" type="success">{{ resource.substring(0, 23) + '...' }}</el-tag>
|
||||
</el-tooltip>
|
||||
<el-tag v-else effect="dark" type="success">{{ resource }}</el-tag>
|
||||
</span>
|
||||
<el-divider v-if="slots.buttons" direction="vertical" />
|
||||
<slot v-if="slots.buttons" name="buttons"></slot>
|
||||
</template>
|
||||
<template #extra>
|
||||
<slot v-if="slots.extra" name="extra"></slot>
|
||||
</template>
|
||||
</el-page-header>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useSlots } from 'vue';
|
||||
const slots = useSlots();
|
||||
|
||||
defineOptions({ name: 'DrawerHeader' });
|
||||
|
||||
const props = defineProps({
|
||||
header: String,
|
||||
back: Function,
|
||||
resource: String,
|
||||
hideResource: Boolean,
|
||||
});
|
||||
</script>
|
||||
@@ -115,18 +115,18 @@
|
||||
>
|
||||
<!-- 插槽:预留功能 -->
|
||||
<template #default="scope" v-if="item.slot">
|
||||
<slot :name="item.prop" :data="scope.row"></slot>
|
||||
<slot :name="item.slotName ? item.slotName : item.prop" :data="scope.row"></slot>
|
||||
</template>
|
||||
|
||||
<!-- 枚举类型使用tab展示 -->
|
||||
<template #default="scope" v-else-if="item.type == 'tag'">
|
||||
<enum-tag :size="props.size" :enums="item.typeParam" :value="scope.row[item.prop]"></enum-tag>
|
||||
<enum-tag :size="props.size" :enums="item.typeParam" :value="item.getValueByData(scope.row)"></enum-tag>
|
||||
</template>
|
||||
|
||||
<template #default="scope" v-else>
|
||||
<!-- 配置了美化文本按钮以及文本内容大于指定长度,则显示美化按钮 -->
|
||||
<el-popover
|
||||
v-if="item.isBeautify && scope.row[item.prop]?.length > 35"
|
||||
v-if="item.isBeautify && item.getValueByData(scope.row)?.length > 35"
|
||||
effect="light"
|
||||
trigger="click"
|
||||
placement="top"
|
||||
@@ -137,7 +137,7 @@
|
||||
</template>
|
||||
<template #reference>
|
||||
<el-link
|
||||
@click="formatText(scope.row[item.prop])"
|
||||
@click="formatText(item.getValueByData(scope.row))"
|
||||
:underline="false"
|
||||
type="success"
|
||||
icon="MagicStick"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import EnumValue from '@/common/Enum';
|
||||
import { dateFormat } from '@/common/utils/date';
|
||||
import { getValueByPath } from '@/common/utils/object';
|
||||
import { getTextWidth } from '@/common/utils/string';
|
||||
|
||||
export class TableColumn {
|
||||
@@ -29,10 +30,15 @@ export class TableColumn {
|
||||
minWidth: number | string;
|
||||
|
||||
/**
|
||||
* 是否插槽,是的话插槽名则为prop属性名
|
||||
* 是否为插槽,若slotName为空则插槽名为prop属性名
|
||||
*/
|
||||
slot: boolean = false;
|
||||
|
||||
/**
|
||||
* 插槽名,
|
||||
*/
|
||||
slotName: string = '';
|
||||
|
||||
showOverflowTooltip: boolean = true;
|
||||
|
||||
sortable: boolean = false;
|
||||
@@ -87,7 +93,7 @@ export class TableColumn {
|
||||
if (this.formatFunc) {
|
||||
return this.formatFunc(rowData, this.prop);
|
||||
}
|
||||
return rowData[this.prop];
|
||||
return getValueByPath(rowData, this.prop);
|
||||
}
|
||||
|
||||
static new(prop: string, label: string): TableColumn {
|
||||
@@ -144,8 +150,9 @@ export class TableColumn {
|
||||
* 标识该列为插槽
|
||||
* @returns this
|
||||
*/
|
||||
isSlot(): TableColumn {
|
||||
isSlot(slotName: string = ''): TableColumn {
|
||||
this.slot = true;
|
||||
this.slotName = slotName;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -165,7 +172,7 @@ export class TableColumn {
|
||||
*/
|
||||
isTime(): TableColumn {
|
||||
this.setFormatFunc((data: any, prop: string) => {
|
||||
return dateFormat(data[prop]);
|
||||
return dateFormat(getValueByPath(data, prop));
|
||||
});
|
||||
return this;
|
||||
}
|
||||
@@ -176,7 +183,7 @@ export class TableColumn {
|
||||
*/
|
||||
isEnum(enums: any): TableColumn {
|
||||
this.setFormatFunc((data: any, prop: string) => {
|
||||
return EnumValue.getLabelByValue(enums, data[prop]);
|
||||
return EnumValue.getLabelByValue(enums, getValueByPath(data, prop));
|
||||
});
|
||||
return this;
|
||||
}
|
||||
@@ -218,7 +225,7 @@ export class TableColumn {
|
||||
// 获取该列中最长的数据(内容)
|
||||
for (let i = 0; i < tableData.length; i++) {
|
||||
let nowData = tableData[i];
|
||||
let nowValue = nowData[prop];
|
||||
let nowValue = getValueByPath(nowData, prop);
|
||||
if (!nowValue) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div id="terminal-body" :style="{ height, background: themeConfig.terminalBackground }">
|
||||
<div id="terminal-body" :style="{ height }">
|
||||
<div ref="terminalRef" class="terminal" />
|
||||
|
||||
<TerminalSearch ref="terminalSearchRef" :search-addon="state.addon.search" @close="focus" />
|
||||
@@ -23,6 +23,11 @@ import { useEventListener } from '@vueuse/core';
|
||||
import themes from './themes';
|
||||
|
||||
const props = defineProps({
|
||||
// mounted时,是否执行init方法
|
||||
mountInit: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
/**
|
||||
* 初始化执行命令
|
||||
*/
|
||||
@@ -65,9 +70,9 @@ const state = reactive({
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
if (props.mountInit) {
|
||||
init();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
watch(
|
||||
@@ -94,6 +99,13 @@ function init() {
|
||||
console.log('重新连接...');
|
||||
close();
|
||||
}
|
||||
nextTick(() => {
|
||||
initTerm();
|
||||
initSocket();
|
||||
});
|
||||
}
|
||||
|
||||
function initTerm() {
|
||||
term = new Terminal({
|
||||
fontSize: themeConfig.value.terminalFontSize || 15,
|
||||
fontWeight: themeConfig.value.terminalFontWeight || 'normal',
|
||||
@@ -107,25 +119,11 @@ function init() {
|
||||
|
||||
term.open(terminalRef.value);
|
||||
|
||||
// 注册 terminal 事件
|
||||
term.onResize((event) => sendResize(event.cols, event.rows));
|
||||
term.onData((event) => sendCmd(event));
|
||||
|
||||
// 注册自定义快捷键
|
||||
term.attachCustomKeyEventHandler((event: KeyboardEvent) => {
|
||||
// 注册搜索键 ctrl + f
|
||||
if (event.key === 'f' && (event.ctrlKey || event.metaKey) && event.type === 'keydown') {
|
||||
event.preventDefault();
|
||||
terminalSearchRef.value.open();
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
// 注册自适应组件
|
||||
const fitAddon = new FitAddon();
|
||||
state.addon.fit = fitAddon;
|
||||
term.loadAddon(fitAddon);
|
||||
fitTerminal();
|
||||
|
||||
// 注册搜索组件
|
||||
const searchAddon = new SearchAddon();
|
||||
@@ -137,12 +135,21 @@ function init() {
|
||||
state.addon.weblinks = weblinks;
|
||||
term.loadAddon(weblinks);
|
||||
|
||||
initSocket();
|
||||
// 注册自定义快捷键
|
||||
term.attachCustomKeyEventHandler((event: KeyboardEvent) => {
|
||||
// 注册搜索键 ctrl + f
|
||||
if (event.key === 'f' && (event.ctrlKey || event.metaKey) && event.type === 'keydown') {
|
||||
event.preventDefault();
|
||||
terminalSearchRef.value.open();
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
function initSocket() {
|
||||
if (props.socketUrl) {
|
||||
socket = new WebSocket(`${props.socketUrl}`);
|
||||
socket = new WebSocket(`${props.socketUrl}&rows=${term?.rows}&cols=${term?.cols}`);
|
||||
}
|
||||
|
||||
// 监听socket连接
|
||||
@@ -151,11 +158,13 @@ function initSocket() {
|
||||
pingInterval = setInterval(sendPing, 15000);
|
||||
state.status = TerminalStatus.Connected;
|
||||
|
||||
// 注册 terminal 事件
|
||||
term.onResize((event) => sendResize(event.cols, event.rows));
|
||||
term.onData((event) => sendCmd(event));
|
||||
|
||||
// // 注册窗口大小监听器
|
||||
useEventListener('resize', debounce(fitTerminal, 400));
|
||||
focus();
|
||||
fitTerminal();
|
||||
sendResize(term.cols, term.rows);
|
||||
|
||||
// 如果有初始要执行的命令,则发送执行命令
|
||||
if (props.cmd) {
|
||||
@@ -261,7 +270,6 @@ defineExpose({ init, fitTerminal, focus, clear, close, getStatus, sendResize });
|
||||
</script>
|
||||
<style lang="scss">
|
||||
#terminal-body {
|
||||
background: #212529;
|
||||
width: 100%;
|
||||
|
||||
.terminal {
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="terminal-wrapper" :style="{ height: `calc(100vh - ${openTerminal.fullscreen ? '49px' : '200px'})` }">
|
||||
<div :style="{ height: `calc(100vh - ${openTerminal.fullscreen ? '49px' : '200px'})` }">
|
||||
<TerminalBody
|
||||
@status-change="terminalStatusChange(openTerminal.terminalId, $event)"
|
||||
:ref="(el) => setTerminalRef(el, openTerminal.terminalId)"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Api from '@/common/Api';
|
||||
import { isReactive, reactive, toRefs, toValue } from 'vue';
|
||||
import { reactive, toRefs, toValue } from 'vue';
|
||||
|
||||
/**
|
||||
* @description table 页面操作方法封装
|
||||
@@ -41,12 +41,6 @@ export const usePageTable = (
|
||||
let sp = toValue(state.searchParams);
|
||||
if (beforeQueryFn) {
|
||||
sp = beforeQueryFn(sp);
|
||||
|
||||
if (isReactive(state.searchParams)) {
|
||||
state.searchParams.value = sp;
|
||||
} else {
|
||||
state.searchParams = sp;
|
||||
}
|
||||
}
|
||||
|
||||
let res = await api.request(sp);
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
<template>
|
||||
<div class="layout-navbars-breadcrumb" v-show="themeConfig.isBreadcrumb">
|
||||
<SvgIcon class="layout-navbars-breadcrumb-icon" :name="themeConfig.isCollapse ? 'expand' : 'fold'"
|
||||
@click="onThemeConfigChange" />
|
||||
<SvgIcon class="layout-navbars-breadcrumb-icon" :name="themeConfig.isCollapse ? 'expand' : 'fold'" @click="onThemeConfigChange" />
|
||||
<el-breadcrumb class="layout-navbars-breadcrumb-hide">
|
||||
<transition-group name="breadcrumb" mode="out-in">
|
||||
<el-breadcrumb-item v-for="(v, k) in state.breadcrumbList" :key="v.meta.title">
|
||||
<span v-if="k === state.breadcrumbList.length - 1" class="layout-navbars-breadcrumb-span">
|
||||
<SvgIcon :name="v.meta.icon" class="layout-navbars-breadcrumb-iconfont"
|
||||
v-if="themeConfig.isBreadcrumbIcon" />
|
||||
<SvgIcon :name="v.meta.icon" class="layout-navbars-breadcrumb-iconfont" v-if="themeConfig.isBreadcrumbIcon" />
|
||||
{{ v.meta.title }}
|
||||
</span>
|
||||
<a v-else @click.prevent="onBreadcrumbClick(v)">
|
||||
<SvgIcon :name="v.meta.icon" class="layout-navbars-breadcrumb-iconfont"
|
||||
v-if="themeConfig.isBreadcrumbIcon" />
|
||||
<SvgIcon :name="v.meta.icon" class="layout-navbars-breadcrumb-iconfont" v-if="themeConfig.isBreadcrumbIcon" />
|
||||
{{ v.meta.title }}
|
||||
</a>
|
||||
</el-breadcrumb-item>
|
||||
|
||||
@@ -707,6 +707,25 @@ defineExpose({ openDrawer });
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
::v-deep(.el-drawer) {
|
||||
--el-drawer-padding-primary: unset !important;
|
||||
|
||||
.el-drawer__header {
|
||||
padding: 0 15px !important;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 0 !important;
|
||||
border-bottom: 1px solid var(--el-border-color);
|
||||
}
|
||||
|
||||
.el-drawer__body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.layout-breadcrumb-seting-bar {
|
||||
height: calc(100vh - 50px);
|
||||
padding: 0 15px;
|
||||
|
||||
@@ -284,7 +284,9 @@ const onTagsClick = (v: any, k: number) => {
|
||||
state.tagsRefsIndex = k;
|
||||
try {
|
||||
router.push(v);
|
||||
} catch (e) {}
|
||||
} catch (e) {
|
||||
// skip
|
||||
}
|
||||
};
|
||||
// 更新滚动条显示
|
||||
const updateScrollbar = () => {
|
||||
|
||||
@@ -7,335 +7,353 @@
|
||||
------------------------------- */
|
||||
// 菜单搜索
|
||||
.el-autocomplete-suggestion__wrap {
|
||||
max-height: 280px !important;
|
||||
max-height: 280px !important;
|
||||
}
|
||||
|
||||
/* Form 表单
|
||||
------------------------------- */
|
||||
.el-form {
|
||||
// 用于修改弹窗时表单内容间隔太大问题,如系统设置的新增菜单弹窗里的表单内容
|
||||
.el-form-item:last-of-type {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
// 修复行内表单最后一个 el-form-item 位置下移问题
|
||||
&.el-form--inline {
|
||||
.el-form-item--large.el-form-item:last-of-type {
|
||||
margin-bottom: 22px !important;
|
||||
}
|
||||
.el-form-item--default.el-form-item:last-of-type,
|
||||
.el-form-item--small.el-form-item:last-of-type {
|
||||
margin-bottom: 18px !important;
|
||||
}
|
||||
}
|
||||
// https://gitee.com/lyt-top/vue-next-admin/issues/I5K1PM
|
||||
.el-form-item .el-form-item__label .el-icon {
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
// 用于修改弹窗时表单内容间隔太大问题,如系统设置的新增菜单弹窗里的表单内容
|
||||
.el-form-item:last-of-type {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
// 修复行内表单最后一个 el-form-item 位置下移问题
|
||||
&.el-form--inline {
|
||||
.el-form-item--large.el-form-item:last-of-type {
|
||||
margin-bottom: 22px !important;
|
||||
}
|
||||
|
||||
.el-form-item--default.el-form-item:last-of-type,
|
||||
.el-form-item--small.el-form-item:last-of-type {
|
||||
margin-bottom: 18px !important;
|
||||
}
|
||||
}
|
||||
|
||||
// https://gitee.com/lyt-top/vue-next-admin/issues/I5K1PM
|
||||
.el-form-item .el-form-item__label .el-icon {
|
||||
margin-right: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Alert 警告
|
||||
------------------------------- */
|
||||
.el-alert {
|
||||
border: 1px solid;
|
||||
border: 1px solid;
|
||||
}
|
||||
|
||||
.el-alert__title {
|
||||
word-break: break-all;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
/* Message 消息提示
|
||||
------------------------------- */
|
||||
.el-message {
|
||||
min-width: unset !important;
|
||||
padding: 15px !important;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.02);
|
||||
min-width: unset !important;
|
||||
padding: 15px !important;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.02);
|
||||
}
|
||||
|
||||
/* NavMenu 导航菜单
|
||||
------------------------------- */
|
||||
// 鼠标 hover 时颜色
|
||||
.el-menu-hover-bg-color {
|
||||
background-color: var(--bg-menuBarActiveColor) !important;
|
||||
background-color: var(--bg-menuBarActiveColor) !important;
|
||||
}
|
||||
|
||||
// 默认样式修改
|
||||
.el-menu {
|
||||
border-right: none !important;
|
||||
width: 220px;
|
||||
border-right: none !important;
|
||||
width: 220px;
|
||||
}
|
||||
|
||||
.el-menu-item {
|
||||
height: 56px !important;
|
||||
line-height: 56px !important;
|
||||
height: 56px !important;
|
||||
line-height: 56px !important;
|
||||
}
|
||||
|
||||
.el-menu-item,
|
||||
.el-sub-menu__title {
|
||||
color: var(--bg-menuBarColor);
|
||||
color: var(--bg-menuBarColor);
|
||||
}
|
||||
|
||||
// 修复点击左侧菜单折叠再展开时,宽度不跟随问题
|
||||
.el-menu--collapse {
|
||||
width: 64px !important;
|
||||
width: 64px !important;
|
||||
}
|
||||
|
||||
// 外部链接时
|
||||
.el-menu-item a,
|
||||
.el-menu-item a:hover,
|
||||
.el-menu-item i,
|
||||
.el-sub-menu__title i {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
// 第三方图标字体间距/大小设置
|
||||
.el-menu-item .iconfont,
|
||||
.el-sub-menu .iconfont,
|
||||
.el-menu-item .fa,
|
||||
.el-sub-menu .fa {
|
||||
@include generalIcon;
|
||||
@include generalIcon;
|
||||
}
|
||||
|
||||
// 水平菜单、横向菜单高亮 背景色,鼠标 hover 时,有子级菜单的背景色
|
||||
.el-menu-item.is-active,
|
||||
.el-sub-menu.is-active .el-sub-menu__title,
|
||||
.el-sub-menu:not(.is-opened):hover .el-sub-menu__title {
|
||||
@extend .el-menu-hover-bg-color;
|
||||
@extend .el-menu-hover-bg-color;
|
||||
}
|
||||
|
||||
.el-menu-item:hover {
|
||||
@extend .el-menu-hover-bg-color;
|
||||
@extend .el-menu-hover-bg-color;
|
||||
}
|
||||
|
||||
.el-sub-menu.is-active.is-opened .el-sub-menu__title {
|
||||
background-color: unset !important;
|
||||
background-color: unset !important;
|
||||
}
|
||||
|
||||
// 子级菜单背景颜色
|
||||
// .el-menu--inline {
|
||||
// background: var(--next-bg-menuBar-light-1);
|
||||
// }
|
||||
// 水平菜单、横向菜单折叠 a 标签
|
||||
.el-popper.is-dark a {
|
||||
color: var(--el-color-white) !important;
|
||||
text-decoration: none;
|
||||
color: var(--el-color-white) !important;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
// 水平菜单、横向菜单折叠背景色
|
||||
.el-popper.is-pure.is-light {
|
||||
// 水平菜单
|
||||
.el-menu--vertical {
|
||||
background: var(--bg-menuBar);
|
||||
.el-sub-menu.is-active .el-sub-menu__title {
|
||||
color: var(--el-menu-active-color);
|
||||
}
|
||||
.el-popper.is-pure.is-light {
|
||||
.el-menu--vertical {
|
||||
.el-sub-menu .el-sub-menu__title {
|
||||
background-color: unset !important;
|
||||
color: var(--bg-menuBarColor);
|
||||
}
|
||||
.el-sub-menu.is-active .el-sub-menu__title {
|
||||
color: var(--el-menu-active-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 横向菜单
|
||||
.el-menu--horizontal {
|
||||
background: var(--bg-topBar);
|
||||
.el-menu-item,
|
||||
.el-sub-menu {
|
||||
height: 48px !important;
|
||||
line-height: 48px !important;
|
||||
color: var(--bg-topBarColor);
|
||||
.el-sub-menu__title {
|
||||
height: 48px !important;
|
||||
line-height: 48px !important;
|
||||
color: var(--bg-topBarColor);
|
||||
}
|
||||
.el-popper.is-pure.is-light {
|
||||
.el-menu--horizontal {
|
||||
.el-sub-menu .el-sub-menu__title {
|
||||
background-color: unset !important;
|
||||
color: var(--bg-topBarColor);
|
||||
}
|
||||
.el-sub-menu.is-active .el-sub-menu__title {
|
||||
color: var(--el-menu-active-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.el-menu-item.is-active,
|
||||
.el-sub-menu.is-active .el-sub-menu__title {
|
||||
color: var(--el-menu-active-color);
|
||||
}
|
||||
}
|
||||
|
||||
// 水平菜单
|
||||
.el-menu--vertical {
|
||||
background: var(--bg-menuBar);
|
||||
|
||||
.el-sub-menu.is-active .el-sub-menu__title {
|
||||
color: var(--el-menu-active-color);
|
||||
}
|
||||
|
||||
.el-popper.is-pure.is-light {
|
||||
.el-menu--vertical {
|
||||
.el-sub-menu .el-sub-menu__title {
|
||||
background-color: unset !important;
|
||||
color: var(--bg-menuBarColor);
|
||||
}
|
||||
|
||||
.el-sub-menu.is-active .el-sub-menu__title {
|
||||
color: var(--el-menu-active-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 横向菜单
|
||||
.el-menu--horizontal {
|
||||
background: var(--bg-topBar);
|
||||
|
||||
.el-menu-item,
|
||||
.el-sub-menu {
|
||||
height: 48px !important;
|
||||
line-height: 48px !important;
|
||||
color: var(--bg-topBarColor);
|
||||
|
||||
.el-sub-menu__title {
|
||||
height: 48px !important;
|
||||
line-height: 48px !important;
|
||||
color: var(--bg-topBarColor);
|
||||
}
|
||||
|
||||
.el-popper.is-pure.is-light {
|
||||
.el-menu--horizontal {
|
||||
.el-sub-menu .el-sub-menu__title {
|
||||
background-color: unset !important;
|
||||
color: var(--bg-topBarColor);
|
||||
}
|
||||
|
||||
.el-sub-menu.is-active .el-sub-menu__title {
|
||||
color: var(--el-menu-active-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-menu-item.is-active,
|
||||
.el-sub-menu.is-active .el-sub-menu__title {
|
||||
color: var(--el-menu-active-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 横向菜单(经典、横向)布局
|
||||
.el-menu.el-menu--horizontal {
|
||||
border-bottom: none !important;
|
||||
width: 100% !important;
|
||||
.el-menu-item,
|
||||
.el-sub-menu__title {
|
||||
height: 48px !important;
|
||||
color: var(--bg-topBarColor);
|
||||
}
|
||||
.el-menu-item:not(.is-active):hover,
|
||||
.el-sub-menu:not(.is-active):hover .el-sub-menu__title {
|
||||
color: var(--bg-topBarColor);
|
||||
}
|
||||
border-bottom: none !important;
|
||||
width: 100% !important;
|
||||
|
||||
.el-menu-item,
|
||||
.el-sub-menu__title {
|
||||
height: 48px !important;
|
||||
color: var(--bg-topBarColor);
|
||||
}
|
||||
|
||||
.el-menu-item:not(.is-active):hover,
|
||||
.el-sub-menu:not(.is-active):hover .el-sub-menu__title {
|
||||
color: var(--bg-topBarColor);
|
||||
}
|
||||
}
|
||||
|
||||
// 菜单收起时,图标不居中问题
|
||||
.el-menu--collapse {
|
||||
.el-menu-item .iconfont,
|
||||
.el-sub-menu .iconfont,
|
||||
.el-menu-item .fa,
|
||||
.el-sub-menu .fa {
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
.el-sub-menu__title {
|
||||
padding-right: 0 !important;
|
||||
}
|
||||
|
||||
.el-menu-item .iconfont,
|
||||
.el-sub-menu .iconfont,
|
||||
.el-menu-item .fa,
|
||||
.el-sub-menu .fa {
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
|
||||
.el-sub-menu__title {
|
||||
padding-right: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Tabs 标签页
|
||||
------------------------------- */
|
||||
.el-tabs__nav-wrap::after {
|
||||
height: 1px !important;
|
||||
height: 1px !important;
|
||||
}
|
||||
|
||||
/* Dropdown 下拉菜单
|
||||
------------------------------- */
|
||||
.el-dropdown-menu {
|
||||
list-style: none !important; /*修复 Dropdown 下拉菜单样式问题 2022.03.04*/
|
||||
}
|
||||
.el-dropdown-menu .el-dropdown-menu__item {
|
||||
white-space: nowrap;
|
||||
&:not(.is-disabled):hover {
|
||||
background-color: var(--el-dropdown-menuItem-hover-fill);
|
||||
color: var(--el-dropdown-menuItem-hover-color);
|
||||
}
|
||||
list-style: none !important;
|
||||
/*修复 Dropdown 下拉菜单样式问题 2022.03.04*/
|
||||
}
|
||||
|
||||
/* Steps 步骤条
|
||||
------------------------------- */
|
||||
.el-step__icon-inner {
|
||||
font-size: 30px !important;
|
||||
font-weight: 400 !important;
|
||||
}
|
||||
.el-step__title {
|
||||
font-size: 14px;
|
||||
.el-dropdown-menu .el-dropdown-menu__item {
|
||||
white-space: nowrap;
|
||||
|
||||
&:not(.is-disabled):hover {
|
||||
background-color: var(--el-dropdown-menuItem-hover-fill);
|
||||
color: var(--el-dropdown-menuItem-hover-color);
|
||||
}
|
||||
}
|
||||
|
||||
/* Dialog 对话框
|
||||
------------------------------- */
|
||||
.el-overlay {
|
||||
overflow: hidden;
|
||||
.el-overlay-dialog {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: unset !important;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
.el-dialog {
|
||||
margin: 0 auto !important;
|
||||
position: absolute;
|
||||
.el-dialog__body {
|
||||
padding: 20px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
overflow: hidden;
|
||||
|
||||
.el-overlay-dialog {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: unset !important;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.el-dialog {
|
||||
margin: 0 auto !important;
|
||||
position: absolute;
|
||||
|
||||
.el-dialog__body {
|
||||
padding: 20px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-dialog__body {
|
||||
max-height: calc(90vh - 111px) !important;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
max-height: calc(90vh - 111px) !important;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* Card 卡片
|
||||
------------------------------- */
|
||||
.el-card__header {
|
||||
padding: 15px 20px;
|
||||
padding: 15px 20px;
|
||||
}
|
||||
|
||||
/* Table 表格 element plus 2.2.0 版本
|
||||
------------------------------- */
|
||||
.el-table {
|
||||
.el-button.is-text {
|
||||
padding: 0;
|
||||
}
|
||||
.el-button.is-text {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* scrollbar
|
||||
------------------------------- */
|
||||
.el-scrollbar__bar {
|
||||
z-index: 4;
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
/*防止页面切换时,滚动条高度不变的问题(滚动条高度非滚动条滚动高度)*/
|
||||
.el-scrollbar__wrap {
|
||||
max-height: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.el-select-dropdown .el-scrollbar__wrap {
|
||||
overflow-x: scroll !important;
|
||||
overflow-x: scroll !important;
|
||||
}
|
||||
|
||||
/*修复Select 选择器高度问题*/
|
||||
.el-select-dropdown__wrap {
|
||||
max-height: 274px !important;
|
||||
max-height: 274px !important;
|
||||
}
|
||||
|
||||
/*修复Cascader 级联选择器高度问题*/
|
||||
.el-cascader-menu__wrap.el-scrollbar__wrap {
|
||||
height: 204px !important;
|
||||
height: 204px !important;
|
||||
}
|
||||
|
||||
/*用于界面高度自适应(main.vue),区分 scrollbar__view,防止其它使用 scrollbar 的地方出现滚动条消失*/
|
||||
.layout-container-view .el-scrollbar__view {
|
||||
height: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/*防止分栏布局二级菜单很多时,滚动条消失问题*/
|
||||
.layout-columns-warp .layout-aside .el-scrollbar__view {
|
||||
height: unset !important;
|
||||
height: unset !important;
|
||||
}
|
||||
|
||||
/* Pagination 分页
|
||||
------------------------------- */
|
||||
.el-pagination__editor {
|
||||
margin-right: 8px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
/*深色模式时分页高亮问题*/
|
||||
.el-pagination.is-background .btn-next.is-active,
|
||||
.el-pagination.is-background .btn-prev.is-active,
|
||||
.el-pagination.is-background .el-pager li.is-active {
|
||||
background-color: var(--el-color-primary) !important;
|
||||
color: var(--el-color-white) !important;
|
||||
}
|
||||
|
||||
/* Drawer 抽屉
|
||||
------------------------------- */
|
||||
.el-drawer {
|
||||
--el-drawer-padding-primary: unset !important;
|
||||
.el-drawer__header {
|
||||
padding: 0 15px !important;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 0 !important;
|
||||
border-bottom: 1px solid var(--el-border-color);
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
.el-drawer__body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
background-color: var(--el-color-primary) !important;
|
||||
color: var(--el-color-white) !important;
|
||||
}
|
||||
|
||||
/* Breadcrumb 面包屑
|
||||
------------------------------- */
|
||||
.el-breadcrumb__inner a:hover,
|
||||
.el-breadcrumb__inner.is-link:hover {
|
||||
color: var(--el-color-primary);
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.el-breadcrumb__inner a,
|
||||
.el-breadcrumb__inner.is-link {
|
||||
color: var(--bg-topBarColor);
|
||||
font-weight: normal;
|
||||
color: var(--bg-topBarColor);
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
// el-tooltip使用自定义主题时的样式
|
||||
.el-popper.is-customized {
|
||||
/* Set padding to ensure the height is 32px */
|
||||
// padding: 6px 12px;
|
||||
// padding: 6px 12px;
|
||||
background: linear-gradient(90deg, rgb(159, 229, 151), rgb(204, 229, 129));
|
||||
}
|
||||
|
||||
.el-popper.is-customized .el-popper__arrow::before {
|
||||
background: linear-gradient(45deg, #b2e68d, #bce689);
|
||||
right: 0;
|
||||
@@ -343,7 +361,9 @@
|
||||
|
||||
|
||||
.el-dialog {
|
||||
border-radius: 6px; /* 设置圆角 */
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); /* 添加轻微阴影效果 */
|
||||
border-radius: 6px;
|
||||
/* 设置圆角 */
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
/* 添加轻微阴影效果 */
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
}
|
||||
@@ -98,4 +98,3 @@ export default {
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@/router/staticRouter
|
||||
|
||||
211
mayfly_go_web/src/views/flow/ProcdefEdit.vue
Executable file
211
mayfly_go_web/src/views/flow/ProcdefEdit.vue
Executable file
@@ -0,0 +1,211 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-drawer @open="initSort" :title="title" v-model="visible" :before-close="cancel" :destroy-on-close="true" :close-on-click-modal="false">
|
||||
<template #header>
|
||||
<DrawerHeader :header="title" :back="cancel" />
|
||||
</template>
|
||||
|
||||
<el-form :model="form" ref="formRef" :rules="rules" label-width="auto">
|
||||
<el-form-item prop="name" label="名称">
|
||||
<el-input v-model.trim="form.name" placeholder="请输入流程名称" auto-complete="off" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="defKey" label="key">
|
||||
<el-input :disabled="form.id" v-model.trim="form.defKey" placeholder="请输入流程key" auto-complete="off" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="status" label="状态">
|
||||
<el-select v-model="form.status" placeholder="请选择状态">
|
||||
<el-option v-for="item in ProcdefStatus" :key="item.value" :label="item.label" :value="item.value"> </el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item prop="remark" label="备注">
|
||||
<el-input v-model.trim="form.remark" placeholder="备注" auto-complete="off" clearable></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-divider content-position="left">审批节点</el-divider>
|
||||
|
||||
<el-table ref="taskTableRef" :data="tasks" row-key="taskKey" stripe style="width: 100%">
|
||||
<el-table-column prop="name" label="名称" min-width="100px">
|
||||
<template #header>
|
||||
<el-button class="ml0" type="primary" circle size="small" icon="Plus" @click="addTask()"> </el-button>
|
||||
<span class="ml10">节点名称</span>
|
||||
<el-tooltip content="点击指定节点可进行拖拽排序" placement="top">
|
||||
<el-icon class="ml5">
|
||||
<question-filled />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
<template #default="scope">
|
||||
<el-input v-model="scope.row.name"> </el-input>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="userId" label="审核人员" min-width="150px" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<AccountSelectFormItem v-model="scope.row.userId" label="" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="60px">
|
||||
<template #default="scope">
|
||||
<el-link @click="deleteTask(scope.$index)" class="ml5" type="danger" icon="delete" plain></el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<div>
|
||||
<el-button @click="cancel()">取 消</el-button>
|
||||
<el-button type="primary" :loading="saveBtnLoading" @click="btnOk">确 定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { toRefs, reactive, watch, ref, nextTick } from 'vue';
|
||||
import { procdefApi } from './api';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
|
||||
import AccountSelectFormItem from '@/views/system/account/components/AccountSelectFormItem.vue';
|
||||
import Sortable from 'sortablejs';
|
||||
import { randomUuid } from '../../common/utils/string';
|
||||
import { ProcdefStatus } from './enums';
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: [Boolean, Object],
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
},
|
||||
});
|
||||
|
||||
const visible = defineModel<boolean>('visible', { default: false });
|
||||
|
||||
//定义事件
|
||||
const emit = defineEmits(['cancel', 'val-change']);
|
||||
|
||||
const formRef: any = ref(null);
|
||||
const taskTableRef: any = ref(null);
|
||||
|
||||
const rules = {
|
||||
name: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入流程名称',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
defKey: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入流程key',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const state = reactive({
|
||||
tasks: [] as any,
|
||||
form: {
|
||||
id: null,
|
||||
name: null,
|
||||
defKey: null,
|
||||
status: null,
|
||||
remark: null,
|
||||
// 流程的审批节点任务
|
||||
tasks: '',
|
||||
},
|
||||
sortable: '' as any,
|
||||
});
|
||||
|
||||
const { form, tasks } = toRefs(state);
|
||||
|
||||
const { isFetching: saveBtnLoading, execute: saveFlowDefExec } = procdefApi.save.useApi(form);
|
||||
|
||||
watch(props, (newValue: any) => {
|
||||
if (newValue.data) {
|
||||
state.form = { ...newValue.data };
|
||||
const tasks = JSON.parse(state.form.tasks);
|
||||
tasks.forEach((t: any) => {
|
||||
t.userId = Number.parseInt(t.userId);
|
||||
});
|
||||
state.tasks = tasks;
|
||||
} else {
|
||||
state.form = { status: ProcdefStatus.Enable.value } as any;
|
||||
state.tasks = [];
|
||||
}
|
||||
});
|
||||
|
||||
const initSort = () => {
|
||||
nextTick(() => {
|
||||
const table = taskTableRef.value.$el.querySelector('table > tbody') as any;
|
||||
state.sortable = Sortable.create(table, {
|
||||
animation: 200,
|
||||
//拖拽结束事件
|
||||
onEnd: (evt) => {
|
||||
const curRow = state.tasks.splice(evt.oldIndex, 1)[0];
|
||||
state.tasks.splice(evt.newIndex, 0, curRow);
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const addTask = () => {
|
||||
state.tasks.push({ taskKey: randomUuid() });
|
||||
};
|
||||
|
||||
const deleteTask = (idx: any) => {
|
||||
state.tasks.splice(idx, 1);
|
||||
};
|
||||
|
||||
const btnOk = async () => {
|
||||
formRef.value.validate(async (valid: boolean) => {
|
||||
if (!valid) {
|
||||
ElMessage.error('表单填写有误');
|
||||
return false;
|
||||
}
|
||||
const checkRes = checkTasks();
|
||||
if (checkRes.err) {
|
||||
ElMessage.error(checkRes.err);
|
||||
return false;
|
||||
}
|
||||
|
||||
state.form.tasks = JSON.stringify(checkRes.tasks);
|
||||
await saveFlowDefExec();
|
||||
ElMessage.success('操作成功');
|
||||
emit('val-change', state.form);
|
||||
//重置表单域
|
||||
formRef.value.resetFields();
|
||||
state.form = {} as any;
|
||||
});
|
||||
};
|
||||
|
||||
const checkTasks = () => {
|
||||
if (state.tasks?.length == 0) {
|
||||
return { err: '请完善审批节点任务' };
|
||||
}
|
||||
|
||||
const tasks = [];
|
||||
for (let i = 0; i < state.tasks.length; i++) {
|
||||
const task = { ...state.tasks[i] };
|
||||
if (!task.name || !task.userId) {
|
||||
return { err: `请完善第${i + 1}个审批节点任务信息` };
|
||||
}
|
||||
// 转为字符串(方便后续万一需要调整啥的)
|
||||
task.userId = `${task.userId}`;
|
||||
if (!task.taskKey) {
|
||||
task.taskKey = randomUuid();
|
||||
}
|
||||
tasks.push(task);
|
||||
}
|
||||
|
||||
return { tasks: tasks };
|
||||
};
|
||||
|
||||
const cancel = () => {
|
||||
visible.value = false;
|
||||
emit('cancel');
|
||||
};
|
||||
</script>
|
||||
<style lang="scss"></style>
|
||||
141
mayfly_go_web/src/views/flow/ProcdefList.vue
Normal file
141
mayfly_go_web/src/views/flow/ProcdefList.vue
Normal file
@@ -0,0 +1,141 @@
|
||||
<template>
|
||||
<div>
|
||||
<page-table
|
||||
ref="pageTableRef"
|
||||
:page-api="procdefApi.list"
|
||||
:search-items="searchItems"
|
||||
v-model:query-form="query"
|
||||
:show-selection="true"
|
||||
v-model:selection-data="selectionData"
|
||||
:columns="columns"
|
||||
>
|
||||
<template #tableHeader>
|
||||
<el-button v-auth="perms.save" type="primary" icon="plus" @click="editFlowDef(false)">添加</el-button>
|
||||
<el-button v-auth="perms.del" :disabled="state.selectionData.length < 1" @click="deleteProcdef()" type="danger" icon="delete">删除</el-button>
|
||||
</template>
|
||||
|
||||
<template #tasks="{ data }">
|
||||
<el-link @click="showProcdefTasks(data)" icon="view" type="primary" :underline="false"> </el-link>
|
||||
</template>
|
||||
|
||||
<template #action="{ data }">
|
||||
<el-button link v-if="actionBtns[perms.save]" @click="editFlowDef(data)" type="primary">编辑</el-button>
|
||||
</template>
|
||||
</page-table>
|
||||
|
||||
<el-dialog v-model="flowTasksDialog.visible" :title="flowTasksDialog.title">
|
||||
<procdef-tasks :tasks="flowTasksDialog.tasks" />
|
||||
</el-dialog>
|
||||
|
||||
<procdef-edit v-model:visible="flowDefEditor.visible" :title="flowDefEditor.title" v-model:data="flowDefEditor.data" @val-change="valChange()" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, toRefs, reactive, onMounted, Ref } from 'vue';
|
||||
import { procdefApi } from './api';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import PageTable from '@/components/pagetable/PageTable.vue';
|
||||
import { TableColumn } from '@/components/pagetable';
|
||||
import { hasPerms } from '@/components/auth/auth';
|
||||
import { SearchItem } from '@/components/SearchForm';
|
||||
import ProcdefEdit from './ProcdefEdit.vue';
|
||||
import ProcdefTasks from './components/ProcdefTasks.vue';
|
||||
import { ProcdefStatus } from './enums';
|
||||
|
||||
const perms = {
|
||||
save: 'flow:procdef:save',
|
||||
del: 'flow:procdef:del',
|
||||
};
|
||||
|
||||
const searchItems = [SearchItem.input('name', '名称'), SearchItem.input('defKey', 'key')];
|
||||
const columns = [
|
||||
TableColumn.new('name', '名称'),
|
||||
TableColumn.new('defKey', 'key'),
|
||||
TableColumn.new('status', '状态').typeTag(ProcdefStatus),
|
||||
TableColumn.new('remark', '备注'),
|
||||
TableColumn.new('tasks', '审批节点').isSlot().alignCenter().setMinWidth(60),
|
||||
TableColumn.new('creator', '创建账号'),
|
||||
TableColumn.new('createTime', '创建时间').isTime(),
|
||||
];
|
||||
|
||||
// 该用户拥有的的操作列按钮权限
|
||||
const actionBtns = hasPerms([perms.save, perms.del]);
|
||||
const actionColumn = TableColumn.new('action', '操作').isSlot().fixedRight().setMinWidth(160).noShowOverflowTooltip().alignCenter();
|
||||
|
||||
const pageTableRef: Ref<any> = ref(null);
|
||||
const state = reactive({
|
||||
/**
|
||||
* 选中的数据
|
||||
*/
|
||||
selectionData: [],
|
||||
/**
|
||||
* 查询条件
|
||||
*/
|
||||
query: {
|
||||
name: '',
|
||||
pageNum: 1,
|
||||
pageSize: 0,
|
||||
},
|
||||
flowDefEditor: {
|
||||
title: '新建流程定义',
|
||||
visible: false,
|
||||
data: null as any,
|
||||
},
|
||||
flowTasksDialog: {
|
||||
title: '',
|
||||
visible: false,
|
||||
tasks: '',
|
||||
},
|
||||
});
|
||||
|
||||
const { selectionData, query, flowDefEditor, flowTasksDialog } = toRefs(state);
|
||||
|
||||
onMounted(() => {
|
||||
if (Object.keys(actionBtns).length > 0) {
|
||||
columns.push(actionColumn);
|
||||
}
|
||||
});
|
||||
|
||||
const search = async () => {
|
||||
pageTableRef.value.search();
|
||||
};
|
||||
|
||||
const showProcdefTasks = (procdef: any) => {
|
||||
state.flowTasksDialog.tasks = procdef.tasks;
|
||||
state.flowTasksDialog.title = procdef.name + '-审批节点';
|
||||
state.flowTasksDialog.visible = true;
|
||||
};
|
||||
|
||||
const editFlowDef = (data: any) => {
|
||||
if (!data) {
|
||||
state.flowDefEditor.data = null;
|
||||
state.flowDefEditor.title = '新建流程定义';
|
||||
} else {
|
||||
state.flowDefEditor.data = data;
|
||||
state.flowDefEditor.title = '编辑流程定义';
|
||||
}
|
||||
state.flowDefEditor.visible = true;
|
||||
};
|
||||
|
||||
const valChange = () => {
|
||||
state.flowDefEditor.visible = false;
|
||||
search();
|
||||
};
|
||||
|
||||
const deleteProcdef = async () => {
|
||||
try {
|
||||
await ElMessageBox.confirm(`确定删除【${state.selectionData.map((x: any) => x.name).join(', ')}】的流程定义?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
});
|
||||
await procdefApi.del.request({ id: state.selectionData.map((x: any) => x.id).join(',') });
|
||||
ElMessage.success('删除成功');
|
||||
search();
|
||||
} catch (err) {
|
||||
//
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style lang="scss"></style>
|
||||
158
mayfly_go_web/src/views/flow/ProcinstDetail.vue
Executable file
158
mayfly_go_web/src/views/flow/ProcinstDetail.vue
Executable file
@@ -0,0 +1,158 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-drawer :title="props.title" v-model="visible" :before-close="cancel" size="40%" :close-on-click-modal="!props.instTaskId">
|
||||
<template #header>
|
||||
<DrawerHeader :header="title" :back="cancel" />
|
||||
</template>
|
||||
|
||||
<div>
|
||||
<el-divider content-position="left">流程信息</el-divider>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="流程名">{{ procinst.procdefName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="业务">
|
||||
<enum-tag :enums="FlowBizType" :value="procinst.bizType"></enum-tag>
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="发起人">{{ procinst.creator }}</el-descriptions-item>
|
||||
<el-descriptions-item label="发起时间">{{ dateFormat(procinst.createTime) }}</el-descriptions-item>
|
||||
|
||||
<div v-if="procinst.duration">
|
||||
<el-descriptions-item label="持续时间">{{ formatTime(procinst.duration) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="结束时间">{{ dateFormat(procinst.endTime) }}</el-descriptions-item>
|
||||
</div>
|
||||
|
||||
<el-descriptions-item label="流程状态">
|
||||
<enum-tag :enums="ProcinstStatus" :value="procinst.status"></enum-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="业务状态">
|
||||
<enum-tag :enums="ProcinstBizStatus" :value="procinst.bizStatus"></enum-tag>
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="备注">
|
||||
{{ procinst.remark }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<el-divider content-position="left">审批节点</el-divider>
|
||||
<procdef-tasks :tasks="procinst?.procdef?.tasks" :procinst-tasks="procinst.procinstTasks" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<el-divider content-position="left">业务信息</el-divider>
|
||||
<component v-if="procinst.bizType" ref="keyValueRef" :is="bizComponents[procinst.bizType]" :biz-key="procinst.bizKey"> </component>
|
||||
</div>
|
||||
|
||||
<div v-if="props.instTaskId">
|
||||
<el-divider content-position="left">审批表单</el-divider>
|
||||
<el-form :model="form" label-width="auto">
|
||||
<el-form-item prop="status" label="结果" required>
|
||||
<el-select v-model="form.status" placeholder="请选择审批结果">
|
||||
<el-option :label="ProcinstTaskStatus.Pass.label" :value="ProcinstTaskStatus.Pass.value"> </el-option>
|
||||
<!-- <el-option :label="ProcinstTaskStatus.Back.label" :value="ProcinstTaskStatus.Back.value"> </el-option> -->
|
||||
<el-option :label="ProcinstTaskStatus.Reject.label" :value="ProcinstTaskStatus.Reject.value"> </el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item prop="remark" label="备注">
|
||||
<el-input v-model.trim="form.remark" placeholder="备注" type="textarea" clearable></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<template #footer v-if="props.instTaskId">
|
||||
<div>
|
||||
<el-button @click="cancel()">取 消</el-button>
|
||||
<el-button type="primary" :loading="saveBtnLoading" @click="btnOk">确 定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { toRefs, reactive, watch, defineAsyncComponent, shallowReactive } from 'vue';
|
||||
import { procinstApi } from './api';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
|
||||
import { FlowBizType, ProcinstBizStatus, ProcinstTaskStatus, ProcinstStatus } from './enums';
|
||||
import { dateFormat } from '@/common/utils/date';
|
||||
import ProcdefTasks from './components/ProcdefTasks.vue';
|
||||
import { formatTime } from '@/common/utils/format';
|
||||
import EnumTag from '@/components/enumtag/EnumTag.vue';
|
||||
|
||||
const DbSqlExecBiz = defineAsyncComponent(() => import('./flowbiz/DbSqlExecBiz.vue'));
|
||||
|
||||
const props = defineProps({
|
||||
procinstId: {
|
||||
type: Number,
|
||||
},
|
||||
// 流程实例任务id(存在则展示审批相关信息)
|
||||
instTaskId: {
|
||||
type: Number,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
},
|
||||
});
|
||||
|
||||
const visible = defineModel<boolean>('visible', { default: false });
|
||||
|
||||
//定义事件
|
||||
const emit = defineEmits(['cancel', 'val-change']);
|
||||
|
||||
// 业务组件
|
||||
const bizComponents = shallowReactive({
|
||||
db_sql_exec_flow: DbSqlExecBiz,
|
||||
});
|
||||
|
||||
const state = reactive({
|
||||
procinst: {} as any,
|
||||
tasks: [] as any,
|
||||
form: {
|
||||
status: ProcinstTaskStatus.Pass.value,
|
||||
remark: '',
|
||||
},
|
||||
saveBtnLoading: false,
|
||||
sortable: '' as any,
|
||||
});
|
||||
|
||||
const { procinst, form, saveBtnLoading } = toRefs(state);
|
||||
|
||||
watch(
|
||||
() => props.procinstId,
|
||||
async (newValue: any) => {
|
||||
if (newValue) {
|
||||
state.procinst = await procinstApi.detail.request({ id: newValue });
|
||||
} else {
|
||||
state.procinst = {};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const btnOk = async () => {
|
||||
const status = state.form.status;
|
||||
let api = procinstApi.completeTask;
|
||||
if (status === ProcinstTaskStatus.Back.value) {
|
||||
api = procinstApi.backTask;
|
||||
} else if (status === ProcinstTaskStatus.Reject.value) {
|
||||
api = procinstApi.rejectTask;
|
||||
}
|
||||
|
||||
try {
|
||||
state.saveBtnLoading = true;
|
||||
await api.request({ id: props.instTaskId, remark: state.form.remark });
|
||||
ElMessage.success('操作成功');
|
||||
cancel();
|
||||
emit('val-change');
|
||||
} finally {
|
||||
state.saveBtnLoading = false;
|
||||
}
|
||||
};
|
||||
|
||||
const cancel = () => {
|
||||
visible.value = false;
|
||||
emit('cancel');
|
||||
};
|
||||
</script>
|
||||
<style lang="scss"></style>
|
||||
120
mayfly_go_web/src/views/flow/ProcinstList.vue
Normal file
120
mayfly_go_web/src/views/flow/ProcinstList.vue
Normal file
@@ -0,0 +1,120 @@
|
||||
<template>
|
||||
<div>
|
||||
<page-table
|
||||
ref="pageTableRef"
|
||||
:page-api="procinstApi.list"
|
||||
:search-items="searchItems"
|
||||
v-model:query-form="query"
|
||||
v-model:selection-data="selectionData"
|
||||
:columns="columns"
|
||||
>
|
||||
<template #tableHeader>
|
||||
<!-- <el-button v-auth="perms.addAccount" type="primary" icon="plus" @click="editFlowDef(false)">添加</el-button> -->
|
||||
</template>
|
||||
|
||||
<template #action="{ data }">
|
||||
<el-button link @click="showProcinst(data)" type="primary">查看</el-button>
|
||||
|
||||
<el-popconfirm
|
||||
v-if="data.status == ProcinstStatus.Active.value || data.status == ProcinstStatus.Suspended.value"
|
||||
title="确认取消该流程?"
|
||||
width="160"
|
||||
@confirm="procinstCancel(data)"
|
||||
>
|
||||
<template #reference>
|
||||
<el-button link type="warning">取消</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</template>
|
||||
</page-table>
|
||||
|
||||
<ProcinstDetail
|
||||
v-model:visible="procinstDetail.visible"
|
||||
:title="procinstDetail.title"
|
||||
:procinst-id="procinstDetail.procinstId"
|
||||
:inst-task-id="procinstDetail.instTaskId"
|
||||
@val-change="valChange()"
|
||||
@cancel="procinstDetail.procinstId = 0"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, toRefs, reactive, Ref } from 'vue';
|
||||
import { procinstApi } from './api';
|
||||
import PageTable from '@/components/pagetable/PageTable.vue';
|
||||
import { TableColumn } from '@/components/pagetable';
|
||||
import { SearchItem } from '@/components/SearchForm';
|
||||
import ProcinstDetail from './ProcinstDetail.vue';
|
||||
import { FlowBizType, ProcinstBizStatus, ProcinstStatus } from './enums';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { formatTime } from '@/common/utils/format';
|
||||
|
||||
const searchItems = [SearchItem.select('status', '流程状态').withEnum(ProcinstStatus), SearchItem.select('bizType', '业务类型').withEnum(FlowBizType)];
|
||||
const columns = [
|
||||
TableColumn.new('procdefName', '流程名'),
|
||||
TableColumn.new('bizType', '业务').typeTag(FlowBizType),
|
||||
TableColumn.new('remark', '备注'),
|
||||
TableColumn.new('creator', '发起人'),
|
||||
TableColumn.new('status', '流程状态').typeTag(ProcinstStatus),
|
||||
TableColumn.new('bizStatus', '业务状态').typeTag(ProcinstBizStatus),
|
||||
TableColumn.new('createTime', '发起时间').isTime(),
|
||||
TableColumn.new('endTime', '结束时间').isTime(),
|
||||
TableColumn.new('duration', '持续时间').setFormatFunc((data: any, prop: string) => {
|
||||
const duration = data[prop];
|
||||
if (!duration) {
|
||||
return '';
|
||||
}
|
||||
return formatTime(duration);
|
||||
}),
|
||||
TableColumn.new('bizHandleRes', '业务处理结果'),
|
||||
TableColumn.new('action', '操作').isSlot().fixedRight().setMinWidth(160).noShowOverflowTooltip().alignCenter(),
|
||||
];
|
||||
|
||||
const pageTableRef: Ref<any> = ref(null);
|
||||
const state = reactive({
|
||||
/**
|
||||
* 选中的数据
|
||||
*/
|
||||
selectionData: [],
|
||||
/**
|
||||
* 查询条件
|
||||
*/
|
||||
query: {
|
||||
status: null,
|
||||
bizType: '',
|
||||
pageNum: 1,
|
||||
pageSize: 0,
|
||||
},
|
||||
procinstDetail: {
|
||||
title: '查看流程',
|
||||
visible: false,
|
||||
procinstId: 0,
|
||||
instTaskId: 0,
|
||||
},
|
||||
});
|
||||
|
||||
const { selectionData, query, procinstDetail } = toRefs(state);
|
||||
|
||||
const search = async () => {
|
||||
pageTableRef.value.search();
|
||||
};
|
||||
|
||||
const procinstCancel = async (data: any) => {
|
||||
await procinstApi.cancel.request({ id: data.id });
|
||||
ElMessage.success('操作成功');
|
||||
search();
|
||||
};
|
||||
|
||||
const showProcinst = (data: any) => {
|
||||
state.procinstDetail.procinstId = data.id;
|
||||
state.procinstDetail.title = '流程查看';
|
||||
state.procinstDetail.visible = true;
|
||||
};
|
||||
|
||||
const valChange = () => {
|
||||
state.procinstDetail.visible = false;
|
||||
search();
|
||||
};
|
||||
</script>
|
||||
<style lang="scss"></style>
|
||||
110
mayfly_go_web/src/views/flow/ProcinstTaskList.vue
Normal file
110
mayfly_go_web/src/views/flow/ProcinstTaskList.vue
Normal file
@@ -0,0 +1,110 @@
|
||||
<template>
|
||||
<div>
|
||||
<page-table
|
||||
ref="pageTableRef"
|
||||
:page-api="procinstApi.tasks"
|
||||
:search-items="searchItems"
|
||||
v-model:query-form="query"
|
||||
v-model:selection-data="selectionData"
|
||||
:columns="columns"
|
||||
>
|
||||
<template #tableHeader>
|
||||
<!-- <el-button v-auth="perms.addAccount" type="primary" icon="plus" @click="editFlowDef(false)">添加</el-button> -->
|
||||
</template>
|
||||
|
||||
<template #action="{ data }">
|
||||
<el-button link @click="showProcinst(data, false)" type="primary">查看</el-button>
|
||||
<el-button v-if="data.status == ProcinstTaskStatus.Process.value" link @click="showProcinst(data, true)" type="primary">审核</el-button>
|
||||
</template>
|
||||
</page-table>
|
||||
|
||||
<ProcinstDetail
|
||||
v-model:visible="procinstDetail.visible"
|
||||
:title="procinstDetail.title"
|
||||
:procinst-id="procinstDetail.procinstId"
|
||||
:inst-task-id="procinstDetail.instTaskId"
|
||||
@val-change="valChange()"
|
||||
@cancel="procinstDetail.procinstId = 0"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, toRefs, reactive, Ref } from 'vue';
|
||||
import { procinstApi } from './api';
|
||||
import PageTable from '@/components/pagetable/PageTable.vue';
|
||||
import { TableColumn } from '@/components/pagetable';
|
||||
import { SearchItem } from '@/components/SearchForm';
|
||||
import ProcinstDetail from './ProcinstDetail.vue';
|
||||
import { FlowBizType, ProcinstStatus, ProcinstTaskStatus } from './enums';
|
||||
import { formatTime } from '@/common/utils/format';
|
||||
|
||||
const searchItems = [SearchItem.select('status', '任务状态').withEnum(ProcinstTaskStatus), SearchItem.select('bizType', '业务类型').withEnum(FlowBizType)];
|
||||
const columns = [
|
||||
TableColumn.new('procinst.procdefName', '流程名'),
|
||||
TableColumn.new('procinst.bizType', '业务').typeTag(FlowBizType),
|
||||
TableColumn.new('procinst.remark', '备注'),
|
||||
TableColumn.new('procinst.creator', '发起人'),
|
||||
TableColumn.new('procinst.status', '流程状态').typeTag(ProcinstStatus),
|
||||
TableColumn.new('status', '任务状态').typeTag(ProcinstTaskStatus),
|
||||
TableColumn.new('taskName', '当前节点'),
|
||||
TableColumn.new('procinst.createTime', '发起时间').isTime(),
|
||||
TableColumn.new('createTime', '开始时间').isTime(),
|
||||
TableColumn.new('endTime', '结束时间').isTime(),
|
||||
TableColumn.new('duration', '持续时间').setFormatFunc((data: any, prop: string) => {
|
||||
const duration = data[prop];
|
||||
if (!duration) {
|
||||
return '';
|
||||
}
|
||||
return formatTime(duration);
|
||||
}),
|
||||
TableColumn.new('action', '操作').isSlot().fixedRight().setMinWidth(160).noShowOverflowTooltip().alignCenter(),
|
||||
];
|
||||
|
||||
const pageTableRef: Ref<any> = ref(null);
|
||||
const state = reactive({
|
||||
/**
|
||||
* 选中的数据
|
||||
*/
|
||||
selectionData: [],
|
||||
/**
|
||||
* 查询条件
|
||||
*/
|
||||
query: {
|
||||
status: ProcinstTaskStatus.Process.value,
|
||||
bizType: '',
|
||||
pageNum: 1,
|
||||
pageSize: 0,
|
||||
},
|
||||
procinstDetail: {
|
||||
title: '查看流程',
|
||||
visible: false,
|
||||
procinstId: 0,
|
||||
instTaskId: 0,
|
||||
},
|
||||
});
|
||||
|
||||
const { selectionData, query, procinstDetail } = toRefs(state);
|
||||
|
||||
const search = async () => {
|
||||
pageTableRef.value.search();
|
||||
};
|
||||
|
||||
const showProcinst = (data: any, audit: boolean) => {
|
||||
state.procinstDetail.procinstId = data.procinstId;
|
||||
if (!audit) {
|
||||
state.procinstDetail.instTaskId = 0;
|
||||
state.procinstDetail.title = '流程查看';
|
||||
} else {
|
||||
state.procinstDetail.instTaskId = data.id;
|
||||
state.procinstDetail.title = '流程审批';
|
||||
}
|
||||
state.procinstDetail.visible = true;
|
||||
};
|
||||
|
||||
const valChange = () => {
|
||||
state.procinstDetail.visible = false;
|
||||
search();
|
||||
};
|
||||
</script>
|
||||
<style lang="scss"></style>
|
||||
20
mayfly_go_web/src/views/flow/api.ts
Normal file
20
mayfly_go_web/src/views/flow/api.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import Api from '@/common/Api';
|
||||
|
||||
export const procdefApi = {
|
||||
list: Api.newGet('/flow/procdefs'),
|
||||
getByKey: Api.newGet('/flow/procdefs/{key}'),
|
||||
save: Api.newPost('/flow/procdefs'),
|
||||
del: Api.newDelete('/flow/procdefs/{id}'),
|
||||
};
|
||||
|
||||
export const procinstApi = {
|
||||
list: Api.newGet('/flow/procinsts'),
|
||||
detail: Api.newGet('/flow/procinsts/{id}'),
|
||||
cancel: Api.newPost('/flow/procinsts/{id}/cancel'),
|
||||
tasks: Api.newGet('/flow/procinsts/tasks'),
|
||||
completeTask: Api.newPost('/flow/procinsts/tasks/complete'),
|
||||
backTask: Api.newPost('/flow/procinsts/tasks/back'),
|
||||
rejectTask: Api.newPost('/flow/procinsts/tasks/reject'),
|
||||
save: Api.newPost('/flow/procdefs'),
|
||||
del: Api.newDelete('/flow/procdefs/{id}'),
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<el-form-item :label="props.label">
|
||||
<el-select style="width: 100%" v-model="procdefKey" filterable placeholder="绑定流程则开启对应审批流程" v-bind="$attrs" clearable>
|
||||
<el-option v-for="item in procdefs" :key="item.defKey" :label="`${item.defKey} [${item.name}]`" :value="item.defKey"> </el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { procdefApi } from '../api';
|
||||
|
||||
const props = defineProps({
|
||||
label: {
|
||||
type: String,
|
||||
default: '工单流程',
|
||||
},
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
getProcdefs();
|
||||
});
|
||||
|
||||
const procdefKey = defineModel('modelValue');
|
||||
|
||||
const procdefs: any = ref([]);
|
||||
|
||||
const getProcdefs = () => {
|
||||
procdefApi.list.request({ pageSize: 200 }).then((res) => {
|
||||
procdefs.value = res.list;
|
||||
});
|
||||
};
|
||||
</script>
|
||||
136
mayfly_go_web/src/views/flow/components/ProcdefTasks.vue
Executable file
136
mayfly_go_web/src/views/flow/components/ProcdefTasks.vue
Executable file
@@ -0,0 +1,136 @@
|
||||
<template>
|
||||
<el-steps align-center :active="stepActive">
|
||||
<el-step v-for="task in tasksArr" :status="getStepStatus(task)" :title="task.name" :key="task.taskKey">
|
||||
<template #description>
|
||||
<div>{{ `${task.accountUsername}(${task.accountName})` }}</div>
|
||||
<div v-if="task.completeTime">{{ `${dateFormat(task.completeTime)}` }}</div>
|
||||
<div v-if="task.remark">{{ task.remark }}</div>
|
||||
</template>
|
||||
</el-step>
|
||||
</el-steps>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { toRefs, reactive, watch, onMounted } from 'vue';
|
||||
import { accountApi } from '../../system/api';
|
||||
import { ProcinstTaskStatus } from '../enums';
|
||||
import { dateFormat } from '@/common/utils/date';
|
||||
import { procdefApi } from '../api';
|
||||
import { ElSteps, ElStep } from 'element-plus';
|
||||
|
||||
const props = defineProps({
|
||||
// 流程定义任务
|
||||
tasks: {
|
||||
type: [String, Object],
|
||||
},
|
||||
procdefKey: {
|
||||
type: String,
|
||||
},
|
||||
// 流程实例任务列表
|
||||
procinstTasks: {
|
||||
type: [Array],
|
||||
},
|
||||
});
|
||||
|
||||
const state = reactive({
|
||||
tasksArr: [] as any,
|
||||
stepActive: 0,
|
||||
});
|
||||
|
||||
const { tasksArr, stepActive } = toRefs(state);
|
||||
|
||||
watch(
|
||||
() => props.tasks,
|
||||
(newValue: any) => {
|
||||
parseTasks(newValue);
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.procinstTasks,
|
||||
() => {
|
||||
parseTasks(props.tasks);
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.procdefKey,
|
||||
async (newValue: any) => {
|
||||
if (newValue) {
|
||||
parseTasksByKey(newValue);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
if (props.procdefKey) {
|
||||
parseTasksByKey(props.procdefKey);
|
||||
return;
|
||||
}
|
||||
parseTasks(props.tasks);
|
||||
});
|
||||
|
||||
const parseTasksByKey = async (key: string) => {
|
||||
const procdef = await procdefApi.getByKey.request({ key });
|
||||
parseTasks(procdef.tasks);
|
||||
};
|
||||
|
||||
const parseTasks = async (tasksStr: any) => {
|
||||
if (!tasksStr) return;
|
||||
const tasks = JSON.parse(tasksStr);
|
||||
const userIds = tasks.map((x: any) => x.userId);
|
||||
const usersRes = await accountApi.querySimple.request({ ids: [...new Set(userIds)].join(','), pageSize: 50 });
|
||||
const users = usersRes.list;
|
||||
// 将数组转换为 Map 结构,以 id 为 key
|
||||
const userMap = users.reduce((acc: any, obj: any) => {
|
||||
acc.set(obj.id, obj);
|
||||
return acc;
|
||||
}, new Map());
|
||||
|
||||
// 流程实例任务(用于显示完成时间,完成到哪一步等)
|
||||
let instTasksMap: any;
|
||||
if (props.procinstTasks) {
|
||||
state.stepActive = props.procinstTasks.length - 1;
|
||||
instTasksMap = props.procinstTasks.reduce((acc: any, obj: any) => {
|
||||
acc.set(obj.taskKey, obj);
|
||||
return acc;
|
||||
}, new Map());
|
||||
}
|
||||
|
||||
for (let task of tasks) {
|
||||
const user = userMap.get(Number.parseInt(task.userId));
|
||||
task.accountUsername = user.username;
|
||||
task.accountName = user.name;
|
||||
|
||||
// 存在实例任务,则赋值实例任务对应的完成时间和备注
|
||||
const instTask = instTasksMap?.get(task.taskKey);
|
||||
if (instTask) {
|
||||
task.status = instTask.status;
|
||||
task.completeTime = instTask.endTime;
|
||||
task.remark = instTask.remark;
|
||||
}
|
||||
}
|
||||
|
||||
state.tasksArr = tasks;
|
||||
};
|
||||
|
||||
const getStepStatus = (task: any): any => {
|
||||
const taskStatus = task.status;
|
||||
if (!taskStatus) {
|
||||
return 'wait';
|
||||
}
|
||||
|
||||
if (taskStatus == ProcinstTaskStatus.Pass.value) {
|
||||
return 'success';
|
||||
}
|
||||
if (taskStatus == ProcinstTaskStatus.Process.value) {
|
||||
return 'proccess';
|
||||
}
|
||||
if (taskStatus == ProcinstTaskStatus.Back.value || taskStatus == ProcinstTaskStatus.Reject.value) {
|
||||
return 'error';
|
||||
}
|
||||
|
||||
return 'wait';
|
||||
};
|
||||
</script>
|
||||
<style lang="scss"></style>
|
||||
33
mayfly_go_web/src/views/flow/enums.ts
Normal file
33
mayfly_go_web/src/views/flow/enums.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { EnumValue } from '@/common/Enum';
|
||||
|
||||
export const ProcdefStatus = {
|
||||
Enable: EnumValue.of(1, '启用').setTagType('success'),
|
||||
Disable: EnumValue.of(-1, '禁用').setTagType('warning'),
|
||||
};
|
||||
|
||||
export const ProcinstStatus = {
|
||||
Active: EnumValue.of(1, '执行中').setTagType('primary'),
|
||||
Completed: EnumValue.of(2, '完成').setTagType('success'),
|
||||
Suspended: EnumValue.of(-1, '挂起').setTagType('warning'),
|
||||
Terminated: EnumValue.of(-2, '终止').setTagType('danger'),
|
||||
Cancelled: EnumValue.of(-3, '取消').setTagType('warning'),
|
||||
};
|
||||
|
||||
export const ProcinstBizStatus = {
|
||||
Wait: EnumValue.of(1, '待处理').setTagType('primary'),
|
||||
Success: EnumValue.of(2, '处理成功').setTagType('success'),
|
||||
Fail: EnumValue.of(-2, '处理失败').setTagType('danger'),
|
||||
No: EnumValue.of(-1, '不处理').setTagType('warning'),
|
||||
};
|
||||
|
||||
export const ProcinstTaskStatus = {
|
||||
Process: EnumValue.of(1, '待处理').setTagType('primary'),
|
||||
Pass: EnumValue.of(2, '通过').setTagType('success'),
|
||||
Reject: EnumValue.of(-1, '拒绝').setTagType('danger'),
|
||||
Back: EnumValue.of(-2, '驳回').setTagType('warning'),
|
||||
Canceled: EnumValue.of(-3, '取消').setTagType('warning'),
|
||||
};
|
||||
|
||||
export const FlowBizType = {
|
||||
DbSqlExec: EnumValue.of('db_sql_exec_flow', 'DBMS-执行SQL'),
|
||||
};
|
||||
79
mayfly_go_web/src/views/flow/flowbiz/DbSqlExecBiz.vue
Executable file
79
mayfly_go_web/src/views/flow/flowbiz/DbSqlExecBiz.vue
Executable file
@@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-descriptions :column="3" border>
|
||||
<el-descriptions-item :span="2" label="名称">{{ db?.name }}</el-descriptions-item>
|
||||
<el-descriptions-item :span="1" label="id">{{ db?.id }}</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item :span="3" label="关联标签"><ResourceTags :tags="db.tags" /></el-descriptions-item>
|
||||
|
||||
<el-descriptions-item :span="1" label="主机">{{ `${db?.host}:${db?.port}` }}</el-descriptions-item>
|
||||
<el-descriptions-item :span="1" label="类型">
|
||||
<SvgIcon :name="getDbDialect(db?.type).getInfo().icon" :size="20" />{{ db?.type }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :span="1" label="用户名">{{ db?.username }}</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="数据库">{{ sqlExec.db }}</el-descriptions-item>
|
||||
<el-descriptions-item label="表">
|
||||
{{ sqlExec.table }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="类型">
|
||||
<el-tag size="small">{{ EnumValue.getLabelByValue(DbSqlExecTypeEnum, sqlExec.type) }}</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="执行SQL">
|
||||
<monaco-editor height="300px" language="sql" v-model="sqlExec.sql" :options="{ readOnly: true }" />
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { toRefs, reactive, watch, onMounted } from 'vue';
|
||||
import EnumValue from '@/common/Enum';
|
||||
import { dbApi } from '@/views/ops/db/api';
|
||||
import { DbSqlExecTypeEnum } from '@/views/ops/db/enums';
|
||||
import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
|
||||
import { getDbDialect } from '@/views/ops/db/dialect';
|
||||
import ResourceTags from '@/views/ops/component/ResourceTags.vue';
|
||||
|
||||
const props = defineProps({
|
||||
// 业务key
|
||||
bizKey: {
|
||||
type: [String],
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
|
||||
const state = reactive({
|
||||
sqlExec: {
|
||||
sql: '',
|
||||
} as any,
|
||||
db: {} as any,
|
||||
});
|
||||
|
||||
const { sqlExec, db } = toRefs(state);
|
||||
|
||||
onMounted(() => {
|
||||
getDbSqlExec(props.bizKey);
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.bizKey,
|
||||
(newValue: any) => {
|
||||
getDbSqlExec(newValue);
|
||||
}
|
||||
);
|
||||
|
||||
const getDbSqlExec = async (bizKey: string) => {
|
||||
if (!bizKey) {
|
||||
return;
|
||||
}
|
||||
const res = await dbApi.getSqlExecs.request({ flowBizKey: bizKey });
|
||||
if (!res.list) {
|
||||
return;
|
||||
}
|
||||
state.sqlExec = res.list?.[0];
|
||||
const dbRes = await dbApi.dbs.request({ id: state.sqlExec.dbId });
|
||||
state.db = dbRes.list?.[0];
|
||||
};
|
||||
</script>
|
||||
<style lang="scss"></style>
|
||||
@@ -1,45 +0,0 @@
|
||||
<template>
|
||||
<div style="display: inline-flex; justify-content: center; align-items: center; cursor: pointer; vertical-align: middle">
|
||||
<el-popover :show-after="500" @show="getTags" placement="top-start" width="230" trigger="hover">
|
||||
<template #reference>
|
||||
<div>
|
||||
<!-- <el-button type="primary" link size="small">标签</el-button> -->
|
||||
<SvgIcon name="view" :size="16" color="var(--el-color-primary)" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-tag effect="plain" v-for="tag in tags" :key="tag" class="ml5" type="success" size="small">{{ tag.tagPath }}</el-tag>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, toRefs } from 'vue';
|
||||
import { tagApi } from '../tag/api';
|
||||
import SvgIcon from '@/components/svgIcon/index.vue';
|
||||
const props = defineProps({
|
||||
resourceCode: {
|
||||
type: [String],
|
||||
required: true,
|
||||
},
|
||||
resourceType: {
|
||||
type: [Number],
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const state = reactive({
|
||||
tags: [] as any,
|
||||
});
|
||||
|
||||
const { tags } = toRefs(state);
|
||||
|
||||
const getTags = async () => {
|
||||
state.tags = await tagApi.getTagResources.request({
|
||||
resourceCode: props.resourceCode,
|
||||
resourceType: props.resourceType,
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss"></style>
|
||||
33
mayfly_go_web/src/views/ops/component/ResourceTags.vue
Normal file
33
mayfly_go_web/src/views/ops/component/ResourceTags.vue
Normal file
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<div v-if="props.tags">
|
||||
<el-row v-for="(tag, idx) in props.tags?.slice(0, 1)" :key="idx">
|
||||
<TagInfo :tag-path="tag.tagPath" />
|
||||
<span class="ml3">{{ tag.tagPath }}</span>
|
||||
|
||||
<!-- 展示剩余的标签信息 -->
|
||||
<el-popover :show-after="300" v-if="props.tags.length > 1 && idx == 0" placement="top-start" width="230" trigger="hover">
|
||||
<template #reference>
|
||||
<SvgIcon class="mt5 ml5" color="var(--el-color-primary)" name="MoreFilled" />
|
||||
</template>
|
||||
|
||||
<el-row v-for="i in props.tags.slice(1)" :key="i">
|
||||
<TagInfo :tag-path="i.tagPath" />
|
||||
<span class="ml3">{{ i.tagPath }}</span>
|
||||
</el-row>
|
||||
</el-popover>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import SvgIcon from '@/components/svgIcon/index.vue';
|
||||
import TagInfo from './TagInfo.vue';
|
||||
const props = defineProps({
|
||||
tags: {
|
||||
type: [Array<any>],
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss"></style>
|
||||
@@ -2,13 +2,13 @@
|
||||
<div>
|
||||
<el-tree-select
|
||||
v-bind="$attrs"
|
||||
v-model="selectTags"
|
||||
v-model="state.selectTags"
|
||||
@change="changeTag"
|
||||
style="width: 100%"
|
||||
:data="tags"
|
||||
placeholder="请选择关联标签"
|
||||
:render-after-expand="true"
|
||||
:default-expanded-keys="[selectTags]"
|
||||
:default-expanded-keys="[state.selectTags]"
|
||||
show-checkbox
|
||||
node-key="id"
|
||||
:props="{
|
||||
@@ -40,32 +40,22 @@ import { tagApi } from '../tag/api';
|
||||
const emit = defineEmits(['update:modelValue', 'changeTag', 'input']);
|
||||
|
||||
const props = defineProps({
|
||||
resourceCode: {
|
||||
type: [String],
|
||||
required: true,
|
||||
},
|
||||
resourceType: {
|
||||
type: [Number],
|
||||
required: true,
|
||||
selectTags: {
|
||||
type: [Array<any>],
|
||||
},
|
||||
});
|
||||
|
||||
const state = reactive({
|
||||
tags: [],
|
||||
// 单选则为id,多选为id数组
|
||||
selectTags: [],
|
||||
selectTags: [] as any,
|
||||
});
|
||||
|
||||
const { tags, selectTags } = toRefs(state);
|
||||
const { tags } = toRefs(state);
|
||||
|
||||
onMounted(async () => {
|
||||
if (props.resourceCode) {
|
||||
const resourceTags = await tagApi.getTagResources.request({
|
||||
resourceCode: props.resourceCode,
|
||||
resourceType: props.resourceType,
|
||||
});
|
||||
state.selectTags = resourceTags.map((x: any) => x.tagId);
|
||||
changeTag();
|
||||
if (props.selectTags) {
|
||||
state.selectTags = props.selectTags;
|
||||
}
|
||||
|
||||
state.tags = await tagApi.getTagTrees.request(null);
|
||||
|
||||
@@ -19,8 +19,7 @@
|
||||
}
|
||||
"
|
||||
multiple
|
||||
:resource-code="form.code"
|
||||
:resource-type="TagResourceTypeEnum.Db.value"
|
||||
:select-tags="form.tagId"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
@@ -75,6 +74,8 @@
|
||||
<el-form-item prop="remark" label="备注">
|
||||
<el-input v-model.trim="form.remark" auto-complete="off" type="textarea"></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<procdef-select-form-item v-model="form.flowProcdefKey" />
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
@@ -92,8 +93,8 @@ import { toRefs, reactive, watch, ref } from 'vue';
|
||||
import { dbApi } from './api';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import TagTreeSelect from '../component/TagTreeSelect.vue';
|
||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||
import type { CheckboxValueType } from 'element-plus';
|
||||
import ProcdefSelectFormItem from '@/views/flow/components/ProcdefSelectFormItem.vue';
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
@@ -163,6 +164,7 @@ const state = reactive({
|
||||
database: '',
|
||||
remark: '',
|
||||
instanceId: null as any,
|
||||
flowProcdefKey: '',
|
||||
},
|
||||
instances: [] as any,
|
||||
});
|
||||
@@ -178,7 +180,7 @@ watch(props, async (newValue: any) => {
|
||||
}
|
||||
if (newValue.db) {
|
||||
state.form = { ...newValue.db };
|
||||
|
||||
state.form.tagId = newValue.db.tags.map((t: any) => t.tagId);
|
||||
// 将数据库名使用空格切割,获取所有数据库列表
|
||||
state.dbNamesSelected = newValue.db.database.split(' ');
|
||||
} else {
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
</template>
|
||||
|
||||
<template #tagPath="{ data }">
|
||||
<resource-tag :resource-code="data.code" :resource-type="TagResourceTypeEnum.Db.value" />
|
||||
<ResourceTags :tags="data.tags" />
|
||||
</template>
|
||||
|
||||
<template #action="{ data }">
|
||||
@@ -164,23 +164,30 @@
|
||||
<db-restore-list :dbId="dbRestoreDialog.dbId" :dbNames="dbRestoreDialog.dbs" />
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog v-model="infoDialog.visible" :before-close="onBeforeCloseInfoDialog" :close-on-click-modal="false">
|
||||
<el-dialog v-if="infoDialog.visible" v-model="infoDialog.visible" :before-close="onBeforeCloseInfoDialog">
|
||||
<el-descriptions title="详情" :column="3" border>
|
||||
<!-- <el-descriptions-item :span="3" label="标签路径">{{ infoDialog.data?.tagPath }}</el-descriptions-item> -->
|
||||
<el-descriptions-item :span="2" label="名称">{{ infoDialog.data?.name }}</el-descriptions-item>
|
||||
<el-descriptions-item :span="1" label="id">{{ infoDialog.data?.id }}</el-descriptions-item>
|
||||
<el-descriptions-item :span="3" label="数据库">{{ infoDialog.data?.database }}</el-descriptions-item>
|
||||
<el-descriptions-item :span="3" label="备注">{{ infoDialog.data?.remark }}</el-descriptions-item>
|
||||
<el-descriptions-item :span="2" label="创建时间">{{ dateFormat(infoDialog.data?.createTime) }} </el-descriptions-item>
|
||||
<el-descriptions-item :span="1" label="创建者">{{ infoDialog.data?.creator }}</el-descriptions-item>
|
||||
<el-descriptions-item :span="2" label="更新时间">{{ dateFormat(infoDialog.data?.updateTime) }} </el-descriptions-item>
|
||||
<el-descriptions-item :span="1" label="修改者">{{ infoDialog.data?.modifier }}</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item :span="3" label="关联标签"><ResourceTags :tags="infoDialog.data.tags" /></el-descriptions-item>
|
||||
<el-descriptions-item :span="3" label="数据库实例名称">{{ infoDialog.instance?.name }}</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item :span="2" label="主机">{{ infoDialog.instance?.host }}</el-descriptions-item>
|
||||
<el-descriptions-item :span="1" label="端口">{{ infoDialog.instance?.port }}</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item :span="2" label="用户名">{{ infoDialog.instance?.username }}</el-descriptions-item>
|
||||
<el-descriptions-item :span="1" label="类型">{{ infoDialog.instance?.type }}</el-descriptions-item>
|
||||
<el-descriptions-item :span="1" label="类型">
|
||||
<SvgIcon :name="getDbDialect(infoDialog.instance?.type).getInfo().icon" :size="20" />{{ infoDialog.instance?.type }}
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item :span="3" label="数据库">{{ infoDialog.data?.database }}</el-descriptions-item>
|
||||
<el-descriptions-item :span="3" label="备注">{{ infoDialog.data?.remark }}</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item :span="2" label="创建时间">{{ dateFormat(infoDialog.data?.createTime) }} </el-descriptions-item>
|
||||
<el-descriptions-item :span="1" label="创建者">{{ infoDialog.data?.creator }}</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item :span="2" label="更新时间">{{ dateFormat(infoDialog.data?.updateTime) }} </el-descriptions-item>
|
||||
<el-descriptions-item :span="1" label="修改者">{{ infoDialog.data?.modifier }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-dialog>
|
||||
|
||||
@@ -196,7 +203,6 @@ import config from '@/common/config';
|
||||
import { joinClientParams } from '@/common/request';
|
||||
import { isTrue } from '@/common/assert';
|
||||
import { dateFormat } from '@/common/utils/date';
|
||||
import ResourceTag from '../component/ResourceTag.vue';
|
||||
import PageTable from '@/components/pagetable/PageTable.vue';
|
||||
import { TableColumn } from '@/components/pagetable';
|
||||
import { hasPerms } from '@/components/auth/auth';
|
||||
@@ -210,6 +216,7 @@ import { SearchItem } from '@/components/SearchForm';
|
||||
import DbBackupList from './DbBackupList.vue';
|
||||
import DbBackupHistoryList from './DbBackupHistoryList.vue';
|
||||
import DbRestoreList from './DbRestoreList.vue';
|
||||
import ResourceTags from '../component/ResourceTags.vue';
|
||||
|
||||
const DbEdit = defineAsyncComponent(() => import('./DbEdit.vue'));
|
||||
|
||||
@@ -224,12 +231,13 @@ const perms = {
|
||||
const searchItems = [getTagPathSearchItem(TagResourceTypeEnum.Db.value), SearchItem.slot('instanceId', '实例', 'instanceSelect')];
|
||||
|
||||
const columns = ref([
|
||||
TableColumn.new('tags[0].tagPath', '关联标签').isSlot('tagPath').setAddWidth(20),
|
||||
TableColumn.new('name', '名称'),
|
||||
TableColumn.new('type', '类型').isSlot().setAddWidth(-15).alignCenter(),
|
||||
TableColumn.new('instanceName', '实例名'),
|
||||
TableColumn.new('host', 'ip:port').isSlot().setAddWidth(40),
|
||||
TableColumn.new('username', 'username'),
|
||||
TableColumn.new('tagPath', '关联标签').isSlot().setAddWidth(10).alignCenter(),
|
||||
TableColumn.new('flowProcdefKey', '关联流程'),
|
||||
TableColumn.new('remark', '备注'),
|
||||
]);
|
||||
|
||||
|
||||
@@ -17,7 +17,10 @@
|
||||
|
||||
<template #action="{ data }">
|
||||
<el-link
|
||||
v-if="data.type == DbSqlExecTypeEnum.Update.value || data.type == DbSqlExecTypeEnum.Delete.value"
|
||||
v-if="
|
||||
data.status == DbSqlExecStatusEnum.Success.value &&
|
||||
(data.type == DbSqlExecTypeEnum.Update.value || data.type == DbSqlExecTypeEnum.Delete.value)
|
||||
"
|
||||
type="primary"
|
||||
plain
|
||||
size="small"
|
||||
@@ -38,7 +41,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, reactive, Ref, ref, toRefs, watch } from 'vue';
|
||||
import { dbApi } from './api';
|
||||
import { DbSqlExecTypeEnum } from './enums';
|
||||
import { DbSqlExecTypeEnum, DbSqlExecStatusEnum } from './enums';
|
||||
import PageTable from '@/components/pagetable/PageTable.vue';
|
||||
import { TableColumn } from '@/components/pagetable';
|
||||
import { SearchItem } from '@/components/SearchForm';
|
||||
@@ -66,9 +69,11 @@ const columns = ref([
|
||||
TableColumn.new('type', '类型').typeTag(DbSqlExecTypeEnum).setAddWidth(10),
|
||||
TableColumn.new('creator', '执行人'),
|
||||
TableColumn.new('sql', 'SQL').canBeautify(),
|
||||
TableColumn.new('oldValue', '原值').canBeautify(),
|
||||
TableColumn.new('createTime', '执行时间').isTime(),
|
||||
TableColumn.new('remark', '备注'),
|
||||
TableColumn.new('status', '执行状态').typeTag(DbSqlExecStatusEnum),
|
||||
TableColumn.new('res', '执行结果'),
|
||||
TableColumn.new('createTime', '执行时间').isTime(),
|
||||
TableColumn.new('oldValue', '原值').canBeautify(),
|
||||
TableColumn.new('action', '操作').isSlot().setMinWidth(90).fixedRight().alignCenter(),
|
||||
]);
|
||||
|
||||
@@ -80,6 +85,7 @@ const state = reactive({
|
||||
dbId: 0,
|
||||
db: '',
|
||||
table: '',
|
||||
status: [DbSqlExecStatusEnum.Success.value, DbSqlExecStatusEnum.Fail.value].join(','),
|
||||
type: null,
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
|
||||
@@ -143,6 +143,7 @@
|
||||
:db-id="dt.params.id"
|
||||
:db="dt.params.db"
|
||||
:db-type="dt.params.type"
|
||||
:flow-procdef-key="dt.params.flowProcdefKey"
|
||||
:height="state.tablesOpHeight"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
@@ -157,6 +158,7 @@
|
||||
:dbId="tableCreateDialog.dbId"
|
||||
:db="tableCreateDialog.db"
|
||||
:dbType="tableCreateDialog.dbType"
|
||||
:flow-procdef-key="tableCreateDialog.flowProcdefKey"
|
||||
:data="tableCreateDialog.data"
|
||||
v-model:visible="tableCreateDialog.visible"
|
||||
@submit-sql="onSubmitEditTableSql"
|
||||
@@ -225,7 +227,18 @@ const SqlIcon = {
|
||||
const nodeClickChangeDb = (nodeData: TagTreeNode) => {
|
||||
const params = nodeData.params;
|
||||
if (params.db) {
|
||||
changeDb({ id: params.id, host: `${params.host}`, name: params.name, type: params.type, tagPath: params.tagPath, databases: params.dbs }, params.db);
|
||||
changeDb(
|
||||
{
|
||||
id: params.id,
|
||||
host: `${params.host}`,
|
||||
name: params.name,
|
||||
type: params.type,
|
||||
tagPath: params.tagPath,
|
||||
databases: params.dbs,
|
||||
flowProcdefKey: params.flowProcdefKey,
|
||||
},
|
||||
params.db
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -263,6 +276,7 @@ const NodeTypeDbInst = new NodeType(SqlExecNodeType.DbInst).withLoadNodesFunc((p
|
||||
host: `${params.host}:${params.port}`,
|
||||
dbs: dbs,
|
||||
db: x,
|
||||
flowProcdefKey: params.flowProcdefKey,
|
||||
})
|
||||
.withIcon(DbIcon);
|
||||
});
|
||||
@@ -322,7 +336,7 @@ const NodeTypeTableMenu = new NodeType(SqlExecNodeType.TableMenu)
|
||||
])
|
||||
.withLoadNodesFunc(async (parentNode: TagTreeNode) => {
|
||||
const params = parentNode.params;
|
||||
let { id, db, type } = params;
|
||||
let { id, db, type, flowProcdefKey } = params;
|
||||
// 获取当前库的所有表信息
|
||||
let tables = await DbInst.getInst(id).loadTables(db, state.reloadStatus);
|
||||
state.reloadStatus = false;
|
||||
@@ -337,6 +351,7 @@ const NodeTypeTableMenu = new NodeType(SqlExecNodeType.TableMenu)
|
||||
id,
|
||||
db,
|
||||
type,
|
||||
flowProcdefKey: flowProcdefKey,
|
||||
key: key,
|
||||
parentKey: parentNode.key,
|
||||
tableName: x.tableName,
|
||||
@@ -417,6 +432,7 @@ const state = reactive({
|
||||
dbId: 0,
|
||||
db: '',
|
||||
dbType: '',
|
||||
flowProcdefKey: '',
|
||||
data: {},
|
||||
parentKey: '',
|
||||
},
|
||||
@@ -639,7 +655,7 @@ const reloadNode = (nodeKey: string) => {
|
||||
};
|
||||
|
||||
const onEditTable = async (data: any) => {
|
||||
let { db, id, tableName, tableComment, type, parentKey, key } = data.params;
|
||||
let { db, id, tableName, tableComment, type, parentKey, key, flowProcdefKey } = data.params;
|
||||
// data.label就是表名
|
||||
if (tableName) {
|
||||
state.tableCreateDialog.title = '修改表';
|
||||
@@ -654,15 +670,16 @@ const onEditTable = async (data: any) => {
|
||||
state.tableCreateDialog.parentKey = key;
|
||||
}
|
||||
|
||||
state.tableCreateDialog.visible = true;
|
||||
state.tableCreateDialog.activeName = '1';
|
||||
state.tableCreateDialog.dbId = id;
|
||||
state.tableCreateDialog.db = db;
|
||||
state.tableCreateDialog.dbType = type;
|
||||
state.tableCreateDialog.flowProcdefKey = flowProcdefKey;
|
||||
state.tableCreateDialog.visible = true;
|
||||
};
|
||||
|
||||
const onDeleteTable = async (data: any) => {
|
||||
let { db, id, tableName, parentKey } = data.params;
|
||||
let { db, id, tableName, parentKey, flowProcdefKey } = data.params;
|
||||
await ElMessageBox.confirm(`此操作是永久性且无法撤销,确定删除【${tableName}】? `, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
@@ -670,6 +687,10 @@ const onDeleteTable = async (data: any) => {
|
||||
});
|
||||
// 执行sql
|
||||
dbApi.sqlExec.request({ id, db, sql: `drop table ${tableName}` }).then(() => {
|
||||
if (flowProcdefKey) {
|
||||
ElMessage.success('工单提交成功');
|
||||
return;
|
||||
}
|
||||
ElMessage.success('删除成功');
|
||||
setTimeout(() => {
|
||||
parentKey && reloadNode(parentKey);
|
||||
@@ -728,6 +749,7 @@ const getNowDbInfo = () => {
|
||||
name: di.name,
|
||||
type: di.type,
|
||||
host: di.host,
|
||||
flowProcdefKey: di.flowProcdefKey,
|
||||
dbName: state.db,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -35,7 +35,7 @@ export const dbApi = {
|
||||
getSqlNames: Api.newGet('/dbs/{id}/sql-names'),
|
||||
deleteDbSql: Api.newDelete('/dbs/{id}/sql'),
|
||||
// 获取数据库sql执行记录
|
||||
getSqlExecs: Api.newGet('/dbs/{dbId}/sql-execs'),
|
||||
getSqlExecs: Api.newGet('/dbs/sql-execs'),
|
||||
|
||||
instances: Api.newGet('/instances'),
|
||||
getInstance: Api.newGet('/instances/{instanceId}'),
|
||||
@@ -84,3 +84,8 @@ export const dbApi = {
|
||||
stopDatasyncTask: Api.newPost('/datasync/tasks/{taskId}/stop'),
|
||||
datasyncLogs: Api.newGet('/datasync/tasks/{taskId}/logs'),
|
||||
};
|
||||
|
||||
export const dbSqlExecApi = {
|
||||
// 根据业务key获取sql执行信息
|
||||
getSqlExecByBizKey: Api.newGet('/dbs/sql-execs'),
|
||||
};
|
||||
|
||||
@@ -297,13 +297,16 @@ const onRunSql = async (newTab = false) => {
|
||||
sql = sql.replace(/(^\s*)/g, '');
|
||||
let execRemark = '';
|
||||
let canRun = true;
|
||||
|
||||
// 简单截取前十个字符
|
||||
const sqlPrefix = sql.slice(0, 10).toLowerCase();
|
||||
if (
|
||||
sql.startsWith('update') ||
|
||||
sql.startsWith('UPDATE') ||
|
||||
sql.startsWith('INSERT') ||
|
||||
sql.startsWith('insert') ||
|
||||
sql.startsWith('DELETE') ||
|
||||
sql.startsWith('delete')
|
||||
sqlPrefix.startsWith('update') ||
|
||||
sqlPrefix.startsWith('insert') ||
|
||||
sqlPrefix.startsWith('delete') ||
|
||||
sqlPrefix.startsWith('alert') ||
|
||||
sqlPrefix.startsWith('drop') ||
|
||||
sqlPrefix.startsWith('create')
|
||||
) {
|
||||
const res: any = await ElMessageBox.prompt('请输入备注', 'Tip', {
|
||||
confirmButtonText: '确定',
|
||||
@@ -320,6 +323,18 @@ const onRunSql = async (newTab = false) => {
|
||||
return;
|
||||
}
|
||||
|
||||
// 启用工单审批
|
||||
if (execRemark && getNowDbInst().flowProcdefKey) {
|
||||
try {
|
||||
await getNowDbInst().runSql(props.dbName, sql, execRemark);
|
||||
ElMessage.success('工单提交成功');
|
||||
return;
|
||||
} catch (e) {
|
||||
ElMessage.success('工单提交失败');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let execRes: ExecResTab;
|
||||
let i = 0;
|
||||
let id;
|
||||
|
||||
@@ -6,6 +6,7 @@ export type SqlExecProps = {
|
||||
dbId: number;
|
||||
db: string;
|
||||
dbType?: string;
|
||||
flowProcdefKey?: string;
|
||||
runSuccessCallback?: Function;
|
||||
cancelCallback?: Function;
|
||||
};
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
<el-dialog title="待执行SQL" v-model="dialogVisible" :show-close="false" width="600px" @close="cancel">
|
||||
<monaco-editor height="300px" class="codesql" language="sql" v-model="sqlValue" />
|
||||
<el-input @keyup.enter="runSql" ref="remarkInputRef" v-model="remark" placeholder="请输入执行备注" class="mt5" />
|
||||
|
||||
<div v-if="state.flowProcdefKey">
|
||||
<el-divider content-position="left">审批节点</el-divider>
|
||||
<procdef-tasks :procdef-key="state.flowProcdefKey" />
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
@@ -16,12 +22,13 @@
|
||||
<script lang="ts" setup>
|
||||
import { toRefs, ref, nextTick, reactive } from 'vue';
|
||||
import { dbApi } from '@/views/ops/db/api';
|
||||
import { ElDialog, ElButton, ElInput, ElMessage, InputInstance } from 'element-plus';
|
||||
import { ElDialog, ElButton, ElInput, ElMessage, InputInstance, ElDivider } from 'element-plus';
|
||||
// import base style
|
||||
import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
|
||||
import { format as sqlFormatter } from 'sql-formatter';
|
||||
|
||||
import { SqlExecProps } from './SqlExecBox';
|
||||
import ProcdefTasks from '@/views/flow/components/ProcdefTasks.vue';
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
@@ -44,6 +51,7 @@ const state = reactive({
|
||||
sqlValue: '',
|
||||
dbId: 0,
|
||||
db: '',
|
||||
flowProcdefKey: '' as any,
|
||||
remark: '',
|
||||
btnLoading: false,
|
||||
});
|
||||
@@ -73,6 +81,13 @@ const runSql = async () => {
|
||||
sql: state.sqlValue.trim(),
|
||||
});
|
||||
|
||||
// 存在流程审批
|
||||
if (state.flowProcdefKey) {
|
||||
runSuccess = false;
|
||||
ElMessage.success('工单提交成功');
|
||||
return;
|
||||
}
|
||||
|
||||
for (let re of res.res) {
|
||||
if (re.result !== 'success') {
|
||||
ElMessage.error(`${re.sql} \n执行失败: ${re.result}`);
|
||||
@@ -84,14 +99,16 @@ const runSql = async () => {
|
||||
ElMessage.success('执行成功');
|
||||
} catch (e) {
|
||||
runSuccess = false;
|
||||
}
|
||||
if (runSuccess) {
|
||||
if (runSuccessCallback) {
|
||||
runSuccessCallback();
|
||||
} finally {
|
||||
if (runSuccess) {
|
||||
if (runSuccessCallback) {
|
||||
runSuccessCallback();
|
||||
}
|
||||
// cancel();
|
||||
}
|
||||
state.btnLoading = false;
|
||||
cancel();
|
||||
}
|
||||
state.btnLoading = false;
|
||||
};
|
||||
|
||||
const cancel = () => {
|
||||
@@ -117,6 +134,7 @@ const open = (props: SqlExecProps) => {
|
||||
state.sqlValue = sqlFormatter(props.sql, { language: props.dbType });
|
||||
state.dbId = props.dbId;
|
||||
state.db = props.db;
|
||||
state.flowProcdefKey = props.flowProcdefKey;
|
||||
state.dialogVisible = true;
|
||||
nextTick(() => {
|
||||
setTimeout(() => {
|
||||
|
||||
@@ -766,16 +766,11 @@ const submitUpdateFields = async () => {
|
||||
res += await dbInst.genUpdateSql(db, state.table, updateColumnValue, rowData);
|
||||
}
|
||||
|
||||
dbInst.promptExeSql(
|
||||
db,
|
||||
res,
|
||||
() => {},
|
||||
() => {
|
||||
triggerRefresh();
|
||||
cellUpdateMap.clear();
|
||||
changeUpdatedField();
|
||||
}
|
||||
);
|
||||
dbInst.promptExeSql(db, res, cancelUpdateFields, () => {
|
||||
triggerRefresh();
|
||||
cellUpdateMap.clear();
|
||||
changeUpdatedField();
|
||||
});
|
||||
};
|
||||
|
||||
const cancelUpdateFields = () => {
|
||||
|
||||
@@ -151,6 +151,9 @@ const props = defineProps({
|
||||
dbType: {
|
||||
type: String,
|
||||
},
|
||||
flowProcdefKey: {
|
||||
type: String,
|
||||
},
|
||||
});
|
||||
|
||||
//定义事件
|
||||
@@ -330,6 +333,7 @@ const submit = async () => {
|
||||
dbId: props.dbId as any,
|
||||
db: props.db as any,
|
||||
dbType: dbDialect.getInfo().formatSqlDialect,
|
||||
flowProcdefKey: props.flowProcdefKey,
|
||||
runSuccessCallback: () => {
|
||||
emit('submit-sql', { tableName: state.tableData.tableName });
|
||||
// cancel();
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
:dbId="dbId"
|
||||
:db="db"
|
||||
:dbType="dbType"
|
||||
:flow-procdef-key="props.flowProcdefKey"
|
||||
:data="tableCreateDialog.data"
|
||||
v-model:visible="tableCreateDialog.visible"
|
||||
@submit-sql="onSubmitSql"
|
||||
@@ -125,7 +126,7 @@ import SqlExecBox from '../sqleditor/SqlExecBox';
|
||||
import config from '@/common/config';
|
||||
import { joinClientParams } from '@/common/request';
|
||||
import { isTrue } from '@/common/assert';
|
||||
import { compatibleMysql, DbType, editDbTypes } from '../../dialect/index';
|
||||
import { compatibleMysql, editDbTypes } from '../../dialect/index';
|
||||
|
||||
const DbTableOp = defineAsyncComponent(() => import('./DbTableOp.vue'));
|
||||
|
||||
@@ -146,6 +147,9 @@ const props = defineProps({
|
||||
type: [String],
|
||||
required: true,
|
||||
},
|
||||
flowProcdefKey: {
|
||||
type: [String],
|
||||
},
|
||||
});
|
||||
|
||||
const state = reactive({
|
||||
@@ -317,6 +321,7 @@ const dropTable = async (row: any) => {
|
||||
sql: `DROP TABLE ${tableName}`,
|
||||
dbId: props.dbId as any,
|
||||
db: props.db as any,
|
||||
flowProcdefKey: props.flowProcdefKey,
|
||||
runSuccessCallback: async () => {
|
||||
await getTables();
|
||||
},
|
||||
|
||||
@@ -40,6 +40,11 @@ export class DbInst {
|
||||
*/
|
||||
type: string;
|
||||
|
||||
/**
|
||||
* 流程定义key,若存在则需要审批执行
|
||||
*/
|
||||
flowProcdefKey: string;
|
||||
|
||||
/**
|
||||
* dbName -> db
|
||||
*/
|
||||
@@ -340,6 +345,7 @@ export class DbInst {
|
||||
dbType: this.getDialect().getInfo().formatSqlDialect,
|
||||
runSuccessCallback: successFunc,
|
||||
cancelCallback: cancelFunc,
|
||||
flowProcdefKey: this.flowProcdefKey,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -374,6 +380,7 @@ export class DbInst {
|
||||
dbInst.name = inst.name;
|
||||
dbInst.type = inst.type;
|
||||
dbInst.databases = inst.databases;
|
||||
dbInst.flowProcdefKey = inst.flowProcdefKey;
|
||||
|
||||
dbInstCache.set(dbInst.id, dbInst);
|
||||
return dbInst;
|
||||
|
||||
@@ -9,6 +9,11 @@ export const DbSqlExecTypeEnum = {
|
||||
Other: EnumValue.of(-1, 'OTHER').setTagColor('#F9E2AE'),
|
||||
};
|
||||
|
||||
export const DbSqlExecStatusEnum = {
|
||||
Success: EnumValue.of(2, '成功').setTagType('success'),
|
||||
Fail: EnumValue.of(-2, '失败').setTagType('danger'),
|
||||
};
|
||||
|
||||
export const DbDataSyncRecentStateEnum = {
|
||||
Success: EnumValue.of(1, '成功').setTagType('success'),
|
||||
Fail: EnumValue.of(-1, '失败').setTagType('danger'),
|
||||
|
||||
@@ -14,8 +14,7 @@
|
||||
}
|
||||
"
|
||||
:tag-path="form.tagPath"
|
||||
:resource-code="form.code"
|
||||
:resource-type="TagResourceTypeEnum.Machine.value"
|
||||
:select-tags="form.tagId"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
@@ -86,7 +85,6 @@ import { ElMessage } from 'element-plus';
|
||||
import TagTreeSelect from '../component/TagTreeSelect.vue';
|
||||
import SshTunnelSelect from '../component/SshTunnelSelect.vue';
|
||||
import AuthCertSelect from './authcert/AuthCertSelect.vue';
|
||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
@@ -183,7 +181,7 @@ watch(props, async (newValue: any) => {
|
||||
state.tabActiveName = 'basic';
|
||||
if (newValue.machine) {
|
||||
state.form = { ...newValue.machine };
|
||||
|
||||
state.form.tagId = newValue.machine.tags.map((t: any) => t.tagId);
|
||||
// 如果凭证类型为公共的,则表示使用授权凭证认证
|
||||
const authCertId = (state.form as any).authCertId;
|
||||
if (authCertId > 0) {
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
</template>
|
||||
|
||||
<template #tagPath="{ data }">
|
||||
<resource-tag :resource-code="data.code" :resource-type="TagResourceTypeEnum.Machine.value" />
|
||||
<ResourceTags :tags="data.tags" />
|
||||
</template>
|
||||
|
||||
<template #action="{ data }">
|
||||
@@ -124,12 +124,12 @@
|
||||
</template>
|
||||
</page-table>
|
||||
|
||||
<el-dialog v-model="infoDialog.visible">
|
||||
<el-dialog v-if="infoDialog.visible" v-model="infoDialog.visible">
|
||||
<el-descriptions title="详情" :column="3" border>
|
||||
<el-descriptions-item :span="1.5" label="机器id">{{ infoDialog.data.id }}</el-descriptions-item>
|
||||
<el-descriptions-item :span="1.5" label="名称">{{ infoDialog.data.name }}</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item :span="3" label="标签路径">{{ infoDialog.data.tagPath }}</el-descriptions-item>
|
||||
<el-descriptions-item :span="3" label="关联标签"><ResourceTags :tags="infoDialog.data.tags" /></el-descriptions-item>
|
||||
|
||||
<el-descriptions-item :span="2" label="IP">{{ infoDialog.data.ip }}</el-descriptions-item>
|
||||
<el-descriptions-item :span="1" label="端口">{{ infoDialog.data.port }}</el-descriptions-item>
|
||||
@@ -187,7 +187,7 @@ import { useRouter, useRoute } from 'vue-router';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { machineApi, getMachineTerminalSocketUrl } from './api';
|
||||
import { dateFormat } from '@/common/utils/date';
|
||||
import ResourceTag from '../component/ResourceTag.vue';
|
||||
import ResourceTags from '../component/ResourceTags.vue';
|
||||
import PageTable from '@/components/pagetable/PageTable.vue';
|
||||
import { TableColumn } from '@/components/pagetable';
|
||||
import { hasPerms } from '@/components/auth/auth';
|
||||
@@ -220,13 +220,13 @@ const perms = {
|
||||
const searchItems = [getTagPathSearchItem(TagResourceTypeEnum.Machine.value), SearchItem.input('ip', 'IP'), SearchItem.input('name', '名称')];
|
||||
|
||||
const columns = [
|
||||
TableColumn.new('tags[0].tagPath', '关联标签').isSlot('tagPath').setAddWidth(20),
|
||||
TableColumn.new('name', '名称'),
|
||||
TableColumn.new('ipPort', 'ip:port').isSlot().setAddWidth(50),
|
||||
TableColumn.new('stat', '运行状态').isSlot().setAddWidth(55),
|
||||
TableColumn.new('fs', '磁盘(挂载点=>可用/总)').isSlot().setAddWidth(25),
|
||||
TableColumn.new('username', '用户名'),
|
||||
TableColumn.new('status', '状态').isSlot().setMinWidth(85),
|
||||
TableColumn.new('tagPath', '关联标签').isSlot().setAddWidth(10).alignCenter(),
|
||||
TableColumn.new('remark', '备注'),
|
||||
TableColumn.new('action', '操作').isSlot().setMinWidth(238).fixedRight().alignCenter(),
|
||||
];
|
||||
|
||||
@@ -59,8 +59,9 @@
|
||||
</el-popover>
|
||||
</template>
|
||||
|
||||
<div class="terminal-wrapper" :style="{ height: `calc(100vh - 155px)` }">
|
||||
<div class="terminal-wrapper" style="height: calc(100vh - 155px)">
|
||||
<TerminalBody
|
||||
:mount-init="false"
|
||||
@status-change="terminalStatusChange(dt.key, $event)"
|
||||
:ref="(el) => setTerminalRef(el, dt.key)"
|
||||
:socket-url="dt.socketUrl"
|
||||
@@ -113,7 +114,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, toRefs, reactive, defineAsyncComponent } from 'vue';
|
||||
import { ref, toRefs, reactive, defineAsyncComponent, nextTick } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { machineApi, getMachineTerminalSocketUrl } from './api';
|
||||
import { dateFormat } from '@/common/utils/date';
|
||||
@@ -274,7 +275,10 @@ const openTerminal = (machine: any, ex?: boolean) => {
|
||||
socketUrl: getMachineTerminalSocketUrl(id),
|
||||
});
|
||||
state.activeTermName = key;
|
||||
fitTerminal();
|
||||
|
||||
nextTick(() => {
|
||||
handleReconnect(key);
|
||||
});
|
||||
};
|
||||
|
||||
const serviceManager = (row: any) => {
|
||||
|
||||
@@ -13,8 +13,7 @@
|
||||
}
|
||||
"
|
||||
multiple
|
||||
:resource-code="form.code"
|
||||
:resource-type="TagResourceTypeEnum.Mongo.value"
|
||||
:select-tags="form.tagId"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
@@ -58,7 +57,6 @@ import { mongoApi } from './api';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import TagTreeSelect from '../component/TagTreeSelect.vue';
|
||||
import SshTunnelSelect from '../component/SshTunnelSelect.vue';
|
||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
@@ -129,6 +127,7 @@ watch(props, async (newValue: any) => {
|
||||
state.tabActiveName = 'basic';
|
||||
if (newValue.mongo) {
|
||||
state.form = { ...newValue.mongo };
|
||||
state.form.tagId = newValue.mongo.tags.map((t: any) => t.tagId);
|
||||
} else {
|
||||
state.form = { db: 0 } as any;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
</template>
|
||||
|
||||
<template #tagPath="{ data }">
|
||||
<resource-tag :resource-code="data.code" :resource-type="TagResourceTypeEnum.Mongo.value" />
|
||||
<resource-tags :tags="data.tags" />
|
||||
</template>
|
||||
|
||||
<template #action="{ data }">
|
||||
@@ -45,7 +45,7 @@
|
||||
import { mongoApi } from './api';
|
||||
import { defineAsyncComponent, ref, toRefs, reactive, onMounted, Ref } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import ResourceTag from '../component/ResourceTag.vue';
|
||||
import ResourceTags from '../component/ResourceTags.vue';
|
||||
import PageTable from '@/components/pagetable/PageTable.vue';
|
||||
import { TableColumn } from '@/components/pagetable';
|
||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||
@@ -62,9 +62,9 @@ const pageTableRef: Ref<any> = ref(null);
|
||||
const searchItems = [getTagPathSearchItem(TagResourceTypeEnum.Mongo.value)];
|
||||
|
||||
const columns = [
|
||||
TableColumn.new('tags[0].tagPath', '关联标签').isSlot('tagPath').setAddWidth(20),
|
||||
TableColumn.new('name', '名称'),
|
||||
TableColumn.new('uri', '连接uri'),
|
||||
TableColumn.new('tagPath', '关联标签').isSlot().setAddWidth(20).alignCenter(),
|
||||
TableColumn.new('createTime', '创建时间').isTime(),
|
||||
TableColumn.new('creator', '创建人'),
|
||||
TableColumn.new('action', '操作').isSlot().setMinWidth(170).fixedRight().alignCenter(),
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
import { reactive, watch, toRefs, onMounted } from 'vue';
|
||||
import { redisApi } from './api';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { formatTime } from '@/common/utils/format';
|
||||
|
||||
const props = defineProps({
|
||||
redisId: {
|
||||
@@ -174,33 +175,7 @@ const ttlConveter = (ttl: any) => {
|
||||
if (!ttl) {
|
||||
ttl = 0;
|
||||
}
|
||||
let second = parseInt(ttl); // 秒
|
||||
let min = 0; // 分
|
||||
let hour = 0; // 小时
|
||||
let day = 0;
|
||||
if (second > 60) {
|
||||
min = parseInt(second / 60 + '');
|
||||
second = second % 60;
|
||||
if (min > 60) {
|
||||
hour = parseInt(min / 60 + '');
|
||||
min = min % 60;
|
||||
if (hour > 24) {
|
||||
day = parseInt(hour / 24 + '');
|
||||
hour = hour % 24;
|
||||
}
|
||||
}
|
||||
}
|
||||
let result = '' + second + 's';
|
||||
if (min > 0) {
|
||||
result = '' + min + 'm:' + result;
|
||||
}
|
||||
if (hour > 0) {
|
||||
result = '' + hour + 'h:' + result;
|
||||
}
|
||||
if (day > 0) {
|
||||
result = '' + day + 'd:' + result;
|
||||
}
|
||||
return result;
|
||||
return formatTime(ttl);
|
||||
};
|
||||
</script>
|
||||
<style lang="scss">
|
||||
|
||||
@@ -13,8 +13,7 @@
|
||||
}
|
||||
"
|
||||
multiple
|
||||
:resource-code="form.code"
|
||||
:resource-type="TagResourceTypeEnum.Redis.value"
|
||||
:select-tags="form.tagId"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
@@ -100,7 +99,6 @@ import { ElMessage } from 'element-plus';
|
||||
import { RsaEncrypt } from '@/common/rsa';
|
||||
import TagTreeSelect from '../component/TagTreeSelect.vue';
|
||||
import SshTunnelSelect from '../component/SshTunnelSelect.vue';
|
||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
@@ -191,6 +189,7 @@ watch(props, async (newValue: any) => {
|
||||
state.tabActiveName = 'basic';
|
||||
if (newValue.redis) {
|
||||
state.form = { ...newValue.redis };
|
||||
state.form.tagId = newValue.redis.tags.map((t: any) => t.tagId);
|
||||
convertDb(state.form.db);
|
||||
} else {
|
||||
state.form = { db: '0' } as any;
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
</template>
|
||||
|
||||
<template #tagPath="{ data }">
|
||||
<resource-tag :resource-code="data.code" :resource-type="TagResourceTypeEnum.Redis.value" />
|
||||
<resource-tags :tags="data.tags" />
|
||||
</template>
|
||||
|
||||
<template #action="{ data }">
|
||||
@@ -116,12 +116,12 @@
|
||||
</el-table>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog v-model="detailDialog.visible">
|
||||
<el-dialog v-if="detailDialog.visible" v-model="detailDialog.visible">
|
||||
<el-descriptions title="详情" :column="3" border>
|
||||
<el-descriptions-item :span="1.5" label="id">{{ detailDialog.data.id }}</el-descriptions-item>
|
||||
<el-descriptions-item :span="1.5" label="名称">{{ detailDialog.data.name }}</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item :span="3" label="标签路径">{{ detailDialog.data.tagPath }}</el-descriptions-item>
|
||||
<el-descriptions-item :span="3" label="关联标签"><ResourceTags :tags="detailDialog.data.tags" /></el-descriptions-item>
|
||||
|
||||
<el-descriptions-item :span="3" label="主机">{{ detailDialog.data.host }}</el-descriptions-item>
|
||||
|
||||
@@ -154,7 +154,7 @@ import { ref, toRefs, reactive, onMounted, Ref } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import RedisEdit from './RedisEdit.vue';
|
||||
import { dateFormat } from '@/common/utils/date';
|
||||
import ResourceTag from '../component/ResourceTag.vue';
|
||||
import ResourceTags from '../component/ResourceTags.vue';
|
||||
import PageTable from '@/components/pagetable/PageTable.vue';
|
||||
import { TableColumn } from '@/components/pagetable';
|
||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||
@@ -167,10 +167,10 @@ const pageTableRef: Ref<any> = ref(null);
|
||||
const searchItems = [getTagPathSearchItem(TagResourceTypeEnum.Redis.value)];
|
||||
|
||||
const columns = ref([
|
||||
TableColumn.new('tags[0].tagPath', '关联标签').isSlot('tagPath').setAddWidth(20),
|
||||
TableColumn.new('name', '名称'),
|
||||
TableColumn.new('host', 'host:port'),
|
||||
TableColumn.new('mode', 'mode'),
|
||||
TableColumn.new('tagPath', '关联标签').isSlot().setAddWidth(10).alignCenter(),
|
||||
TableColumn.new('remark', '备注'),
|
||||
TableColumn.new('action', '操作').isSlot().setMinWidth(200).fixedRight().alignCenter(),
|
||||
]);
|
||||
|
||||
@@ -415,4 +415,3 @@ const removeDeafultExpandId = (id: any) => {
|
||||
user-select: none;
|
||||
}
|
||||
</style>
|
||||
@/components/contextmenu
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-form-item label="账号">
|
||||
<el-form-item :label="label">
|
||||
<el-select
|
||||
style="width: 100%"
|
||||
remote
|
||||
@@ -16,7 +16,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { accountApi } from '../../api';
|
||||
|
||||
const props = defineProps({
|
||||
@@ -25,6 +25,19 @@ const props = defineProps({
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: '账号',
|
||||
},
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
// 如果初始化时有accountId,则需要获取对应用户信息,用于回显用户名等信息
|
||||
if (accountId.value) {
|
||||
accountApi.querySimple.request({ ids: accountId.value }).then((res) => {
|
||||
accounts.value = res.list;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const accountId = defineModel('modelValue');
|
||||
@@ -33,7 +46,7 @@ const accounts: any = ref([]);
|
||||
|
||||
const getAccount = (username: any) => {
|
||||
if (username) {
|
||||
accountApi.list.request({ username }).then((res) => {
|
||||
accountApi.querySimple.request({ username }).then((res) => {
|
||||
accounts.value = res.list;
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -24,6 +24,7 @@ export const roleApi = {
|
||||
|
||||
export const accountApi = {
|
||||
list: Api.newGet('/sys/accounts'),
|
||||
querySimple: Api.newGet('/sys/accounts/simple'),
|
||||
save: Api.newPost('/sys/accounts'),
|
||||
update: Api.newPut('/sys/accounts/{id}'),
|
||||
del: Api.newDelete('/sys/accounts/{id}'),
|
||||
|
||||
@@ -30,7 +30,7 @@ require (
|
||||
github.com/sijms/go-ora/v2 v2.8.9
|
||||
github.com/stretchr/testify v1.8.4
|
||||
go.mongodb.org/mongo-driver v1.14.0 // mongo
|
||||
golang.org/x/crypto v0.19.0 // ssh
|
||||
golang.org/x/crypto v0.20.0 // ssh
|
||||
golang.org/x/oauth2 v0.17.0
|
||||
golang.org/x/sync v0.6.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
msgapp "mayfly-go/internal/msg/application"
|
||||
msgdto "mayfly-go/internal/msg/application/dto"
|
||||
tagapp "mayfly-go/internal/tag/application"
|
||||
tagentity "mayfly-go/internal/tag/domain/entity"
|
||||
"mayfly-go/pkg/biz"
|
||||
"mayfly-go/pkg/logx"
|
||||
"mayfly-go/pkg/model"
|
||||
@@ -49,8 +50,15 @@ func (d *Db) Dbs(rc *req.Ctx) {
|
||||
}
|
||||
queryCond.Codes = codes
|
||||
|
||||
res, err := d.DbApp.GetPageList(queryCond, page, new([]vo.DbListVO))
|
||||
var dbvos []*vo.DbListVO
|
||||
res, err := d.DbApp.GetPageList(queryCond, page, &dbvos)
|
||||
biz.ErrIsNil(err)
|
||||
|
||||
// 填充标签信息
|
||||
d.TagApp.FillTagInfo(collx.ArrayMap(dbvos, func(dbvo *vo.DbListVO) tagentity.ITagResource {
|
||||
return dbvo
|
||||
})...)
|
||||
|
||||
rc.ResData = res
|
||||
}
|
||||
|
||||
@@ -117,7 +125,7 @@ func (d *Db) ExecSql(rc *req.Ctx) {
|
||||
s = stringx.TrimSpaceAndBr(s)
|
||||
// 多条执行,暂不支持查询语句
|
||||
if isMulti {
|
||||
biz.IsTrue(!strings.HasPrefix(strings.ToLower(s), "select"), "多条语句执行暂不不支持select语句")
|
||||
biz.IsTrue(!strings.HasPrefix(strings.ToLower(s[:10]), "select"), "多条语句执行暂不不支持select语句")
|
||||
}
|
||||
|
||||
execReq.Sql = s
|
||||
@@ -132,8 +140,10 @@ func (d *Db) ExecSql(rc *req.Ctx) {
|
||||
}
|
||||
|
||||
colAndRes := make(map[string]any)
|
||||
colAndRes["columns"] = execResAll.Columns
|
||||
colAndRes["res"] = execResAll.Res
|
||||
if execResAll != nil {
|
||||
colAndRes["columns"] = execResAll.Columns
|
||||
colAndRes["res"] = execResAll.Res
|
||||
}
|
||||
rc.ResData = colAndRes
|
||||
}
|
||||
|
||||
@@ -161,6 +171,8 @@ func (d *Db) ExecSqlFile(rc *req.Ctx) {
|
||||
clientId := rc.Query("clientId")
|
||||
|
||||
dbConn, err := d.DbApp.GetDbConn(dbId, dbName)
|
||||
// 开启流程审批时,执行文件暂时还未处理
|
||||
biz.IsTrue(dbConn.Info.FlowProcdefKey == "", "该库已开启流程审批,暂不支持该操作")
|
||||
biz.ErrIsNil(err)
|
||||
biz.ErrIsNilAppendErr(d.TagApp.CanAccess(rc.GetLoginAccount().Id, dbConn.Info.TagPath...), "%s")
|
||||
rc.ReqParam = fmt.Sprintf("filename: %s -> %s", filename, dbConn.Info.GetLogDesc())
|
||||
|
||||
@@ -5,6 +5,9 @@ import (
|
||||
"mayfly-go/internal/db/domain/entity"
|
||||
"mayfly-go/pkg/biz"
|
||||
"mayfly-go/pkg/req"
|
||||
"mayfly-go/pkg/utils/collx"
|
||||
"mayfly-go/pkg/utils/conv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type DbSqlExec struct {
|
||||
@@ -13,6 +16,12 @@ type DbSqlExec struct {
|
||||
|
||||
func (d *DbSqlExec) DbSqlExecs(rc *req.Ctx) {
|
||||
queryCond, page := req.BindQueryAndPage(rc, new(entity.DbSqlExecQuery))
|
||||
|
||||
if statusStr := rc.Query("status"); statusStr != "" {
|
||||
queryCond.Status = collx.ArrayMap[string, int8](strings.Split(statusStr, ","), func(val string) int8 {
|
||||
return int8(conv.Str2Int(val, 0))
|
||||
})
|
||||
}
|
||||
res, err := d.DbSqlExecApp.GetPageList(queryCond, page, new([]entity.DbSqlExec))
|
||||
biz.ErrIsNil(err)
|
||||
rc.ResData = res
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
package form
|
||||
|
||||
type DbForm struct {
|
||||
Id uint64 `json:"id"`
|
||||
Name string `binding:"required" json:"name"`
|
||||
Database string `json:"database"`
|
||||
Remark string `json:"remark"`
|
||||
TagId []uint64 `binding:"required" json:"tagId"`
|
||||
InstanceId uint64 `binding:"required" json:"instanceId"`
|
||||
Id uint64 `json:"id"`
|
||||
Name string `binding:"required" json:"name"`
|
||||
Database string `json:"database"`
|
||||
Remark string `json:"remark"`
|
||||
TagId []uint64 `binding:"required" json:"tagId"`
|
||||
InstanceId uint64 `binding:"required" json:"instanceId"`
|
||||
FlowProcdefKey string `json:"flowProcdefKey"`
|
||||
}
|
||||
|
||||
type DbSqlSaveForm struct {
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
package vo
|
||||
|
||||
import "time"
|
||||
import (
|
||||
tagentity "mayfly-go/internal/tag/domain/entity"
|
||||
"time"
|
||||
)
|
||||
|
||||
type DbListVO struct {
|
||||
tagentity.ResourceTags
|
||||
|
||||
Id *int64 `json:"id"`
|
||||
Code string `json:"code"`
|
||||
Name *string `json:"name"`
|
||||
@@ -16,6 +21,8 @@ type DbListVO struct {
|
||||
Port int `json:"port"`
|
||||
Username string `json:"username"`
|
||||
|
||||
FlowProcdefKey string `json:"flowProcdefKey"`
|
||||
|
||||
CreateTime *time.Time `json:"createTime"`
|
||||
Creator *string `json:"creator"`
|
||||
CreatorId *int64 `json:"creatorId"`
|
||||
@@ -23,3 +30,7 @@ type DbListVO struct {
|
||||
Modifier *string `json:"modifier"`
|
||||
ModifierId *int64 `json:"modifierId"`
|
||||
}
|
||||
|
||||
func (d DbListVO) GetCode() string {
|
||||
return d.Code
|
||||
}
|
||||
|
||||
@@ -34,9 +34,14 @@ func Init() {
|
||||
panic(fmt.Sprintf("初始化 DbBinlogApp 失败: %v", err))
|
||||
}
|
||||
GetDataSyncTaskApp().InitCronJob()
|
||||
InitDbFlowHandler()
|
||||
})()
|
||||
}
|
||||
|
||||
func GetDbSqlExecApp() DbSqlExec {
|
||||
return ioc.Get[DbSqlExec]("DbSqlExecApp")
|
||||
}
|
||||
|
||||
func GetDbBackupApp() *DbBackupApp {
|
||||
return ioc.Get[*DbBackupApp]("DbBackupApp")
|
||||
}
|
||||
|
||||
@@ -168,7 +168,12 @@ func (d *dbAppImpl) GetDbConn(dbId uint64, dbName string) (*dbi.DbConn, error) {
|
||||
if err := instance.PwdDecrypt(); err != nil {
|
||||
return nil, errorx.NewBiz(err.Error())
|
||||
}
|
||||
return toDbInfo(instance, dbId, dbName, d.tagApp.ListTagPathByResource(consts.TagResourceTypeDb, db.Code)...), nil
|
||||
di := toDbInfo(instance, dbId, dbName, d.tagApp.ListTagPathByResource(consts.TagResourceTypeDb, db.Code)...)
|
||||
if db.FlowProcdefKey != nil {
|
||||
di.FlowProcdefKey = *db.FlowProcdefKey
|
||||
}
|
||||
|
||||
return di, nil
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
11
server/internal/db/application/db_flow.go
Normal file
11
server/internal/db/application/db_flow.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package application
|
||||
|
||||
import flowapp "mayfly-go/internal/flow/application"
|
||||
|
||||
const (
|
||||
DbSqlExecFlowBizType = "db_sql_exec_flow" // db sql exec flow biz type
|
||||
)
|
||||
|
||||
func InitDbFlowHandler() {
|
||||
flowapp.RegisterBizHandler(DbSqlExecFlowBizType, GetDbSqlExecApp())
|
||||
}
|
||||
@@ -7,11 +7,14 @@ import (
|
||||
"mayfly-go/internal/db/dbm/dbi"
|
||||
"mayfly-go/internal/db/domain/entity"
|
||||
"mayfly-go/internal/db/domain/repository"
|
||||
flowapp "mayfly-go/internal/flow/application"
|
||||
flowentity "mayfly-go/internal/flow/domain/entity"
|
||||
"mayfly-go/pkg/contextx"
|
||||
"mayfly-go/pkg/errorx"
|
||||
"mayfly-go/pkg/logx"
|
||||
"mayfly-go/pkg/model"
|
||||
"mayfly-go/pkg/utils/jsonx"
|
||||
"mayfly-go/pkg/utils/stringx"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -47,6 +50,8 @@ func (d *DbSqlExecRes) Merge(execRes *DbSqlExecRes) {
|
||||
}
|
||||
|
||||
type DbSqlExec interface {
|
||||
flowapp.FlowBizHandler
|
||||
|
||||
// 执行sql
|
||||
Exec(ctx context.Context, execSqlReq *DbSqlExecReq) (*DbSqlExecRes, error)
|
||||
|
||||
@@ -58,7 +63,10 @@ type DbSqlExec interface {
|
||||
}
|
||||
|
||||
type dbSqlExecAppImpl struct {
|
||||
dbApp Db `inject:"DbApp"`
|
||||
dbSqlExecRepo repository.DbSqlExec `inject:"DbSqlExecRepo"`
|
||||
|
||||
flowProcinstApp flowapp.Procinst `inject:"ProcinstApp"`
|
||||
}
|
||||
|
||||
func createSqlExecRecord(ctx context.Context, execSqlReq *DbSqlExecReq) *entity.DbSqlExec {
|
||||
@@ -67,6 +75,7 @@ func createSqlExecRecord(ctx context.Context, execSqlReq *DbSqlExecReq) *entity.
|
||||
dbSqlExecRecord.Db = execSqlReq.Db
|
||||
dbSqlExecRecord.Sql = execSqlReq.Sql
|
||||
dbSqlExecRecord.Remark = execSqlReq.Remark
|
||||
dbSqlExecRecord.Status = entity.DbSqlExecStatusSuccess
|
||||
dbSqlExecRecord.FillBaseInfo(model.IdGenTypeNone, contextx.GetLoginAccount(ctx))
|
||||
return dbSqlExecRecord
|
||||
}
|
||||
@@ -105,9 +114,9 @@ func (d *dbSqlExecAppImpl) Exec(ctx context.Context, execSqlReq *DbSqlExecReq) (
|
||||
}
|
||||
var execErr error
|
||||
if isSelect || strings.HasPrefix(lowerSql, "show") {
|
||||
execRes, execErr = doRead(ctx, execSqlReq)
|
||||
execRes, execErr = d.doRead(ctx, execSqlReq)
|
||||
} else {
|
||||
execRes, execErr = doExec(ctx, execSqlReq.Sql, execSqlReq.DbConn)
|
||||
execRes, execErr = d.doExec(ctx, execSqlReq, dbSqlExecRecord)
|
||||
}
|
||||
if execErr != nil {
|
||||
return nil, execErr
|
||||
@@ -119,29 +128,76 @@ func (d *dbSqlExecAppImpl) Exec(ctx context.Context, execSqlReq *DbSqlExecReq) (
|
||||
switch stmt := stmt.(type) {
|
||||
case *sqlparser.Select:
|
||||
isSelect = true
|
||||
execRes, err = doSelect(ctx, stmt, execSqlReq)
|
||||
execRes, err = d.doSelect(ctx, stmt, execSqlReq)
|
||||
case *sqlparser.Show:
|
||||
isSelect = true
|
||||
execRes, err = doRead(ctx, execSqlReq)
|
||||
execRes, err = d.doRead(ctx, execSqlReq)
|
||||
case *sqlparser.OtherRead:
|
||||
isSelect = true
|
||||
execRes, err = doRead(ctx, execSqlReq)
|
||||
execRes, err = d.doRead(ctx, execSqlReq)
|
||||
case *sqlparser.Update:
|
||||
execRes, err = doUpdate(ctx, stmt, execSqlReq, dbSqlExecRecord)
|
||||
execRes, err = d.doUpdate(ctx, stmt, execSqlReq, dbSqlExecRecord)
|
||||
case *sqlparser.Delete:
|
||||
execRes, err = doDelete(ctx, stmt, execSqlReq, dbSqlExecRecord)
|
||||
execRes, err = d.doDelete(ctx, stmt, execSqlReq, dbSqlExecRecord)
|
||||
case *sqlparser.Insert:
|
||||
execRes, err = doInsert(ctx, stmt, execSqlReq, dbSqlExecRecord)
|
||||
execRes, err = d.doInsert(ctx, stmt, execSqlReq, dbSqlExecRecord)
|
||||
default:
|
||||
execRes, err = doExec(ctx, execSqlReq.Sql, execSqlReq.DbConn)
|
||||
execRes, err = d.doExec(ctx, execSqlReq, dbSqlExecRecord)
|
||||
}
|
||||
|
||||
d.saveSqlExecLog(isSelect, dbSqlExecRecord)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d.saveSqlExecLog(isSelect, dbSqlExecRecord)
|
||||
return execRes, nil
|
||||
}
|
||||
|
||||
func (d *dbSqlExecAppImpl) FlowBizHandle(ctx context.Context, procinstStatus flowentity.ProcinstStatus, bizKey string) error {
|
||||
logx.Debugf("DbSqlExec FlowBizHandle -> bizKey: %s, procinstStatus: %s", bizKey, flowentity.ProcinstStatusEnum.GetDesc(procinstStatus))
|
||||
// 流程挂起不处理
|
||||
if procinstStatus == flowentity.ProcinstSuspended {
|
||||
return nil
|
||||
}
|
||||
dbSqlExec := &entity.DbSqlExec{FlowBizKey: bizKey}
|
||||
if err := d.dbSqlExecRepo.GetBy(dbSqlExec); err != nil {
|
||||
logx.Errorf("flow-[%s]关联的sql执行信息不存在", bizKey)
|
||||
return nil
|
||||
}
|
||||
|
||||
if procinstStatus != flowentity.ProcinstCompleted {
|
||||
dbSqlExec.Status = entity.DbSqlExecStatusNo
|
||||
dbSqlExec.Res = fmt.Sprintf("流程%s", flowentity.ProcinstStatusEnum.GetDesc(procinstStatus))
|
||||
return d.dbSqlExecRepo.UpdateById(ctx, dbSqlExec)
|
||||
}
|
||||
|
||||
dbSqlExec.Status = entity.DbSqlExecStatusFail
|
||||
dbConn, err := d.dbApp.GetDbConn(dbSqlExec.DbId, dbSqlExec.Db)
|
||||
if err != nil {
|
||||
dbSqlExec.Res = err.Error()
|
||||
d.dbSqlExecRepo.UpdateById(ctx, dbSqlExec)
|
||||
return err
|
||||
}
|
||||
|
||||
rowsAffected, err := dbConn.ExecContext(ctx, dbSqlExec.Sql)
|
||||
if err != nil {
|
||||
dbSqlExec.Res = err.Error()
|
||||
d.dbSqlExecRepo.UpdateById(ctx, dbSqlExec)
|
||||
return err
|
||||
}
|
||||
|
||||
dbSqlExec.Status = entity.DbSqlExecStatusSuccess
|
||||
dbSqlExec.Res = fmt.Sprintf("执行成功,影响条数: %d", rowsAffected)
|
||||
return d.dbSqlExecRepo.UpdateById(ctx, dbSqlExec)
|
||||
}
|
||||
|
||||
func (d *dbSqlExecAppImpl) DeleteBy(ctx context.Context, condition *entity.DbSqlExec) {
|
||||
d.dbSqlExecRepo.DeleteByCond(ctx, condition)
|
||||
}
|
||||
|
||||
func (d *dbSqlExecAppImpl) GetPageList(condition *entity.DbSqlExecQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
|
||||
return d.dbSqlExecRepo.GetPageList(condition, pageParam, toEntity, orderBy...)
|
||||
}
|
||||
|
||||
// 保存sql执行记录,如果是查询类则根据系统配置判断是否保存
|
||||
func (d *dbSqlExecAppImpl) saveSqlExecLog(isQuery bool, dbSqlExecRecord *entity.DbSqlExec) {
|
||||
if !isQuery {
|
||||
@@ -156,15 +212,7 @@ func (d *dbSqlExecAppImpl) saveSqlExecLog(isQuery bool, dbSqlExecRecord *entity.
|
||||
}
|
||||
}
|
||||
|
||||
func (d *dbSqlExecAppImpl) DeleteBy(ctx context.Context, condition *entity.DbSqlExec) {
|
||||
d.dbSqlExecRepo.DeleteByCond(ctx, condition)
|
||||
}
|
||||
|
||||
func (d *dbSqlExecAppImpl) GetPageList(condition *entity.DbSqlExecQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
|
||||
return d.dbSqlExecRepo.GetPageList(condition, pageParam, toEntity, orderBy...)
|
||||
}
|
||||
|
||||
func doSelect(ctx context.Context, selectStmt *sqlparser.Select, execSqlReq *DbSqlExecReq) (*DbSqlExecRes, error) {
|
||||
func (d *dbSqlExecAppImpl) doSelect(ctx context.Context, selectStmt *sqlparser.Select, execSqlReq *DbSqlExecReq) (*DbSqlExecRes, error) {
|
||||
selectExprsStr := sqlparser.String(selectStmt.SelectExprs)
|
||||
if selectExprsStr == "*" || strings.Contains(selectExprsStr, ".*") ||
|
||||
len(strings.Split(selectExprsStr, ",")) > 1 {
|
||||
@@ -189,10 +237,10 @@ func doSelect(ctx context.Context, selectStmt *sqlparser.Select, execSqlReq *DbS
|
||||
}
|
||||
}
|
||||
|
||||
return doRead(ctx, execSqlReq)
|
||||
return d.doRead(ctx, execSqlReq)
|
||||
}
|
||||
|
||||
func doRead(ctx context.Context, execSqlReq *DbSqlExecReq) (*DbSqlExecRes, error) {
|
||||
func (d *dbSqlExecAppImpl) doRead(ctx context.Context, execSqlReq *DbSqlExecReq) (*DbSqlExecRes, error) {
|
||||
dbConn := execSqlReq.DbConn
|
||||
sql := execSqlReq.Sql
|
||||
cols, res, err := dbConn.QueryContext(ctx, sql)
|
||||
@@ -205,7 +253,7 @@ func doRead(ctx context.Context, execSqlReq *DbSqlExecReq) (*DbSqlExecRes, error
|
||||
}, nil
|
||||
}
|
||||
|
||||
func doUpdate(ctx context.Context, update *sqlparser.Update, execSqlReq *DbSqlExecReq, dbSqlExec *entity.DbSqlExec) (*DbSqlExecRes, error) {
|
||||
func (d *dbSqlExecAppImpl) doUpdate(ctx context.Context, update *sqlparser.Update, execSqlReq *DbSqlExecReq, dbSqlExec *entity.DbSqlExec) (*DbSqlExecRes, error) {
|
||||
dbConn := execSqlReq.DbConn
|
||||
|
||||
tableStr := sqlparser.String(update.TableExprs)
|
||||
@@ -256,10 +304,10 @@ func doUpdate(ctx context.Context, update *sqlparser.Update, execSqlReq *DbSqlEx
|
||||
dbSqlExec.Table = tableName
|
||||
dbSqlExec.Type = entity.DbSqlExecTypeUpdate
|
||||
|
||||
return doExec(ctx, execSqlReq.Sql, dbConn)
|
||||
return d.doExec(ctx, execSqlReq, dbSqlExec)
|
||||
}
|
||||
|
||||
func doDelete(ctx context.Context, delete *sqlparser.Delete, execSqlReq *DbSqlExecReq, dbSqlExec *entity.DbSqlExec) (*DbSqlExecRes, error) {
|
||||
func (d *dbSqlExecAppImpl) doDelete(ctx context.Context, delete *sqlparser.Delete, execSqlReq *DbSqlExecReq, dbSqlExec *entity.DbSqlExec) (*DbSqlExecRes, error) {
|
||||
dbConn := execSqlReq.DbConn
|
||||
|
||||
tableStr := sqlparser.String(delete.TableExprs)
|
||||
@@ -278,24 +326,47 @@ func doDelete(ctx context.Context, delete *sqlparser.Delete, execSqlReq *DbSqlEx
|
||||
dbSqlExec.Table = table
|
||||
dbSqlExec.Type = entity.DbSqlExecTypeDelete
|
||||
|
||||
return doExec(ctx, execSqlReq.Sql, dbConn)
|
||||
return d.doExec(ctx, execSqlReq, dbSqlExec)
|
||||
}
|
||||
|
||||
func doInsert(ctx context.Context, insert *sqlparser.Insert, execSqlReq *DbSqlExecReq, dbSqlExec *entity.DbSqlExec) (*DbSqlExecRes, error) {
|
||||
func (d *dbSqlExecAppImpl) doInsert(ctx context.Context, insert *sqlparser.Insert, execSqlReq *DbSqlExecReq, dbSqlExec *entity.DbSqlExec) (*DbSqlExecRes, error) {
|
||||
tableStr := sqlparser.String(insert.Table)
|
||||
// 可能使用别名,故空格切割
|
||||
table := strings.Split(tableStr, " ")[0]
|
||||
dbSqlExec.Table = table
|
||||
dbSqlExec.Type = entity.DbSqlExecTypeInsert
|
||||
|
||||
return doExec(ctx, execSqlReq.Sql, execSqlReq.DbConn)
|
||||
return d.doExec(ctx, execSqlReq, dbSqlExec)
|
||||
}
|
||||
|
||||
func doExec(ctx context.Context, sql string, dbConn *dbi.DbConn) (*DbSqlExecRes, error) {
|
||||
func (d *dbSqlExecAppImpl) doExec(ctx context.Context, execSqlReq *DbSqlExecReq, dbSqlExecRecord *entity.DbSqlExec) (*DbSqlExecRes, error) {
|
||||
dbConn := execSqlReq.DbConn
|
||||
flowProcdefKey := dbConn.Info.FlowProcdefKey
|
||||
if flowProcdefKey != "" {
|
||||
bizKey := stringx.Rand(24)
|
||||
// 如果该库关联了审批流程,则启动流程实例即可
|
||||
_, err := d.flowProcinstApp.StartProc(ctx, flowProcdefKey, &flowapp.StarProcParam{
|
||||
BizType: DbSqlExecFlowBizType,
|
||||
BizKey: bizKey,
|
||||
Remark: dbSqlExecRecord.Remark,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dbSqlExecRecord.FlowBizKey = bizKey
|
||||
dbSqlExecRecord.Status = entity.DbSqlExecStatusWait
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
sql := execSqlReq.Sql
|
||||
rowsAffected, err := dbConn.ExecContext(ctx, sql)
|
||||
execRes := "success"
|
||||
if err != nil {
|
||||
execRes = err.Error()
|
||||
dbSqlExecRecord.Status = entity.DbSqlExecStatusFail
|
||||
dbSqlExecRecord.Res = err.Error()
|
||||
} else {
|
||||
dbSqlExecRecord.Res = fmt.Sprintf("执行成功,影响条数: %d", rowsAffected)
|
||||
}
|
||||
res := make([]map[string]any, 0)
|
||||
resData := make(map[string]any)
|
||||
|
||||
@@ -23,6 +23,7 @@ type DbInfo struct {
|
||||
Params string
|
||||
Database string
|
||||
|
||||
FlowProcdefKey string // 流程定义key
|
||||
TagPath []string
|
||||
SshTunnelMachineId int
|
||||
|
||||
|
||||
@@ -7,9 +7,10 @@ import (
|
||||
type Db struct {
|
||||
model.Model
|
||||
|
||||
Code string `orm:"column(code)" json:"code"`
|
||||
Name string `orm:"column(name)" json:"name"`
|
||||
Database string `orm:"column(database)" json:"database"`
|
||||
Remark string `json:"remark"`
|
||||
InstanceId uint64
|
||||
Code string `orm:"column(code)" json:"code"`
|
||||
Name string `orm:"column(name)" json:"name"`
|
||||
Database string `orm:"column(database)" json:"database"`
|
||||
Remark string `json:"remark"`
|
||||
InstanceId uint64
|
||||
FlowProcdefKey *string `json:"flowProcdefKey"` // 审批流-流程定义key(有值则说明关键操作需要进行审批执行),使用指针为了方便更新空字符串(取消流程审批)
|
||||
}
|
||||
|
||||
@@ -10,17 +10,17 @@ import (
|
||||
type DbInstance struct {
|
||||
model.Model
|
||||
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"` // 类型,mysql oracle等
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port"`
|
||||
Network string `json:"network"`
|
||||
Sid string `json:"sid"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"-"`
|
||||
Params string `json:"params"`
|
||||
Remark string `json:"remark"`
|
||||
SshTunnelMachineId int `json:"sshTunnelMachineId"` // ssh隧道机器id
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"` // 类型,mysql oracle等
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port"`
|
||||
Network string `json:"network"`
|
||||
Sid string `json:"sid"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"-"`
|
||||
Params *string `json:"params"`
|
||||
Remark string `json:"remark"`
|
||||
SshTunnelMachineId int `json:"sshTunnelMachineId"` // ssh隧道机器id
|
||||
}
|
||||
|
||||
func (d *DbInstance) TableName() string {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package entity
|
||||
|
||||
import "mayfly-go/pkg/model"
|
||||
import (
|
||||
"mayfly-go/pkg/model"
|
||||
)
|
||||
|
||||
// 数据库sql执行记录
|
||||
type DbSqlExec struct {
|
||||
@@ -13,6 +15,10 @@ type DbSqlExec struct {
|
||||
Sql string `json:"sql"` // 执行的sql
|
||||
OldValue string `json:"oldValue"`
|
||||
Remark string `json:"remark"`
|
||||
Status int8 `json:"status"` // 执行状态
|
||||
Res string `json:"res"` // 执行结果
|
||||
|
||||
FlowBizKey string `json:"flowBizKey"` // 流程业务key
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -21,4 +27,9 @@ const (
|
||||
DbSqlExecTypeDelete int8 = 2 // 删除类型
|
||||
DbSqlExecTypeInsert int8 = 3 // 插入类型
|
||||
DbSqlExecTypeQuery int8 = 4 // 查询类型,如select、show等
|
||||
|
||||
DbSqlExecStatusWait = 1
|
||||
DbSqlExecStatusSuccess = 2
|
||||
DbSqlExecStatusNo = -1 // 不执行
|
||||
DbSqlExecStatusFail = -2
|
||||
)
|
||||
|
||||
@@ -31,12 +31,14 @@ type DbQuery struct {
|
||||
}
|
||||
|
||||
type DbSqlExecQuery struct {
|
||||
Id uint64 `json:"id" form:"id"`
|
||||
DbId uint64 `json:"dbId" form:"dbId"`
|
||||
Db string `json:"db" form:"db"`
|
||||
Table string `json:"table" form:"table"`
|
||||
Type int8 `json:"type" form:"type"` // 类型
|
||||
Id uint64 `json:"id" form:"id"`
|
||||
DbId uint64 `json:"dbId" form:"dbId"`
|
||||
Db string `json:"db" form:"db"`
|
||||
Table string `json:"table" form:"table"`
|
||||
Type int8 `json:"type" form:"type"` // 类型
|
||||
FlowBizKey string `json:"flowBizKey" form:"flowBizKey"`
|
||||
|
||||
Status []int8
|
||||
CreatorId uint64
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@ func (d *dbSqlExecRepoImpl) GetPageList(condition *entity.DbSqlExecQuery, pagePa
|
||||
Eq("`table`", condition.Table).
|
||||
Eq("type", condition.Type).
|
||||
Eq("creator_id", condition.CreatorId).
|
||||
Eq("flow_biz_key", condition.FlowBizKey).
|
||||
In("status", condition.Status).
|
||||
RLike("db", condition.Db).WithOrderBy(orderBy...)
|
||||
return gormx.PageQuery(qd, pageParam, toEntity)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
func InitDbSqlExecRouter(router *gin.RouterGroup) {
|
||||
db := router.Group("/dbs/:dbId/sql-execs")
|
||||
db := router.Group("/dbs/sql-execs")
|
||||
|
||||
d := new(api.DbSqlExec)
|
||||
biz.ErrIsNil(ioc.Inject(d))
|
||||
|
||||
12
server/internal/flow/api/form/procdef.go
Normal file
12
server/internal/flow/api/form/procdef.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package form
|
||||
|
||||
import "mayfly-go/internal/flow/domain/entity"
|
||||
|
||||
type Procdef struct {
|
||||
Id uint64 `json:"id"`
|
||||
Name string `json:"name" binding:"required"` // 名称
|
||||
DefKey string `json:"defKey" binding:"required"`
|
||||
Tasks string `json:"tasks" binding:"required"` // 审批节点任务信息
|
||||
Status entity.ProcdefStatus `json:"status" binding:"required"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
6
server/internal/flow/api/form/procinst.go
Normal file
6
server/internal/flow/api/form/procinst.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package form
|
||||
|
||||
type ProcinstTaskAudit struct {
|
||||
Id uint64 `json:"id" binding:"required"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
50
server/internal/flow/api/procdef.go
Normal file
50
server/internal/flow/api/procdef.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/flow/api/form"
|
||||
"mayfly-go/internal/flow/application"
|
||||
"mayfly-go/internal/flow/domain/entity"
|
||||
"mayfly-go/pkg/biz"
|
||||
"mayfly-go/pkg/req"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Procdef struct {
|
||||
ProcdefApp application.Procdef `inject:""`
|
||||
}
|
||||
|
||||
func (p *Procdef) GetProcdefPage(rc *req.Ctx) {
|
||||
cond, page := req.BindQueryAndPage(rc, new(entity.Procdef))
|
||||
res, err := p.ProcdefApp.GetPageList(cond, page, new([]entity.Procdef))
|
||||
biz.ErrIsNil(err)
|
||||
rc.ResData = res
|
||||
}
|
||||
|
||||
func (p *Procdef) GetProcdef(rc *req.Ctx) {
|
||||
defkey := rc.PathParam("key")
|
||||
biz.NotEmpty(defkey, "流程定义key不能为空")
|
||||
|
||||
procdef := &entity.Procdef{DefKey: defkey}
|
||||
biz.ErrIsNil(p.ProcdefApp.GetBy(procdef), "该流程定义不存在")
|
||||
rc.ResData = procdef
|
||||
}
|
||||
|
||||
func (a *Procdef) Save(rc *req.Ctx) {
|
||||
form := &form.Procdef{}
|
||||
procdef := req.BindJsonAndCopyTo(rc, form, new(entity.Procdef))
|
||||
rc.ReqParam = form
|
||||
biz.ErrIsNil(a.ProcdefApp.Save(rc.MetaCtx, procdef))
|
||||
}
|
||||
|
||||
func (p *Procdef) Delete(rc *req.Ctx) {
|
||||
idsStr := rc.PathParam("id")
|
||||
rc.ReqParam = idsStr
|
||||
ids := strings.Split(idsStr, ",")
|
||||
|
||||
for _, v := range ids {
|
||||
value, err := strconv.Atoi(v)
|
||||
biz.ErrIsNilAppendErr(err, "string类型转换为int异常: %s")
|
||||
biz.ErrIsNilAppendErr(p.ProcdefApp.DeleteProcdef(rc.MetaCtx, uint64(value)), "删除失败:%s")
|
||||
}
|
||||
}
|
||||
99
server/internal/flow/api/procinst.go
Normal file
99
server/internal/flow/api/procinst.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mayfly-go/internal/common/consts"
|
||||
"mayfly-go/internal/flow/api/form"
|
||||
"mayfly-go/internal/flow/api/vo"
|
||||
"mayfly-go/internal/flow/application"
|
||||
"mayfly-go/internal/flow/domain/entity"
|
||||
"mayfly-go/internal/flow/domain/repository"
|
||||
"mayfly-go/pkg/biz"
|
||||
"mayfly-go/pkg/req"
|
||||
"mayfly-go/pkg/utils/collx"
|
||||
"mayfly-go/pkg/utils/structx"
|
||||
)
|
||||
|
||||
type Procinst struct {
|
||||
ProcinstApp application.Procinst `inject:""`
|
||||
ProcdefApp application.Procdef `inject:""`
|
||||
|
||||
ProcinstTaskRepo repository.ProcinstTask `inject:""`
|
||||
}
|
||||
|
||||
func (p *Procinst) GetProcinstPage(rc *req.Ctx) {
|
||||
cond, page := req.BindQueryAndPage(rc, new(entity.ProcinstQuery))
|
||||
// 非管理员只能获取自己申请的流程
|
||||
if laId := rc.GetLoginAccount().Id; laId != consts.AdminId {
|
||||
cond.CreatorId = laId
|
||||
}
|
||||
|
||||
res, err := p.ProcinstApp.GetPageList(cond, page, new([]entity.Procinst))
|
||||
biz.ErrIsNil(err)
|
||||
rc.ResData = res
|
||||
}
|
||||
|
||||
func (p *Procinst) ProcinstCancel(rc *req.Ctx) {
|
||||
instId := uint64(rc.PathParamInt("id"))
|
||||
rc.ReqParam = instId
|
||||
biz.ErrIsNil(p.ProcinstApp.CancelProc(rc.MetaCtx, instId))
|
||||
}
|
||||
|
||||
func (p *Procinst) GetProcinstDetail(rc *req.Ctx) {
|
||||
pi, err := p.ProcinstApp.GetById(new(entity.Procinst), uint64(rc.PathParamInt("id")))
|
||||
biz.ErrIsNil(err, "流程实例不存在")
|
||||
pivo := new(vo.ProcinstVO)
|
||||
structx.Copy(pivo, pi)
|
||||
|
||||
// 流程定义信息
|
||||
procdef, _ := p.ProcdefApp.GetById(new(entity.Procdef), pi.ProcdefId)
|
||||
pivo.Procdef = procdef
|
||||
|
||||
// 流程实例任务信息
|
||||
instTasks := new([]*entity.ProcinstTask)
|
||||
biz.ErrIsNil(p.ProcinstTaskRepo.ListByCond(&entity.ProcinstTask{ProcinstId: pi.Id}, instTasks))
|
||||
pivo.ProcinstTasks = *instTasks
|
||||
|
||||
rc.ResData = pivo
|
||||
}
|
||||
|
||||
func (p *Procinst) GetTasks(rc *req.Ctx) {
|
||||
instTaskQuery, page := req.BindQueryAndPage(rc, new(entity.ProcinstTaskQuery))
|
||||
if laId := rc.GetLoginAccount().Id; laId != consts.AdminId {
|
||||
// 赋值操作人为当前登录账号
|
||||
instTaskQuery.Assignee = fmt.Sprintf("%d", rc.GetLoginAccount().Id)
|
||||
}
|
||||
|
||||
taskVos := new([]*vo.ProcinstTask)
|
||||
res, err := p.ProcinstApp.GetProcinstTasks(instTaskQuery, page, taskVos)
|
||||
biz.ErrIsNil(err)
|
||||
|
||||
instIds := collx.ArrayMap[*vo.ProcinstTask, uint64](*taskVos, func(val *vo.ProcinstTask) uint64 { return val.ProcinstId })
|
||||
insts := new([]*entity.Procinst)
|
||||
p.ProcinstApp.GetByIdIn(insts, instIds)
|
||||
instId2Inst := collx.ArrayToMap[*entity.Procinst, uint64](*insts, func(val *entity.Procinst) uint64 { return val.Id })
|
||||
|
||||
// 赋值任务对应的流程实例
|
||||
for _, task := range *taskVos {
|
||||
task.Procinst = instId2Inst[task.ProcinstId]
|
||||
}
|
||||
rc.ResData = res
|
||||
}
|
||||
|
||||
func (p *Procinst) CompleteTask(rc *req.Ctx) {
|
||||
auditForm := req.BindJsonAndValid(rc, new(form.ProcinstTaskAudit))
|
||||
rc.ReqParam = auditForm
|
||||
biz.ErrIsNil(p.ProcinstApp.CompleteTask(rc.MetaCtx, auditForm.Id, auditForm.Remark))
|
||||
}
|
||||
|
||||
func (p *Procinst) RejectTask(rc *req.Ctx) {
|
||||
auditForm := req.BindJsonAndValid(rc, new(form.ProcinstTaskAudit))
|
||||
rc.ReqParam = auditForm
|
||||
biz.ErrIsNil(p.ProcinstApp.RejectTask(rc.MetaCtx, auditForm.Id, auditForm.Remark))
|
||||
}
|
||||
|
||||
func (p *Procinst) BackTask(rc *req.Ctx) {
|
||||
auditForm := req.BindJsonAndValid(rc, new(form.ProcinstTaskAudit))
|
||||
rc.ReqParam = auditForm
|
||||
biz.ErrIsNil(p.ProcinstApp.BackTask(rc.MetaCtx, auditForm.Id, auditForm.Remark))
|
||||
}
|
||||
45
server/internal/flow/api/vo/procinst.go
Normal file
45
server/internal/flow/api/vo/procinst.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package vo
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/flow/domain/entity"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ProcinstVO struct {
|
||||
Id uint64 `json:"id"`
|
||||
ProcdefId uint64 `json:"procdefId"` // 流程定义id
|
||||
ProcdefName string `json:"procdefName"` // 流程定义名称
|
||||
|
||||
BizType string `json:"bizType"` // 业务类型
|
||||
BizKey string `json:"bizKey"` // 业务key
|
||||
BizStatus int8 `json:"bizStatus"` // 业务状态
|
||||
BizHandleRes string `json:"bizHandleRes"` // 业务处理结果
|
||||
TaskKey string `json:"taskKey"` // 当前任务key
|
||||
Remark string `json:"remark"`
|
||||
Status int8 `json:"status"`
|
||||
EndTime *time.Time `json:"endTime"`
|
||||
Duration int64 `json:"duration"` // 持续时间(开始到结束)
|
||||
|
||||
Creator string `json:"creator"`
|
||||
CreateTime *time.Time `json:"createTime"`
|
||||
UpdateTime *time.Time `json:"updateTime"`
|
||||
|
||||
Procdef *entity.Procdef `json:"procdef"`
|
||||
ProcinstTasks []*entity.ProcinstTask `json:"procinstTasks"`
|
||||
}
|
||||
|
||||
type ProcinstTask struct {
|
||||
Id uint64 `json:"id"`
|
||||
ProcinstId uint64 `json:"procinstId"` // 流程实例id
|
||||
TaskKey string `json:"taskKey"` // 当前任务key
|
||||
TaskName string `json:"taskName"` // 当前任务名称
|
||||
Assignee string `json:"assignee"` // 分配到该任务的用户
|
||||
|
||||
Status entity.ProcinstTaskStatus `json:"status"` // 状态
|
||||
Remark string `json:"remark"`
|
||||
Duration int64 `json:"duration"` // 持续时间(开始到结束)
|
||||
CreateTime *time.Time `json:"createTime"`
|
||||
EndTime *time.Time `json:"endTime"`
|
||||
|
||||
Procinst *entity.Procinst `json:"procinst"`
|
||||
}
|
||||
13
server/internal/flow/application/application.go
Normal file
13
server/internal/flow/application/application.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/flow/infrastructure/persistence"
|
||||
"mayfly-go/pkg/ioc"
|
||||
)
|
||||
|
||||
func InitIoc() {
|
||||
persistence.Init()
|
||||
|
||||
ioc.Register(new(procdefAppImpl), ioc.WithComponentName("ProcdefApp"))
|
||||
ioc.Register(new(procinstAppImpl), ioc.WithComponentName("ProcinstApp"))
|
||||
}
|
||||
42
server/internal/flow/application/biz_handler.go
Normal file
42
server/internal/flow/application/biz_handler.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"mayfly-go/internal/flow/domain/entity"
|
||||
"mayfly-go/pkg/errorx"
|
||||
"mayfly-go/pkg/logx"
|
||||
)
|
||||
|
||||
// 流程业务处理函数(流程结束后会根据流程业务类型获取该函数进行处理)
|
||||
// @param procinstStatus 流程实例状态
|
||||
// @param bizKey 业务key,可为业务数据对应的主键
|
||||
// type FlowBizHandlerFunc func(ctx context.Context, procinstStatus entity.ProcinstStatus, bizKey string) error
|
||||
|
||||
// 业务流程处理器(流程状态变更后会根据流程业务类型获取对应的处理器进行回调处理)
|
||||
type FlowBizHandler interface {
|
||||
|
||||
// 业务流程处理函数
|
||||
// @param procinstStatus 流程实例状态
|
||||
// @param bizKey 业务key,可为业务数据对应的主键
|
||||
FlowBizHandle(ctx context.Context, procinstStatus entity.ProcinstStatus, bizKey string) error
|
||||
}
|
||||
|
||||
var (
|
||||
handlers map[string]FlowBizHandler = make(map[string]FlowBizHandler, 0)
|
||||
)
|
||||
|
||||
// 注册流程业务处理函数
|
||||
func RegisterBizHandler(flowBizType string, handler FlowBizHandler) {
|
||||
logx.Infof("flow register biz handelr: bizType=%s", flowBizType)
|
||||
handlers[flowBizType] = handler
|
||||
}
|
||||
|
||||
// 流程业务处理
|
||||
func FlowBizHandle(ctx context.Context, flowBizType string, bizKey string, procinstStatus entity.ProcinstStatus) error {
|
||||
if handler, ok := handlers[flowBizType]; !ok {
|
||||
logx.Warnf("flow biz handler not found: bizType=%s", flowBizType)
|
||||
return errorx.NewBiz("业务流程处理器不存在")
|
||||
} else {
|
||||
return handler.FlowBizHandle(ctx, procinstStatus, bizKey)
|
||||
}
|
||||
}
|
||||
13
server/internal/flow/application/param.go
Normal file
13
server/internal/flow/application/param.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package application
|
||||
|
||||
// 启动流程实例请求入参
|
||||
type StarProcParam struct {
|
||||
BizType string // 业务类型
|
||||
BizKey string // 业务key
|
||||
Remark string // 备注
|
||||
}
|
||||
|
||||
type CompleteProcinstTaskParam struct {
|
||||
TaskId uint64
|
||||
Remark string // 备注
|
||||
}
|
||||
75
server/internal/flow/application/procdef.go
Normal file
75
server/internal/flow/application/procdef.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"mayfly-go/internal/flow/domain/entity"
|
||||
"mayfly-go/internal/flow/domain/repository"
|
||||
"mayfly-go/pkg/base"
|
||||
"mayfly-go/pkg/errorx"
|
||||
"mayfly-go/pkg/model"
|
||||
)
|
||||
|
||||
type Procdef interface {
|
||||
base.App[*entity.Procdef]
|
||||
|
||||
GetPageList(condition *entity.Procdef, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
|
||||
|
||||
// 保存流程实例信息
|
||||
Save(ctx context.Context, def *entity.Procdef) error
|
||||
|
||||
// 删除流程实例信息
|
||||
DeleteProcdef(ctx context.Context, defId uint64) error
|
||||
}
|
||||
|
||||
type procdefAppImpl struct {
|
||||
base.AppImpl[*entity.Procdef, repository.Procdef]
|
||||
|
||||
procinstApp Procinst `inject:"ProcinstApp"`
|
||||
}
|
||||
|
||||
// 注入repo
|
||||
func (p *procdefAppImpl) InjectProcdefRepo(procdefRepo repository.Procdef) {
|
||||
p.Repo = procdefRepo
|
||||
}
|
||||
|
||||
func (p *procdefAppImpl) GetPageList(condition *entity.Procdef, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
|
||||
return p.Repo.GetPageList(condition, pageParam, toEntity, orderBy...)
|
||||
}
|
||||
|
||||
func (p *procdefAppImpl) Save(ctx context.Context, def *entity.Procdef) error {
|
||||
if err := entity.ProcdefStatusEnum.Valid(def.Status); err != nil {
|
||||
return err
|
||||
}
|
||||
if def.Id == 0 {
|
||||
if p.GetBy(&entity.Procdef{DefKey: def.DefKey}) == nil {
|
||||
return errorx.NewBiz("该流程实例key已存在")
|
||||
}
|
||||
return p.Insert(ctx, def)
|
||||
}
|
||||
|
||||
// 防止误修改key
|
||||
def.DefKey = ""
|
||||
if err := p.canModify(def.Id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return p.UpdateById(ctx, def)
|
||||
}
|
||||
|
||||
func (p *procdefAppImpl) DeleteProcdef(ctx context.Context, defId uint64) error {
|
||||
if err := p.canModify(defId); err != nil {
|
||||
return err
|
||||
}
|
||||
return p.DeleteById(ctx, defId)
|
||||
}
|
||||
|
||||
// 判断该流程实例是否可以执行修改操作
|
||||
func (p *procdefAppImpl) canModify(prodefId uint64) error {
|
||||
if activeInstCount := p.procinstApp.CountByCond(&entity.Procinst{ProcdefId: prodefId, Status: entity.ProcinstActive}); activeInstCount > 0 {
|
||||
return errorx.NewBiz("存在运行中的流程实例,无法操作")
|
||||
}
|
||||
if suspInstCount := p.procinstApp.CountByCond(&entity.Procinst{ProcdefId: prodefId, Status: entity.ProcinstSuspended}); suspInstCount > 0 {
|
||||
return errorx.NewBiz("存在挂起中的流程实例,无法操作")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
295
server/internal/flow/application/procinst.go
Normal file
295
server/internal/flow/application/procinst.go
Normal file
@@ -0,0 +1,295 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"mayfly-go/internal/flow/domain/entity"
|
||||
"mayfly-go/internal/flow/domain/repository"
|
||||
"mayfly-go/pkg/base"
|
||||
"mayfly-go/pkg/contextx"
|
||||
"mayfly-go/pkg/errorx"
|
||||
"mayfly-go/pkg/model"
|
||||
)
|
||||
|
||||
type Procinst interface {
|
||||
base.App[*entity.Procinst]
|
||||
|
||||
GetPageList(condition *entity.ProcinstQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
|
||||
|
||||
// 获取流程实例审批节点任务
|
||||
GetProcinstTasks(condition *entity.ProcinstTaskQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
|
||||
|
||||
// 根据流程定义key启动一个流程实例
|
||||
StartProc(ctx context.Context, procdefKey string, reqParam *StarProcParam) (*entity.Procinst, error)
|
||||
|
||||
// 取消流程
|
||||
CancelProc(ctx context.Context, procinstId uint64) error
|
||||
|
||||
// 完成任务
|
||||
CompleteTask(ctx context.Context, taskId uint64, remark string) error
|
||||
|
||||
// 拒绝任务
|
||||
RejectTask(ctx context.Context, taskId uint64, remark string) error
|
||||
|
||||
// 驳回任务(允许重新提交)
|
||||
BackTask(ctx context.Context, taskId uint64, remark string) error
|
||||
}
|
||||
|
||||
type procinstAppImpl struct {
|
||||
base.AppImpl[*entity.Procinst, repository.Procinst]
|
||||
|
||||
procinstTaskRepo repository.ProcinstTask `inject:"ProcinstTaskRepo"`
|
||||
procdefApp Procdef `inject:"ProcdefApp"`
|
||||
}
|
||||
|
||||
// 注入repo
|
||||
func (p *procinstAppImpl) InjectProcinstRepo(procinstRepo repository.Procinst) {
|
||||
p.Repo = procinstRepo
|
||||
}
|
||||
|
||||
func (p *procinstAppImpl) GetPageList(condition *entity.ProcinstQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
|
||||
return p.Repo.GetPageList(condition, pageParam, toEntity, orderBy...)
|
||||
}
|
||||
|
||||
func (p *procinstAppImpl) GetProcinstTasks(condition *entity.ProcinstTaskQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
|
||||
return p.procinstTaskRepo.GetPageList(condition, pageParam, toEntity, orderBy...)
|
||||
}
|
||||
|
||||
func (p *procinstAppImpl) StartProc(ctx context.Context, procdefKey string, reqParam *StarProcParam) (*entity.Procinst, error) {
|
||||
procdef := &entity.Procdef{DefKey: procdefKey}
|
||||
if err := p.procdefApp.GetBy(procdef); err != nil {
|
||||
return nil, errorx.NewBiz("流程实例[%s]不存在", procdefKey)
|
||||
}
|
||||
|
||||
if procdef.Status != entity.ProcdefStatusEnable {
|
||||
return nil, errorx.NewBiz("该流程定义非启用状态")
|
||||
}
|
||||
|
||||
procinst := &entity.Procinst{
|
||||
BizType: reqParam.BizType,
|
||||
BizKey: reqParam.BizKey,
|
||||
BizStatus: entity.ProcinstBizStatusWait,
|
||||
ProcdefId: procdef.Id,
|
||||
ProcdefName: procdef.Name,
|
||||
Remark: reqParam.Remark,
|
||||
Status: entity.ProcinstActive,
|
||||
}
|
||||
|
||||
task := p.getNextTask(procdef, "")
|
||||
procinst.TaskKey = task.TaskKey
|
||||
|
||||
if err := p.Save(ctx, procinst); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return procinst, p.createProcinstTask(ctx, procinst, task)
|
||||
}
|
||||
|
||||
func (p *procinstAppImpl) CancelProc(ctx context.Context, procinstId uint64) error {
|
||||
procinst, err := p.GetById(new(entity.Procinst), procinstId)
|
||||
if err != nil {
|
||||
return errorx.NewBiz("流程不存在")
|
||||
}
|
||||
|
||||
la := contextx.GetLoginAccount(ctx)
|
||||
if la == nil {
|
||||
return errorx.NewBiz("未登录")
|
||||
}
|
||||
if procinst.CreatorId != la.Id {
|
||||
return errorx.NewBiz("只能取消自己创建的流程")
|
||||
}
|
||||
procinst.Status = entity.ProcinstCancelled
|
||||
procinst.BizStatus = entity.ProcinstBizStatusNo
|
||||
procinst.SetEnd()
|
||||
|
||||
return p.Tx(ctx, func(ctx context.Context) error {
|
||||
return p.cancelInstTasks(ctx, procinstId, "流程已取消")
|
||||
}, func(ctx context.Context) error {
|
||||
return p.Save(ctx, procinst)
|
||||
}, func(ctx context.Context) error {
|
||||
return p.triggerProcinstStatusChangeEvent(ctx, procinst)
|
||||
})
|
||||
}
|
||||
|
||||
func (p *procinstAppImpl) CompleteTask(ctx context.Context, instTaskId uint64, remark string) error {
|
||||
instTask, err := p.getAndValidInstTask(ctx, instTaskId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 赋值状态和备注
|
||||
instTask.Status = entity.ProcinstTaskStatusPass
|
||||
instTask.Remark = remark
|
||||
instTask.SetEnd()
|
||||
|
||||
procinst := new(entity.Procinst)
|
||||
p.GetById(procinst, instTask.ProcinstId)
|
||||
|
||||
procdef := new(entity.Procdef)
|
||||
p.procdefApp.GetById(procdef, procinst.ProcdefId)
|
||||
|
||||
// 获取下一实例审批任务
|
||||
task := p.getNextTask(procdef, instTask.TaskKey)
|
||||
if task == nil {
|
||||
procinst.Status = entity.ProcinstCompleted
|
||||
procinst.SetEnd()
|
||||
} else {
|
||||
procinst.TaskKey = task.TaskKey
|
||||
}
|
||||
|
||||
return p.Tx(ctx, func(ctx context.Context) error {
|
||||
return p.UpdateById(ctx, procinst)
|
||||
}, func(ctx context.Context) error {
|
||||
return p.procinstTaskRepo.UpdateById(ctx, instTask)
|
||||
}, func(ctx context.Context) error {
|
||||
return p.createProcinstTask(ctx, procinst, task)
|
||||
}, func(ctx context.Context) error {
|
||||
// 下一审批节点任务不存在,说明该流程已结束
|
||||
if task == nil {
|
||||
return p.triggerProcinstStatusChangeEvent(ctx, procinst)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (p *procinstAppImpl) RejectTask(ctx context.Context, instTaskId uint64, remark string) error {
|
||||
instTask, err := p.getAndValidInstTask(ctx, instTaskId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 赋值状态和备注
|
||||
instTask.Status = entity.ProcinstTaskStatusReject
|
||||
instTask.Remark = remark
|
||||
instTask.SetEnd()
|
||||
|
||||
procinst := new(entity.Procinst)
|
||||
p.GetById(procinst, instTask.ProcinstId)
|
||||
// 更新流程实例为终止状态,无法重新提交
|
||||
procinst.Status = entity.ProcinstTerminated
|
||||
procinst.BizStatus = entity.ProcinstBizStatusNo
|
||||
procinst.SetEnd()
|
||||
|
||||
return p.Tx(ctx, func(ctx context.Context) error {
|
||||
return p.UpdateById(ctx, procinst)
|
||||
}, func(ctx context.Context) error {
|
||||
return p.procinstTaskRepo.UpdateById(ctx, instTask)
|
||||
}, func(ctx context.Context) error {
|
||||
return p.triggerProcinstStatusChangeEvent(ctx, procinst)
|
||||
})
|
||||
}
|
||||
|
||||
func (p *procinstAppImpl) BackTask(ctx context.Context, instTaskId uint64, remark string) error {
|
||||
instTask, err := p.getAndValidInstTask(ctx, instTaskId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 赋值状态和备注
|
||||
instTask.Status = entity.ProcinstTaskStatusBack
|
||||
instTask.Remark = remark
|
||||
|
||||
procinst := new(entity.Procinst)
|
||||
p.GetById(procinst, instTask.ProcinstId)
|
||||
|
||||
// 更新流程实例为挂起状态,等待重新提交
|
||||
procinst.Status = entity.ProcinstSuspended
|
||||
|
||||
return p.Tx(ctx, func(ctx context.Context) error {
|
||||
return p.UpdateById(ctx, procinst)
|
||||
}, func(ctx context.Context) error {
|
||||
return p.procinstTaskRepo.UpdateById(ctx, instTask)
|
||||
}, func(ctx context.Context) error {
|
||||
return p.triggerProcinstStatusChangeEvent(ctx, procinst)
|
||||
})
|
||||
}
|
||||
|
||||
// 取消处理中的流程实例任务
|
||||
func (p *procinstAppImpl) cancelInstTasks(ctx context.Context, procinstId uint64, cancelReason string) error {
|
||||
// 流程实例任务信息
|
||||
instTasks := new([]*entity.ProcinstTask)
|
||||
p.procinstTaskRepo.ListByCond(&entity.ProcinstTask{ProcinstId: procinstId, Status: entity.ProcinstTaskStatusProcess}, instTasks)
|
||||
for _, instTask := range *instTasks {
|
||||
instTask.Status = entity.ProcinstTaskStatusCanceled
|
||||
instTask.Remark = cancelReason
|
||||
instTask.SetEnd()
|
||||
p.procinstTaskRepo.UpdateById(ctx, instTask)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 触发流程实例状态改变事件
|
||||
func (p *procinstAppImpl) triggerProcinstStatusChangeEvent(ctx context.Context, procinst *entity.Procinst) error {
|
||||
err := FlowBizHandle(ctx, procinst.BizType, procinst.BizKey, procinst.Status)
|
||||
if err != nil {
|
||||
// 业务处理错误,非完成状态则终止流程
|
||||
if procinst.Status != entity.ProcinstCompleted {
|
||||
procinst.Status = entity.ProcinstTerminated
|
||||
procinst.SetEnd()
|
||||
p.cancelInstTasks(ctx, procinst.Id, "业务处理失败")
|
||||
}
|
||||
procinst.BizStatus = entity.ProcinstBizStatusFail
|
||||
procinst.BizHandleRes = err.Error()
|
||||
return p.UpdateById(ctx, procinst)
|
||||
}
|
||||
|
||||
// 处理成功,并且状态为完成,则更新业务状态为成功
|
||||
if procinst.Status == entity.ProcinstCompleted {
|
||||
procinst.BizStatus = entity.ProcinstBizStatusSuccess
|
||||
procinst.BizHandleRes = "success"
|
||||
return p.UpdateById(ctx, procinst)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// 获取并校验实例任务
|
||||
func (p *procinstAppImpl) getAndValidInstTask(ctx context.Context, instTaskId uint64) (*entity.ProcinstTask, error) {
|
||||
instTask := new(entity.ProcinstTask)
|
||||
if err := p.procinstTaskRepo.GetById(instTask, instTaskId); err != nil {
|
||||
return nil, errorx.NewBiz("流程实例任务不存在")
|
||||
}
|
||||
|
||||
la := contextx.GetLoginAccount(ctx)
|
||||
if instTask.Assignee != fmt.Sprintf("%d", la.Id) {
|
||||
return nil, errorx.NewBiz("当前用户不是任务处理人,无法完成任务")
|
||||
}
|
||||
|
||||
return instTask, nil
|
||||
}
|
||||
|
||||
// 创建流程实例节点任务
|
||||
func (p *procinstAppImpl) createProcinstTask(ctx context.Context, procinst *entity.Procinst, task *entity.ProcdefTask) error {
|
||||
if task == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
procinstTask := &entity.ProcinstTask{
|
||||
ProcinstId: procinst.Id,
|
||||
Status: entity.ProcinstTaskStatusProcess,
|
||||
|
||||
TaskKey: task.TaskKey,
|
||||
TaskName: task.Name,
|
||||
Assignee: task.UserId,
|
||||
}
|
||||
return p.procinstTaskRepo.Insert(ctx, procinstTask)
|
||||
}
|
||||
|
||||
// 获取下一审批节点任务
|
||||
func (p *procinstAppImpl) getNextTask(procdef *entity.Procdef, nowTaskKey string) *entity.ProcdefTask {
|
||||
tasks := procdef.GetTasks()
|
||||
if len(tasks) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if nowTaskKey == "" {
|
||||
// nowTaskKey为空,则说明为刚启动该流程实例
|
||||
return tasks[0]
|
||||
}
|
||||
|
||||
for index, t := range tasks {
|
||||
if (t.TaskKey == nowTaskKey) && (index < len(tasks)-1) {
|
||||
return tasks[index+1]
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
52
server/internal/flow/domain/entity/procdef.go
Normal file
52
server/internal/flow/domain/entity/procdef.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"mayfly-go/pkg/enumx"
|
||||
"mayfly-go/pkg/logx"
|
||||
"mayfly-go/pkg/model"
|
||||
)
|
||||
|
||||
// 流程定义信息
|
||||
type Procdef struct {
|
||||
model.Model
|
||||
|
||||
Name string `json:"name" form:"name"` // 名称
|
||||
DefKey string `json:"defKey" form:"defKey"` //
|
||||
Tasks string `json:"tasks"` // 审批节点任务信息
|
||||
Status ProcdefStatus `json:"status"` // 状态
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
|
||||
func (p *Procdef) TableName() string {
|
||||
return "t_flow_procdef"
|
||||
}
|
||||
|
||||
// 获取审批节点任务列表
|
||||
func (p *Procdef) GetTasks() []*ProcdefTask {
|
||||
var tasks []*ProcdefTask
|
||||
err := json.Unmarshal([]byte(p.Tasks), &tasks)
|
||||
if err != nil {
|
||||
logx.ErrorTrace("解析procdef tasks失败", err)
|
||||
return tasks
|
||||
}
|
||||
|
||||
return tasks
|
||||
}
|
||||
|
||||
type ProcdefTask struct {
|
||||
Name string `json:"name" form:"name"` // 审批节点任务名称
|
||||
TaskKey string `json:"taskKey" form:"taskKey"` // 任务key
|
||||
UserId string `json:"userId"` // 审批人
|
||||
}
|
||||
|
||||
type ProcdefStatus int8
|
||||
|
||||
const (
|
||||
ProcdefStatusEnable ProcdefStatus = 1
|
||||
ProcdefStatusDisable ProcdefStatus = -1
|
||||
)
|
||||
|
||||
var ProcdefStatusEnum = enumx.NewEnum[ProcdefStatus]("流程定义状态").
|
||||
Add(ProcdefStatusEnable, "启用").
|
||||
Add(ProcdefStatusDisable, "禁用")
|
||||
100
server/internal/flow/domain/entity/procinst.go
Normal file
100
server/internal/flow/domain/entity/procinst.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"mayfly-go/pkg/enumx"
|
||||
"mayfly-go/pkg/model"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 流程实例信息 -> 根据流程定义信息启动一个流程实例
|
||||
type Procinst struct {
|
||||
model.Model
|
||||
|
||||
ProcdefId uint64 `json:"procdefId"` // 流程定义id
|
||||
ProcdefName string `json:"procdefName"` // 流程定义名称
|
||||
|
||||
BizType string `json:"bizType"` // 业务类型
|
||||
BizKey string `json:"bizKey"` // 业务key
|
||||
BizStatus ProcinstBizStatus `json:"bizStatus"` // 业务状态
|
||||
BizHandleRes string `json:"bizHandleRes"` // 业务处理结果
|
||||
TaskKey string `json:"taskKey"` // 当前任务key
|
||||
Status ProcinstStatus `json:"status"` // 状态
|
||||
Remark string `json:"remark"`
|
||||
EndTime *time.Time `json:"endTime"`
|
||||
Duration int64 `json:"duration"` // 持续时间(开始到结束)
|
||||
}
|
||||
|
||||
func (a *Procinst) TableName() string {
|
||||
return "t_flow_procinst"
|
||||
}
|
||||
|
||||
// 设置流程终止结束的一些信息
|
||||
func (a *Procinst) SetEnd() {
|
||||
nowTime := time.Now()
|
||||
a.EndTime = &nowTime
|
||||
a.Duration = int64(time.Since(*a.CreateTime).Seconds())
|
||||
}
|
||||
|
||||
type ProcinstStatus int8
|
||||
|
||||
const (
|
||||
ProcinstActive ProcinstStatus = 1 // 流程实例正在执行中,当前有活动任务等待执行或者正在运行的流程节点
|
||||
ProcinstCompleted ProcinstStatus = 2 // 流程实例已经成功执行完成,没有剩余任务或者等待事件
|
||||
ProcinstSuspended ProcinstStatus = -1 // 流程实例被挂起,暂停执行,可能被驳回等待修改重新提交
|
||||
ProcinstTerminated ProcinstStatus = -2 // 流程实例被终止,可能是由于某种原因如被拒绝等导致流程无法正常执行
|
||||
ProcinstCancelled ProcinstStatus = -3 // 流程实例被取消,通常是用户手动操作取消了流程的执行
|
||||
)
|
||||
|
||||
var ProcinstStatusEnum = enumx.NewEnum[ProcinstStatus]("流程状态").
|
||||
Add(ProcinstActive, "执行中").
|
||||
Add(ProcinstCompleted, "完成").
|
||||
Add(ProcinstSuspended, "挂起").
|
||||
Add(ProcinstTerminated, "终止").
|
||||
Add(ProcinstCancelled, "取消")
|
||||
|
||||
type ProcinstBizStatus int8
|
||||
|
||||
const (
|
||||
ProcinstBizStatusWait ProcinstBizStatus = 1 // 待处理
|
||||
ProcinstBizStatusSuccess ProcinstBizStatus = 2 // 成功
|
||||
ProcinstBizStatusNo ProcinstBizStatus = -1 // 不处理
|
||||
ProcinstBizStatusFail ProcinstBizStatus = -2 // 失败
|
||||
)
|
||||
|
||||
//----------流程实例关联任务-----------
|
||||
|
||||
// 流程实例关联的审批节点任务
|
||||
type ProcinstTask struct {
|
||||
model.Model
|
||||
|
||||
ProcinstId uint64 `json:"procinstId"` // 流程实例id
|
||||
TaskKey string `json:"taskKey"` // 当前任务key
|
||||
TaskName string `json:"taskName"` // 当前任务名称
|
||||
Assignee string `json:"assignee"` // 分配到该任务的用户
|
||||
|
||||
Status ProcinstTaskStatus `json:"status"` // 状态
|
||||
Remark string `json:"remark"`
|
||||
EndTime *time.Time `json:"endTime"`
|
||||
Duration int64 `json:"duration"` // 持续时间(开始到结束)
|
||||
}
|
||||
|
||||
func (a *ProcinstTask) TableName() string {
|
||||
return "t_flow_procinst_task"
|
||||
}
|
||||
|
||||
// 设置流程任务终止结束的一些信息
|
||||
func (p *ProcinstTask) SetEnd() {
|
||||
nowTime := time.Now()
|
||||
p.EndTime = &nowTime
|
||||
p.Duration = int64(time.Since(*p.CreateTime).Seconds())
|
||||
}
|
||||
|
||||
type ProcinstTaskStatus int8
|
||||
|
||||
const (
|
||||
ProcinstTaskStatusProcess ProcinstTaskStatus = 1 // 处理中
|
||||
ProcinstTaskStatusPass ProcinstTaskStatus = 2 // 通过
|
||||
ProcinstTaskStatusReject ProcinstTaskStatus = -1 // 拒绝
|
||||
ProcinstTaskStatusBack ProcinstTaskStatus = -2 // 驳回
|
||||
ProcinstTaskStatusCanceled ProcinstTaskStatus = -3 // 取消
|
||||
)
|
||||
20
server/internal/flow/domain/entity/query.go
Normal file
20
server/internal/flow/domain/entity/query.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package entity
|
||||
|
||||
type ProcinstQuery struct {
|
||||
ProcdefId uint64 `json:"procdefId" form:"procdefId"` // 流程定义id
|
||||
ProcdefName string `json:"procdefName"` // 流程定义名称
|
||||
|
||||
BizType string `json:"bizType" form:"bizType"` // 业务类型
|
||||
BizKey string `json:"bizKey"` // 业务key
|
||||
Status ProcinstStatus `json:"status" form:"status"` // 状态
|
||||
|
||||
CreatorId uint64
|
||||
}
|
||||
|
||||
type ProcinstTaskQuery struct {
|
||||
ProcinstId uint64 `json:"procinstId"` // 流程实例id
|
||||
ProcinstName string `json:"procinstName"` // 流程实例名称
|
||||
BizType string `json:"bizType" form:"bizType"`
|
||||
Assignee string `json:"assignee"` // 分配到该任务的用户
|
||||
Status ProcinstTaskStatus `json:"status" form:"status"` // 状态
|
||||
}
|
||||
13
server/internal/flow/domain/repository/procdef.go
Normal file
13
server/internal/flow/domain/repository/procdef.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/flow/domain/entity"
|
||||
"mayfly-go/pkg/base"
|
||||
"mayfly-go/pkg/model"
|
||||
)
|
||||
|
||||
type Procdef interface {
|
||||
base.Repo[*entity.Procdef]
|
||||
|
||||
GetPageList(condition *entity.Procdef, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
|
||||
}
|
||||
19
server/internal/flow/domain/repository/procinst.go
Normal file
19
server/internal/flow/domain/repository/procinst.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/flow/domain/entity"
|
||||
"mayfly-go/pkg/base"
|
||||
"mayfly-go/pkg/model"
|
||||
)
|
||||
|
||||
type Procinst interface {
|
||||
base.Repo[*entity.Procinst]
|
||||
|
||||
GetPageList(condition *entity.ProcinstQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
|
||||
}
|
||||
|
||||
type ProcinstTask interface {
|
||||
base.Repo[*entity.ProcinstTask]
|
||||
|
||||
GetPageList(condition *entity.ProcinstTaskQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package persistence
|
||||
|
||||
import (
|
||||
"mayfly-go/pkg/ioc"
|
||||
)
|
||||
|
||||
func Init() {
|
||||
ioc.Register(newProcdefRepo(), ioc.WithComponentName("ProcdefRepo"))
|
||||
ioc.Register(newProcinstRepo(), ioc.WithComponentName("ProcinstRepo"))
|
||||
ioc.Register(newProcinstTaskRepo(), ioc.WithComponentName("ProcinstTaskRepo"))
|
||||
}
|
||||
24
server/internal/flow/infrastructure/persistence/procdef.go
Normal file
24
server/internal/flow/infrastructure/persistence/procdef.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package persistence
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/flow/domain/entity"
|
||||
"mayfly-go/internal/flow/domain/repository"
|
||||
"mayfly-go/pkg/base"
|
||||
"mayfly-go/pkg/gormx"
|
||||
"mayfly-go/pkg/model"
|
||||
)
|
||||
|
||||
type procdefImpl struct {
|
||||
base.RepoImpl[*entity.Procdef]
|
||||
}
|
||||
|
||||
func newProcdefRepo() repository.Procdef {
|
||||
return &procdefImpl{base.RepoImpl[*entity.Procdef]{M: new(entity.Procdef)}}
|
||||
}
|
||||
|
||||
func (p *procdefImpl) GetPageList(condition *entity.Procdef, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
|
||||
qd := gormx.NewQuery(new(entity.Procdef)).
|
||||
Like("name", condition.Name).
|
||||
Like("def_key", condition.DefKey)
|
||||
return gormx.PageQuery(qd, pageParam, toEntity)
|
||||
}
|
||||
37
server/internal/flow/infrastructure/persistence/procinst.go
Normal file
37
server/internal/flow/infrastructure/persistence/procinst.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package persistence
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/flow/domain/entity"
|
||||
"mayfly-go/internal/flow/domain/repository"
|
||||
"mayfly-go/pkg/base"
|
||||
"mayfly-go/pkg/gormx"
|
||||
"mayfly-go/pkg/model"
|
||||
)
|
||||
|
||||
type procinstImpl struct {
|
||||
base.RepoImpl[*entity.Procinst]
|
||||
}
|
||||
|
||||
func newProcinstRepo() repository.Procinst {
|
||||
return &procinstImpl{base.RepoImpl[*entity.Procinst]{M: new(entity.Procinst)}}
|
||||
}
|
||||
|
||||
func (p *procinstImpl) GetPageList(condition *entity.ProcinstQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
|
||||
qd := gormx.NewQuery(new(entity.Procinst)).WithCondModel(condition)
|
||||
return gormx.PageQuery(qd, pageParam, toEntity)
|
||||
}
|
||||
|
||||
//-----------procinst task--------------
|
||||
|
||||
type procinstTaskImpl struct {
|
||||
base.RepoImpl[*entity.ProcinstTask]
|
||||
}
|
||||
|
||||
func newProcinstTaskRepo() repository.ProcinstTask {
|
||||
return &procinstTaskImpl{base.RepoImpl[*entity.ProcinstTask]{M: new(entity.ProcinstTask)}}
|
||||
}
|
||||
|
||||
func (p *procinstTaskImpl) GetPageList(condition *entity.ProcinstTaskQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
|
||||
qd := gormx.NewQuery(new(entity.ProcinstTask)).WithCondModel(condition)
|
||||
return gormx.PageQuery(qd, pageParam, toEntity)
|
||||
}
|
||||
12
server/internal/flow/init/init.go
Normal file
12
server/internal/flow/init/init.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package init
|
||||
|
||||
import (
|
||||
"mayfly-go/initialize"
|
||||
"mayfly-go/internal/flow/application"
|
||||
"mayfly-go/internal/flow/router"
|
||||
)
|
||||
|
||||
func init() {
|
||||
initialize.AddInitIocFunc(application.InitIoc)
|
||||
initialize.AddInitRouterFunc(router.Init)
|
||||
}
|
||||
30
server/internal/flow/router/procdef.go
Normal file
30
server/internal/flow/router/procdef.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/flow/api"
|
||||
"mayfly-go/pkg/biz"
|
||||
"mayfly-go/pkg/ioc"
|
||||
"mayfly-go/pkg/req"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func InitProcdefouter(router *gin.RouterGroup) {
|
||||
p := new(api.Procdef)
|
||||
biz.ErrIsNil(ioc.Inject(p))
|
||||
|
||||
reqGroup := router.Group("/flow/procdefs")
|
||||
{
|
||||
reqs := [...]*req.Conf{
|
||||
req.NewGet("", p.GetProcdefPage),
|
||||
|
||||
req.NewGet("/:key", p.GetProcdef),
|
||||
|
||||
req.NewPost("", p.Save).Log(req.NewLogSave("流程定义-保存")).RequiredPermissionCode("flow:procdef:save"),
|
||||
|
||||
req.NewDelete(":id", p.Delete).Log(req.NewLogSave("流程定义-删除")).RequiredPermissionCode("flow:procdef:del"),
|
||||
}
|
||||
|
||||
req.BatchSetGroup(reqGroup, reqs[:])
|
||||
}
|
||||
}
|
||||
36
server/internal/flow/router/procinst.go
Normal file
36
server/internal/flow/router/procinst.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/flow/api"
|
||||
"mayfly-go/pkg/biz"
|
||||
"mayfly-go/pkg/ioc"
|
||||
"mayfly-go/pkg/req"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func InitProcinstRouter(router *gin.RouterGroup) {
|
||||
p := new(api.Procinst)
|
||||
biz.ErrIsNil(ioc.Inject(p))
|
||||
|
||||
reqGroup := router.Group("/flow/procinsts")
|
||||
{
|
||||
reqs := [...]*req.Conf{
|
||||
req.NewGet("", p.GetProcinstPage),
|
||||
|
||||
req.NewGet("/:id", p.GetProcinstDetail),
|
||||
|
||||
req.NewPost("/:id/cancel", p.ProcinstCancel).Log(req.NewLogSave("流程-取消")),
|
||||
|
||||
req.NewGet("/tasks", p.GetTasks),
|
||||
|
||||
req.NewPost("/tasks/complete", p.CompleteTask).Log(req.NewLogSave("流程-任务完成")),
|
||||
|
||||
req.NewPost("/tasks/reject", p.RejectTask).Log(req.NewLogSave("流程-任务拒绝")),
|
||||
|
||||
req.NewPost("/tasks/back", p.BackTask).Log(req.NewLogSave("流程-任务驳回")),
|
||||
}
|
||||
|
||||
req.BatchSetGroup(reqGroup, reqs[:])
|
||||
}
|
||||
}
|
||||
8
server/internal/flow/router/router.go
Normal file
8
server/internal/flow/router/router.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package router
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
|
||||
func Init(router *gin.RouterGroup) {
|
||||
InitProcdefouter(router)
|
||||
InitProcinstRouter(router)
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"mayfly-go/internal/machine/config"
|
||||
"mayfly-go/internal/machine/domain/entity"
|
||||
tagapp "mayfly-go/internal/tag/application"
|
||||
tagentity "mayfly-go/internal/tag/domain/entity"
|
||||
"mayfly-go/pkg/biz"
|
||||
"mayfly-go/pkg/errorx"
|
||||
"mayfly-go/pkg/model"
|
||||
@@ -43,14 +44,20 @@ func (m *Machine) Machines(rc *req.Ctx) {
|
||||
}
|
||||
condition.Codes = codes
|
||||
|
||||
res, err := m.MachineApp.GetMachineList(condition, pageParam, new([]*vo.MachineVO))
|
||||
var machinevos []*vo.MachineVO
|
||||
res, err := m.MachineApp.GetMachineList(condition, pageParam, &machinevos)
|
||||
biz.ErrIsNil(err)
|
||||
if res.Total == 0 {
|
||||
rc.ResData = res
|
||||
return
|
||||
}
|
||||
|
||||
for _, mv := range *res.List {
|
||||
// 填充标签信息
|
||||
m.TagApp.FillTagInfo(collx.ArrayMap(machinevos, func(mvo *vo.MachineVO) tagentity.ITagResource {
|
||||
return mvo
|
||||
})...)
|
||||
|
||||
for _, mv := range machinevos {
|
||||
if machineStats, err := m.MachineApp.GetMachineStats(mv.Id); err == nil {
|
||||
mv.Stat = collx.M{
|
||||
"cpuIdle": machineStats.CPU.Idle,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package vo
|
||||
|
||||
import (
|
||||
tagentity "mayfly-go/internal/tag/domain/entity"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -12,6 +13,8 @@ type AuthCertBaseVO struct {
|
||||
}
|
||||
|
||||
type MachineVO struct {
|
||||
tagentity.ResourceTags
|
||||
|
||||
Id uint64 `json:"id"`
|
||||
Code string `json:"code"`
|
||||
Name string `json:"name"`
|
||||
@@ -35,6 +38,10 @@ type MachineVO struct {
|
||||
Stat map[string]any `json:"stat" gorm:"-"`
|
||||
}
|
||||
|
||||
func (m *MachineVO) GetCode() string {
|
||||
return m.Code
|
||||
}
|
||||
|
||||
type MachineScriptVO struct {
|
||||
Id *int64 `json:"id"`
|
||||
Name *string `json:"name"`
|
||||
|
||||
@@ -67,6 +67,9 @@ func checkClientAvailability(interval time.Duration) {
|
||||
continue
|
||||
}
|
||||
cli := v.Value.(*Cli)
|
||||
if cli.Info == nil {
|
||||
continue
|
||||
}
|
||||
if _, _, err := cli.sshClient.Conn.SendRequest("ping", true, nil); err != nil {
|
||||
logx.Errorf("machine[%s] cache client is not available: %s", cli.Info.Name, err.Error())
|
||||
DeleteCli(cli.Info.Id)
|
||||
|
||||
@@ -4,9 +4,11 @@ import (
|
||||
"context"
|
||||
"mayfly-go/internal/common/consts"
|
||||
"mayfly-go/internal/mongo/api/form"
|
||||
"mayfly-go/internal/mongo/api/vo"
|
||||
"mayfly-go/internal/mongo/application"
|
||||
"mayfly-go/internal/mongo/domain/entity"
|
||||
tagapp "mayfly-go/internal/tag/application"
|
||||
tagentity "mayfly-go/internal/tag/domain/entity"
|
||||
"mayfly-go/pkg/biz"
|
||||
"mayfly-go/pkg/model"
|
||||
"mayfly-go/pkg/req"
|
||||
@@ -36,8 +38,15 @@ func (m *Mongo) Mongos(rc *req.Ctx) {
|
||||
}
|
||||
queryCond.Codes = codes
|
||||
|
||||
res, err := m.MongoApp.GetPageList(queryCond, page, new([]entity.Mongo))
|
||||
var mongovos []*vo.Mongo
|
||||
res, err := m.MongoApp.GetPageList(queryCond, page, &mongovos)
|
||||
biz.ErrIsNil(err)
|
||||
|
||||
// 填充标签信息
|
||||
m.TagApp.FillTagInfo(collx.ArrayMap(mongovos, func(mvo *vo.Mongo) tagentity.ITagResource {
|
||||
return mvo
|
||||
})...)
|
||||
|
||||
rc.ResData = res
|
||||
}
|
||||
|
||||
|
||||
20
server/internal/mongo/api/vo/mongo.go
Normal file
20
server/internal/mongo/api/vo/mongo.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package vo
|
||||
|
||||
import (
|
||||
tagentity "mayfly-go/internal/tag/domain/entity"
|
||||
"mayfly-go/pkg/model"
|
||||
)
|
||||
|
||||
type Mongo struct {
|
||||
model.Model
|
||||
tagentity.ResourceTags
|
||||
|
||||
Code string `orm:"column(code)" json:"code"`
|
||||
Name string `orm:"column(name)" json:"name"`
|
||||
Uri string `orm:"column(uri)" json:"uri"`
|
||||
SshTunnelMachineId int `orm:"column(ssh_tunnel_machine_id)" json:"sshTunnelMachineId"` // ssh隧道机器id
|
||||
}
|
||||
|
||||
func (m *Mongo) GetCode() string {
|
||||
return m.Code
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"mayfly-go/internal/redis/domain/entity"
|
||||
"mayfly-go/internal/redis/rdm"
|
||||
tagapp "mayfly-go/internal/tag/application"
|
||||
tagentity "mayfly-go/internal/tag/domain/entity"
|
||||
"mayfly-go/pkg/biz"
|
||||
"mayfly-go/pkg/model"
|
||||
"mayfly-go/pkg/req"
|
||||
@@ -37,8 +38,15 @@ func (r *Redis) RedisList(rc *req.Ctx) {
|
||||
}
|
||||
queryCond.Codes = codes
|
||||
|
||||
res, err := r.RedisApp.GetPageList(queryCond, page, new([]vo.Redis))
|
||||
var redisvos []*vo.Redis
|
||||
res, err := r.RedisApp.GetPageList(queryCond, page, &redisvos)
|
||||
biz.ErrIsNil(err)
|
||||
|
||||
// 填充标签信息
|
||||
r.TagApp.FillTagInfo(collx.ArrayMap(redisvos, func(rvo *vo.Redis) tagentity.ITagResource {
|
||||
return rvo
|
||||
})...)
|
||||
|
||||
rc.ResData = res
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
package vo
|
||||
|
||||
import "time"
|
||||
import (
|
||||
tagentity "mayfly-go/internal/tag/domain/entity"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Redis struct {
|
||||
tagentity.ResourceTags
|
||||
Id *int64 `json:"id"`
|
||||
Code *string `json:"code"`
|
||||
Code string `json:"code"`
|
||||
Name *string `json:"name"`
|
||||
Host *string `json:"host"`
|
||||
Db string `json:"db"`
|
||||
@@ -19,6 +23,10 @@ type Redis struct {
|
||||
ModifierId *int64 `json:"modifierId"`
|
||||
}
|
||||
|
||||
func (r *Redis) GetCode() string {
|
||||
return r.Code
|
||||
}
|
||||
|
||||
type Keys struct {
|
||||
Cursor map[string]uint64 `json:"cursor"`
|
||||
Keys []string `json:"keys"`
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"mayfly-go/pkg/model"
|
||||
"mayfly-go/pkg/req"
|
||||
"mayfly-go/pkg/utils/collx"
|
||||
"mayfly-go/pkg/utils/conv"
|
||||
"mayfly-go/pkg/utils/cryptox"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -119,13 +120,29 @@ func (a *Account) UpdateAccount(rc *req.Ctx) {
|
||||
|
||||
// @router /accounts [get]
|
||||
func (a *Account) Accounts(rc *req.Ctx) {
|
||||
condition := &entity.Account{}
|
||||
condition := &entity.AccountQuery{}
|
||||
condition.Username = rc.Query("username")
|
||||
condition.Name = rc.Query("name")
|
||||
res, err := a.AccountApp.GetPageList(condition, rc.GetPageParam(), new([]vo.AccountManageVO))
|
||||
biz.ErrIsNil(err)
|
||||
rc.ResData = res
|
||||
}
|
||||
|
||||
func (a *Account) SimpleAccounts(rc *req.Ctx) {
|
||||
condition := &entity.AccountQuery{}
|
||||
condition.Username = rc.Query("username")
|
||||
condition.Name = rc.Query("name")
|
||||
idsStr := rc.Query("ids")
|
||||
if idsStr != "" {
|
||||
condition.Ids = collx.ArrayMap[string, uint64](strings.Split(idsStr, ","), func(val string) uint64 {
|
||||
return uint64(conv.Str2Int(val, 0))
|
||||
})
|
||||
}
|
||||
res, err := a.AccountApp.GetPageList(condition, rc.GetPageParam(), new([]vo.SimpleAccountVO))
|
||||
biz.ErrIsNil(err)
|
||||
rc.ResData = res
|
||||
}
|
||||
|
||||
// @router /accounts
|
||||
func (a *Account) SaveAccount(rc *req.Ctx) {
|
||||
form := &form.AccountCreateForm{}
|
||||
|
||||
@@ -15,6 +15,12 @@ type AccountManageVO struct {
|
||||
OtpSecret string `json:"otpSecret"`
|
||||
}
|
||||
|
||||
type SimpleAccountVO struct {
|
||||
Id uint64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
// 账号角色信息
|
||||
type AccountRoleVO struct {
|
||||
RoleId uint64 `json:"roleId"`
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
type Account interface {
|
||||
base.App[*entity.Account]
|
||||
|
||||
GetPageList(condition *entity.Account, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
|
||||
GetPageList(condition *entity.AccountQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
|
||||
|
||||
Create(ctx context.Context, account *entity.Account) error
|
||||
|
||||
@@ -34,7 +34,7 @@ func (a *accountAppImpl) InjectAccountRepo(repo repository.Account) {
|
||||
a.Repo = repo
|
||||
}
|
||||
|
||||
func (a *accountAppImpl) GetPageList(condition *entity.Account, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
|
||||
func (a *accountAppImpl) GetPageList(condition *entity.AccountQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
|
||||
return a.GetRepo().GetPageList(condition, pageParam, toEntity)
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user