91 changed files with 2192 additions and 1040 deletions
@ -0,0 +1,25 @@ |
|||
import { Tools } from 'platform-core'; |
|||
|
|||
const PassOrNotFormater = (value) => { |
|||
if (Tools.isUndefinedOrNull(value)) { |
|||
return ''; |
|||
} |
|||
if (value === 'PASSED') { |
|||
return { |
|||
componentType: 'QIcon', |
|||
attrs: { name: 'bi-check-circle', size: '20px', color: 'green' }, |
|||
}; |
|||
} else if (value === 'UN_PASSED') { |
|||
return { |
|||
componentType: 'QIcon', |
|||
attrs: { name: 'bi-x-circle', size: '20px', color: 'red' }, |
|||
}; |
|||
} else if (value === 'ERROR') { |
|||
return { |
|||
componentType: 'QIcon', |
|||
attrs: { name: 'bi-x-circle', size: '20px', color: 'red' }, |
|||
}; |
|||
} |
|||
}; |
|||
|
|||
export default PassOrNotFormater; |
@ -0,0 +1,150 @@ |
|||
<template> |
|||
<!-- 组件默认带上的官方属性说明 |
|||
allow-focus-outside: 允许对话框外的元素可聚焦(不设置该值会导致luckysheet在窗口中无法编辑) |
|||
no-esc-dismiss: 用户不能按 ESC 键关闭对话框;如果还设置了 'persistent' 属性,则无需设置它 |
|||
no-backdrop-dismiss: 用户不能通过单击对话框外部来关闭对话框;如果还设置了 'persistent' 属性,则无需设置它 |
|||
no-refocus: 当对话框被隐藏时,不要重新聚焦以前有聚焦过的 DOM 元素 |
|||
--> |
|||
<q-dialog |
|||
ref="dialogRef" |
|||
v-model="dialog.show" |
|||
:maximized="dialog.maximized" |
|||
allow-focus-outside |
|||
no-esc-dismiss |
|||
no-backdrop-dismiss |
|||
no-refocus |
|||
v-bind="attrs" |
|||
> |
|||
<q-card |
|||
:style="{ |
|||
width: dialog.maximized ? '100vw' : props.width, |
|||
'max-width': '100vw', |
|||
height: dialog.maximized ? '100vh' : props.height, |
|||
'max-height': '100vh', |
|||
}" |
|||
> |
|||
<div class="w-full h-full"> |
|||
<div> |
|||
<q-card-section style="height: 59px"> |
|||
<div class="flex justify-between"> |
|||
<div class="text-h6">{{ title }}</div> |
|||
<div class="flex justify-end gap-4"> |
|||
<template v-if="buttons && buttons.length > 0"> |
|||
<template v-for="(btn, index) in buttons as any" :key="index"> |
|||
<q-btn v-if="typeof btn === 'object'" :loading="false" :color="'primary'" v-bind="btn" @click="btn.click ? btn.click() : () => {}"> </q-btn> |
|||
</template> |
|||
</template> |
|||
<slot name="buttons"></slot> |
|||
<q-btn v-if="canMaximize" dense flat :icon="!dialog.maximized ? IconEnum.全屏 : IconEnum.退出全屏" @click="maximizeBtnClick"> |
|||
<q-tooltip v-if="!dialog.maximized">全屏</q-tooltip> |
|||
<q-tooltip v-else-if="dialog.maximized">退出全屏</q-tooltip> |
|||
</q-btn> |
|||
<q-btn v-close-popup dense flat :icon="IconEnum.关闭"> |
|||
<q-tooltip>关闭</q-tooltip> |
|||
</q-btn> |
|||
</div> |
|||
</div> |
|||
</q-card-section> |
|||
<q-separator /> |
|||
</div> |
|||
<div ref="dialogContentDivRef" class="dialog_content_div" style="height: calc(100% - 60px)"> |
|||
<q-card-section style="height: 100%; padding: 0px" class="scroll"> |
|||
<slot></slot> |
|||
</q-card-section> |
|||
</div> |
|||
</div> |
|||
</q-card> |
|||
</q-dialog> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import { ref, reactive, useAttrs } from 'vue'; |
|||
import { IconEnum } from '@/platform/enums'; |
|||
|
|||
const attrs = useAttrs(); |
|||
const props = defineProps({ |
|||
title: { type: String, default: '' }, |
|||
width: { type: String, default: '70%' }, |
|||
height: { type: String, default: '70%' }, |
|||
canMaximize: { type: Boolean, default: true }, |
|||
buttons: { type: Array, default: () => [] }, |
|||
}); |
|||
const emit = defineEmits<{ |
|||
( |
|||
e: 'maximized', // 全屏按钮点击事件 |
|||
maximized: boolean, // 第一个参数,true:全屏状态,false:退出全屏 |
|||
): void; |
|||
}>(); |
|||
|
|||
const dialogContentDivRef = ref(); |
|||
const dialog = reactive({ |
|||
show: false, |
|||
maximized: attrs.maximized || false, |
|||
}); |
|||
|
|||
const maximizeBtnClick = () => { |
|||
dialog.maximized = !dialog.maximized; |
|||
emit('maximized', dialog.maximized); |
|||
}; |
|||
|
|||
const show = () => { |
|||
dialog.show = true; |
|||
}; |
|||
const hide = () => { |
|||
dialog.show = false; |
|||
}; |
|||
|
|||
// 获取元素的绝对位置坐标(像对于浏览器视区左上角) |
|||
// const getElementViewPosition = (element) => { |
|||
// //计算x坐标 |
|||
// let actualLeft = element.offsetLeft; |
|||
// let xcurrent = element.offsetParent; |
|||
// while (xcurrent !== null) { |
|||
// actualLeft += xcurrent.offsetLeft + xcurrent.clientLeft; |
|||
// xcurrent = xcurrent.offsetParent; |
|||
// } |
|||
// let elementScrollLeft = document.documentElement.scrollLeft; |
|||
// if (document.compatMode == 'BackCompat') { |
|||
// elementScrollLeft = document.body.scrollLeft; |
|||
// } |
|||
// const left = actualLeft - elementScrollLeft; |
|||
|
|||
// //计算y坐标 |
|||
// let actualTop = element.offsetTop; |
|||
// let ycurrent = element.offsetParent; |
|||
// while (ycurrent !== null) { |
|||
// actualTop += ycurrent.offsetTop + ycurrent.clientTop; |
|||
// ycurrent = ycurrent.offsetParent; |
|||
// } |
|||
// let elementScrollTop = document.documentElement.scrollTop; |
|||
// if (document.compatMode == 'BackCompat') { |
|||
// elementScrollTop = document.body.scrollTop; |
|||
// } |
|||
// var right = actualTop - elementScrollTop; |
|||
// //返回结果 |
|||
// return { x: left, y: right }; |
|||
// }; |
|||
|
|||
// const getContentHeight = () => { |
|||
// if (dialogContentDivRef?.value) { |
|||
// console.info('dialogContentDivRef.value', dialogContentDivRef.value.offsetHeight); |
|||
// return { |
|||
// height: dialogContentDivRef.value.offsetHeight, |
|||
// y: getElementViewPosition(dialogContentDivRef.value).y, |
|||
// }; |
|||
// } |
|||
// return null; |
|||
// }; |
|||
const getContent = () => { |
|||
if (dialogContentDivRef?.value) { |
|||
return dialogContentDivRef.value; |
|||
} |
|||
return null; |
|||
}; |
|||
|
|||
defineExpose({ |
|||
show, |
|||
hide, |
|||
getContent, |
|||
}); |
|||
</script> |
@ -0,0 +1,11 @@ |
|||
<template> |
|||
<div style="height:100%"> |
|||
|
|||
</div> |
|||
</template> |
|||
<script setup lang="ts"> |
|||
const props = defineProps({ |
|||
|
|||
}); |
|||
|
|||
</script> |
@ -0,0 +1,193 @@ |
|||
<template> |
|||
<w-list-grid |
|||
ref="listGridRef" |
|||
:title="$t('org.wsp.framework.flowable.task.grid.title')" |
|||
:data-url="$fc.apiContextPath + '/system/process/query/task/isc'" |
|||
:form-fields="listGridFields.query" |
|||
:form-field-counter="{ xxs:1, xs:1, sm:2, md:2, lg:2, xl:2, xxl:2 }" |
|||
:table-columns="listGridFields.grid" |
|||
:detail-fields="listGridFields.detail" |
|||
:can-pageable="true" |
|||
:tool-bar-actions="listGridToolBarActions" |
|||
:table-scroll="{x:'100%',y:$fc.ui.contentHeight-$fc.ui.toolBarHeight-$fc.ui.tableHeaderHeight-$fc.ui.paginationHeight-40}" |
|||
@selected-rows-change="selectedRowsChange" |
|||
> |
|||
</w-list-grid> |
|||
<CompleteTaskDialog ref="completeTaskDialogRef" @after-completed="afterCompleted"></CompleteTaskDialog> |
|||
</template> |
|||
<script setup> |
|||
import { |
|||
computed, ref, onMounted, createVNode, toRaw |
|||
} from 'vue'; |
|||
import { useRouter, useRoute } from 'vue-router'; |
|||
import { useI18n } from 'vue-i18n'; |
|||
import { Modal, notification } from 'ant-design-vue'; |
|||
import { QuestionCircleOutlined } from '@ant-design/icons-vue'; |
|||
import { |
|||
useFrameworkConfiguration, useAxios, |
|||
useEnumOptions, getOptionText, useTrueFalseOptions, |
|||
useYesNoOptions, useDataComeFromOptions, useRoleOptions, |
|||
useOrgOptions, useTableColumn |
|||
} from 'framework-core'; |
|||
|
|||
import CompleteTaskDialog from './CompleteTaskDialog'; |
|||
|
|||
/** |
|||
* 定义组件支持的自定义事件 |
|||
*/ |
|||
const emit = defineEmits([ |
|||
'selectedRowsChange', // 选择的行发生改变 |
|||
]); |
|||
|
|||
const { t } = useI18n(); |
|||
const axios = useAxios(); |
|||
const fc = useFrameworkConfiguration(); |
|||
const router = useRouter(); |
|||
const route = useRoute(); |
|||
|
|||
const listGridRef = ref(null); |
|||
const completeTaskDialogRef = ref(null); |
|||
|
|||
const listGridFields = computed(() => { |
|||
return { |
|||
query: [ |
|||
{ component:'input', name:'processInstanceId', label:t('org.wsp.framework.flowable.task.entity.processInstanceId') }, |
|||
], |
|||
grid: [ |
|||
useTableColumn({ width:150, name:'name', title:t('org.wsp.framework.flowable.task.entity.name') }), |
|||
useTableColumn({ width:80, name:'owner', title:t('org.wsp.framework.flowable.task.entity.owner') }), |
|||
useTableColumn({ width:100, name:'assignee', title:t('org.wsp.framework.flowable.task.entity.assignee') }), |
|||
useTableColumn({ width:150, name:'createTime', title:t('org.wsp.framework.flowable.task.entity.createTime') }), |
|||
useTableColumn({ width:150, name:'claimTime', title:t('org.wsp.framework.flowable.task.entity.claimTime') }), |
|||
useTableColumn({ width:150, name:'processInstanceId', title:t('org.wsp.framework.flowable.task.entity.processInstanceId') }), |
|||
] |
|||
}; |
|||
}); |
|||
|
|||
const listGridToolBarActions = computed(() => { |
|||
return ['*query', 'reset', 'refresh', 'divider', |
|||
{ |
|||
icon: 'PartitionOutlined', |
|||
title: t('org.wsp.framework.flowable.action.showWorkflowDiagram'), |
|||
enableIf: (selectedRecords) => { |
|||
const record = listGridRef?.value?.getSelectedRow(); |
|||
return selectedRecords.length > 0; |
|||
}, |
|||
click: () => { |
|||
console.log('1'); |
|||
}, |
|||
}, |
|||
'divider', |
|||
{ |
|||
icon: 'RightCircleOutlined', |
|||
title: t('org.wsp.framework.flowable.action.complete.task'), |
|||
enableIf: (selectedRecords) => { |
|||
const record = listGridRef?.value?.getSelectedRow(); |
|||
return selectedRecords.length > 0; |
|||
}, |
|||
click: () => { |
|||
const record = listGridRef?.value?.getSelectedRow(); |
|||
completeTaskDialogRef?.value?.open(record.id); |
|||
}, |
|||
}, |
|||
{ |
|||
icon: 'DownCircleOutlined', |
|||
title: t('org.wsp.framework.flowable.action.claim.task'), |
|||
enableIf: (selectedRecords) => { |
|||
const record = listGridRef?.value?.getSelectedRow(); |
|||
return selectedRecords.length > 0 && !record.assignee; |
|||
}, |
|||
click: () => { |
|||
const record = listGridRef?.value?.getSelectedRow(); |
|||
axios.post( |
|||
fc.apiContextPath + '/system/process/operation/claim/' + record.id, |
|||
null |
|||
).then(() => { |
|||
notification.success({ |
|||
message: t('operationSuccess'), |
|||
duration:2 |
|||
}); |
|||
listGridRef?.value.refresh(); |
|||
}); |
|||
}, |
|||
}, |
|||
{ |
|||
icon: 'UpCircleOutlined', |
|||
title: t('org.wsp.framework.flowable.action.unclaim.task'), |
|||
enableIf: (selectedRecords) => { |
|||
const record = listGridRef?.value?.getSelectedRow(); |
|||
return selectedRecords.length > 0 && record.assignee; |
|||
}, |
|||
click: () => { |
|||
const record = listGridRef?.value?.getSelectedRow(); |
|||
axios.post( |
|||
fc.apiContextPath + '/system/process/operation/unClaim/' + record.id, |
|||
null |
|||
).then(() => { |
|||
notification.success({ |
|||
message: t('operationSuccess'), |
|||
duration:2 |
|||
}); |
|||
listGridRef?.value.refresh(); |
|||
}); |
|||
}, |
|||
}, |
|||
{ |
|||
icon: 'RollbackOutlined', |
|||
title: t('org.wsp.framework.flowable.action.jump.task'), |
|||
enableIf: (selectedRecords) => { |
|||
const record = listGridRef?.value?.getSelectedRow(); |
|||
return selectedRecords.length > 0; |
|||
}, |
|||
click: () => { |
|||
console.log('2'); |
|||
}, |
|||
}, |
|||
{ |
|||
icon: 'CloseCircleOutlined', |
|||
title: t('org.wsp.framework.flowable.action.terminate.processInstance'), |
|||
enableIf: (selectedRecords) => { |
|||
const record = listGridRef?.value?.getSelectedRow(); |
|||
return selectedRecords.length > 0; |
|||
}, |
|||
click: () => { |
|||
const record = listGridRef?.value?.getSelectedRow(); |
|||
axios.post( |
|||
fc.apiContextPath + '/system/process/operation/terminateProcessInstance/' + record.id, |
|||
null |
|||
).then(() => { |
|||
notification.success({ |
|||
message: t('operationSuccess'), |
|||
duration:2 |
|||
}); |
|||
listGridRef?.value.refresh(); |
|||
}); |
|||
}, |
|||
} |
|||
]; |
|||
}); |
|||
|
|||
const selectedRowsChange = (selectedRecords) => { |
|||
if (selectedRecords && selectedRecords.length > 0) { |
|||
emit('selectedRowsChange', selectedRecords[0]); |
|||
} else { |
|||
emit('selectedRowsChange', null); |
|||
} |
|||
}; |
|||
|
|||
const refresh = () => { |
|||
listGridRef.value.refresh(); |
|||
}; |
|||
|
|||
const afterCompleted = () => { |
|||
notification.success({ |
|||
message: t('operationSuccess'), |
|||
duration:2 |
|||
}); |
|||
listGridRef?.value.refresh(); |
|||
}; |
|||
|
|||
defineExpose({ |
|||
refresh, |
|||
}); |
|||
</script> |
@ -0,0 +1,67 @@ |
|||
<template> |
|||
<a-modal |
|||
v-model:visible="visible" |
|||
:force-render="true" |
|||
:mask-closable="false" |
|||
:title="$t('org.wsp.framework.flowable.action.complete.task')" |
|||
> |
|||
<a-form |
|||
:model="formModelRef" |
|||
layout="horizontal" |
|||
:label-col="{ span: 6 }" |
|||
:wrapper-col="{ span: 18 }" |
|||
autocomplete="off" |
|||
> |
|||
<a-form-item name="variables" :label="$t('org.wsp.framework.flowable.instance.form.variables')"> |
|||
<a-textarea v-model:value="formModelRef.variables" :rows="6" /> |
|||
</a-form-item> |
|||
<a-form-item name="transientVariables" :label="$t('org.wsp.framework.flowable.instance.form.transientVariables')"> |
|||
<a-textarea v-model:value="formModelRef.transientVariables" :rows="6" /> |
|||
</a-form-item> |
|||
</a-form> |
|||
<template #footer> |
|||
<WorkflowAction |
|||
ref="workflowActionRef" |
|||
action-url="/system/process/operation/complete/" |
|||
@after-submit="afterSubmit" |
|||
> |
|||
</WorkflowAction> |
|||
</template> |
|||
</a-modal> |
|||
</template> |
|||
<script setup> |
|||
import { ref, reactive, onMounted } from 'vue'; |
|||
import { useI18n } from 'vue-i18n'; |
|||
import WorkflowAction from './WorkflowAction'; |
|||
|
|||
/** |
|||
* 定义组件支持的自定义事件 |
|||
*/ |
|||
const emit = defineEmits([ |
|||
'afterCompleted', // 提交成功后 |
|||
]); |
|||
|
|||
const { t } = useI18n(); |
|||
|
|||
const visible = ref(false); |
|||
const formModelRef = reactive({}); |
|||
const workflowActionRef = ref([]); |
|||
|
|||
const open = (taskId) => { |
|||
const transientVariables = {}; |
|||
transientVariables.task_treatment = '此处填写处理意见'; |
|||
formModelRef.transientVariables = JSON.stringify(transientVariables, null, 2); |
|||
workflowActionRef.value.setTaskId(taskId); |
|||
workflowActionRef.value.setDataRef(formModelRef); |
|||
visible.value = true; |
|||
}; |
|||
|
|||
const afterSubmit = (taskId) => { |
|||
visible.value = false; |
|||
emit('afterCompleted'); |
|||
}; |
|||
|
|||
defineExpose({ |
|||
open |
|||
}); |
|||
</script> |
@ -0,0 +1,69 @@ |
|||
<template> |
|||
<a-modal |
|||
v-model:visible="visible" |
|||
:force-render="true" |
|||
:mask-closable="false" |
|||
:title="$t('org.wsp.framework.flowable.window.selectAssignee.title')" |
|||
centered |
|||
@ok="okHandle" |
|||
> |
|||
<a-space direction="vertical"> |
|||
<div>{{ t('org.wsp.framework.flowable.task.tip.selectAssignee') }}</div> |
|||
<a-form |
|||
:model="formModelRef" |
|||
layout="horizontal" |
|||
:label-col="{ span: 6 }" |
|||
:wrapper-col="{ span: 18 }" |
|||
autocomplete="off" |
|||
> |
|||
<a-form-item name="assignee" :label="$t('org.wsp.framework.flowable.task.entity.assignee')"> |
|||
<a-select v-model:value="formModelRef.assignee" :options="assigneeOptionsRef" /> |
|||
</a-form-item> |
|||
</a-form> |
|||
</a-space> |
|||
</a-modal> |
|||
</template> |
|||
<script setup> |
|||
import { |
|||
ref, reactive, toRaw, computed, onMounted |
|||
} from 'vue'; |
|||
import { useI18n } from 'vue-i18n'; |
|||
import { notification } from 'ant-design-vue'; |
|||
import { useFrameworkConfiguration, useAxios } from 'framework-core'; |
|||
|
|||
/** |
|||
* 定义组件支持的自定义事件 |
|||
*/ |
|||
const emit = defineEmits([ |
|||
'assigneeSelected', // 选择了候选人 |
|||
]); |
|||
|
|||
const fc = useFrameworkConfiguration(); |
|||
const axios = useAxios(); |
|||
const { t } = useI18n(); |
|||
|
|||
const visible = ref(false); |
|||
const formModelRef = reactive({}); |
|||
const assigneeOptionsRef = ref(null); |
|||
|
|||
const okHandle = () => { |
|||
emit('assigneeSelected', formModelRef.assignee); |
|||
visible.value = false; |
|||
}; |
|||
|
|||
const open = (assignees) => { |
|||
visible.value = true; |
|||
formModelRef.assignee = null; |
|||
const result = []; |
|||
if (assignees) { |
|||
for (let i = 0; i < assignees.length; i++) { |
|||
result.push({ value : assignees[i].loginName, label : assignees[i].loginName + '/' + assignees[i].userName, text : assignees[i].loginName + '/' + assignees[i].userName }); |
|||
} |
|||
} |
|||
assigneeOptionsRef.value = result; |
|||
}; |
|||
|
|||
defineExpose({ |
|||
open |
|||
}); |
|||
</script> |
@ -0,0 +1,96 @@ |
|||
<template> |
|||
<div class="flex justify-end gap-4"> |
|||
<q-btn v-for="action in actionsRef" :key="action.name" :label="action.title" @click="buttonClick(action)"></q-btn> |
|||
</div> |
|||
</template> |
|||
<script setup lang="ts"> |
|||
import { ref } from 'vue'; |
|||
import { useI18n } from 'vue-i18n'; |
|||
import { Environment, axios } from '@/platform'; |
|||
|
|||
/** |
|||
* 定义组件支持的自定义属性 |
|||
*/ |
|||
const props = defineProps({ |
|||
// 获取回退按钮列表的 url,由系统自动提供,使用者无需指定 |
|||
gobackActionUrl: { type: String, default: '/api/flowable/process/operation/getGobacks' }, |
|||
// 操作对应的服务器端控制器 url |
|||
actionUrl: { type: String, default: undefined }, |
|||
// 任务ID |
|||
taskId: { type: String, default: undefined }, |
|||
// 临时变量 |
|||
transientVariables: { type: Object, default: undefined }, |
|||
// 回退按钮默认宽度 |
|||
actionButtonWidth: { type: Number, default: 100 }, |
|||
// 默认按钮 |
|||
defaultActionButtons: { type: [Array, Object], default: undefined }, |
|||
// 默认按钮对齐方式 |
|||
defaultActionButtonsAlign: { type: String, default: 'right' }, |
|||
}); |
|||
|
|||
/** |
|||
* 定义组件支持的自定义事件 |
|||
*/ |
|||
const emit = defineEmits([ |
|||
'afterSubmit', // 提交成功后 |
|||
]); |
|||
|
|||
const { t } = useI18n(); |
|||
const taskIdRef = ref(props.taskId); |
|||
const actionsRef = ref([]); |
|||
|
|||
const buildMembers = (gobacks) => { |
|||
actionsRef.value.splice(0, actionsRef.value.length); |
|||
|
|||
const members = []; |
|||
// 添加默认按钮(左边) |
|||
if (props.defaultActionButtonsAlign === 'left') { |
|||
if (props.defaultActionButtons && props.defaultActionButtons && props.defaultActionButtons.length > 0) { |
|||
for (let i = 0; i < props.defaultActionButtons.length; i++) { |
|||
members.push(props.defaultActionButtons[i]); |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 回退按钮 |
|||
if (gobacks) { |
|||
for (let i = 0; i < gobacks.length; i++) { |
|||
const goback = gobacks[i]; |
|||
const transientVariables = {}; |
|||
transientVariables[goback.variableName] = goback.variableValue; |
|||
members.push({ |
|||
title: goback.title || t('goback'), |
|||
transientVariables, |
|||
}); |
|||
} |
|||
} |
|||
|
|||
// 提交按钮 |
|||
members.push({ title: t('submit') }); |
|||
|
|||
// 添加默认按钮(右边) |
|||
if (props.defaultActionButtonsAlign === 'right') { |
|||
if (props.defaultActionButtons && props.defaultActionButtons && props.defaultActionButtons.length > 0) { |
|||
for (let i = 0; i < props.defaultActionButtons.length; i++) { |
|||
members.push(props.defaultActionButtons[i]); |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 更新到响应式变量中 |
|||
for (let i = 0; i < members.length; i++) { |
|||
actionsRef.value.push(members[i]); |
|||
} |
|||
}; |
|||
|
|||
const setTaskId = (taskId) => { |
|||
taskIdRef.value = taskId; |
|||
axios.get(Environment.apiContextPath(props.gobackActionUrl + '/' + taskId)).then((data) => { |
|||
buildMembers(data.data); |
|||
}); |
|||
}; |
|||
|
|||
defineExpose({ |
|||
setTaskId, |
|||
}); |
|||
</script> |
@ -0,0 +1,158 @@ |
|||
<template> |
|||
<div> |
|||
<a-button v-for="action in actionsRef" @click="buttonClick(action)">{{ action.title }}</a-button> |
|||
</div> |
|||
<SelectAssigneeDialog ref="selectAssigneeDialogRef" @assignee-selected="assigneeSelected"></SelectAssigneeDialog> |
|||
</template> |
|||
<script setup> |
|||
import { |
|||
ref, reactive, toRaw, computed, onMounted |
|||
} from 'vue'; |
|||
import { useI18n } from 'vue-i18n'; |
|||
import { useStore } from 'vuex'; |
|||
import { notification } from 'ant-design-vue'; |
|||
import { useFrameworkConfiguration, useAxios } from 'framework-core'; |
|||
import SelectAssigneeDialog from './SelectAssigneeDialog'; |
|||
|
|||
/** |
|||
* 定义组件支持的自定义属性 |
|||
*/ |
|||
const props = defineProps({ |
|||
// 获取回退按钮列表的 url,由系统自动提供,使用者无需指定 |
|||
gobackActionUrl : { type: String, default: '/system/process/operation/getGobacks/' }, |
|||
// 操作对应的服务器端控制器 url |
|||
actionUrl : { type: String, default: undefined }, |
|||
// 任务ID |
|||
taskId : { type: String, default: undefined }, |
|||
// 临时变量 |
|||
transientVariables : { type: Object, default: undefined }, |
|||
// 回退按钮默认宽度 |
|||
actionButtonWidth : { type: Number, default: 100 }, |
|||
// 默认按钮 |
|||
defaultActionButtons : { type: [Array, Object], default: undefined }, |
|||
// 默认按钮对齐方式 |
|||
defaultActionButtonsAlign : { type: String, default: 'right' }, |
|||
}); |
|||
|
|||
/** |
|||
* 定义组件支持的自定义事件 |
|||
*/ |
|||
const emit = defineEmits([ |
|||
'afterSubmit', // 提交成功后 |
|||
]); |
|||
|
|||
const fc = useFrameworkConfiguration(); |
|||
const store = useStore(); |
|||
const axios = useAxios(); |
|||
const { t } = useI18n(); |
|||
|
|||
const actionIdRef = ref(null); |
|||
const selectAssigneeDialogRef = ref(null); |
|||
|
|||
let dataRef = null; |
|||
let currentAction = null; |
|||
const actionsRef = reactive([]); |
|||
|
|||
const buildMembers = (gobacks) => { |
|||
actionsRef.splice(0, actionsRef.length); |
|||
|
|||
const members = []; |
|||
// 添加默认按钮(左边) |
|||
if (props.defaultActionButtonsAlign === 'left') { |
|||
if (props.defaultActionButtons && props.defaultActionButtons && props.defaultActionButtons.length > 0) { |
|||
for (let i = 0; i < props.defaultActionButtons.length; i++) { |
|||
members.push(props.defaultActionButtons[i]); |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 回退按钮 |
|||
if (gobacks) { |
|||
for (let i = 0; i < gobacks.length; i++) { |
|||
const goback = gobacks[i]; |
|||
const transientVariables = {}; |
|||
transientVariables[goback.variableName] = goback.variableValue; |
|||
members.push({ |
|||
title : goback.title || t('goback'), |
|||
transientVariables, |
|||
}); |
|||
} |
|||
} |
|||
|
|||
// 提交按钮 |
|||
members.push({ title: t('submit') }); |
|||
|
|||
// 添加默认按钮(右边) |
|||
if (props.defaultActionButtonsAlign === 'right') { |
|||
if (props.defaultActionButtons && props.defaultActionButtons && props.defaultActionButtons.length > 0) { |
|||
for (let i = 0; i < props.defaultActionButtons.length; i++) { |
|||
members.push(props.defaultActionButtons[i]); |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 更新到响应式变量中 |
|||
for (let i = 0; i < members.length; i++) { |
|||
actionsRef.push(members[i]); |
|||
} |
|||
}; |
|||
|
|||
const setTaskId = (taskId) => { |
|||
actionIdRef.value = taskId; |
|||
axios.get( |
|||
fc.apiContextPath + props.gobackActionUrl + taskId, |
|||
).then((data) => { |
|||
buildMembers(data.data); |
|||
}); |
|||
}; |
|||
|
|||
const buttonClick = (action) => { |
|||
currentAction = action; |
|||
// 首先清除临时变量,为第二次提交初始化数据 |
|||
const transientVariables = {}; |
|||
|
|||
// 如果是点击了“回退”按钮,将回退控制变量付给临时变量 |
|||
axios.post( |
|||
fc.apiContextPath + props.actionUrl + actionIdRef.value, |
|||
action |
|||
).then((data) => { |
|||
const rawData = data.data; |
|||
if (rawData.code === 0) { // 操作成功 |
|||
emit('afterSubmit'); |
|||
} else if (rawData.code === 1) { // 需要选择处理人 |
|||
selectAssigneeDialogRef.value.open(rawData.assignees); |
|||
} |
|||
}); |
|||
}; |
|||
|
|||
const assigneeSelected = (assignee) => { |
|||
const data = currentAction; |
|||
if (dataRef.variables) { |
|||
data.variables = JSON.parse(dataRef.variables); |
|||
} |
|||
if (dataRef.transientVariables) { |
|||
data.transientVariables = JSON.parse(dataRef.transientVariables); |
|||
data.transientVariables.assignee = assignee; |
|||
} |
|||
axios.post( |
|||
fc.apiContextPath + props.actionUrl + actionIdRef.value, |
|||
data |
|||
).then((response) => { |
|||
const rawData = response.data; |
|||
if (rawData.code === 0) { // 操作成功 |
|||
emit('afterSubmit'); |
|||
} |
|||
}); |
|||
}; |
|||
|
|||
const setDataRef = (_dataRef) => { |
|||
if (_dataRef) { |
|||
dataRef = _dataRef; |
|||
} |
|||
}; |
|||
|
|||
defineExpose({ |
|||
setTaskId, |
|||
setDataRef |
|||
}); |
|||
</script> |
@ -0,0 +1,25 @@ |
|||
import { Tools } from '@/platform'; |
|||
|
|||
const PassOrNotFormater = (value) => { |
|||
if (Tools.isUndefinedOrNull(value)) { |
|||
return ''; |
|||
} |
|||
if (value === 'PASSED') { |
|||
return { |
|||
componentType: 'QIcon', |
|||
attrs: { name: 'bi-check-circle', size: '20px', color: 'green' }, |
|||
}; |
|||
} else if (value === 'UN_PASSED') { |
|||
return { |
|||
componentType: 'QIcon', |
|||
attrs: { name: 'bi-x-circle', size: '20px', color: 'red' }, |
|||
}; |
|||
} else if (value === 'ERROR') { |
|||
return { |
|||
componentType: 'QIcon', |
|||
attrs: { name: 'bi-x-circle', size: '20px', color: 'red' }, |
|||
}; |
|||
} |
|||
}; |
|||
|
|||
export default PassOrNotFormater; |
@ -1,258 +1,85 @@ |
|||
<template> |
|||
<div class="q-pa-md"> |
|||
<q-table flat bordered separator="cell" title="Treats" :rows="rows2" :columns="columns" row-key="name" hide-no-data> |
|||
<template v-if="rows && rows.length > 0" #header="props"> |
|||
<q-tr :props="props"> |
|||
<q-th v-for="col in props.cols" :key="col.name" :props="props" class="text-italic text-purple"> |
|||
{{ col.label }} |
|||
</q-th> |
|||
</q-tr> |
|||
</template> |
|||
<template v-else #header="props"> |
|||
<q-tr :props="props"> |
|||
<q-th v-for="col in props.cols" :key="col.name" :props="props" class="text-italic text-purple"> |
|||
{{ col.label }} |
|||
</q-th> |
|||
</q-tr> |
|||
<q-tr :props="props" style="height: 300px"> </q-tr> |
|||
</template> |
|||
|
|||
<template #body="props"> |
|||
<q-tr :props="props"> |
|||
<q-td key="name" :props="props"> |
|||
{{ props.row.name }} |
|||
</q-td> |
|||
<q-td key="calories" :props="props"> |
|||
<q-badge color="green"> |
|||
{{ props.row.calories }} |
|||
</q-badge> |
|||
</q-td> |
|||
<q-td key="fat" :props="props"> |
|||
<q-badge color="purple"> |
|||
{{ props.row.fat }} |
|||
</q-badge> |
|||
</q-td> |
|||
<q-td key="carbs" :props="props"> |
|||
<q-badge color="orange"> |
|||
{{ props.row.carbs }} |
|||
</q-badge> |
|||
</q-td> |
|||
<q-td key="protein" :props="props"> |
|||
<q-badge color="primary"> |
|||
{{ props.row.protein }} |
|||
</q-badge> |
|||
</q-td> |
|||
<q-td key="sodium" :props="props"> |
|||
<q-badge color="teal"> |
|||
{{ props.row.sodium }} |
|||
</q-badge> |
|||
</q-td> |
|||
<q-td key="calcium" :props="props"> |
|||
<q-badge color="accent"> |
|||
{{ props.row.calcium }} |
|||
</q-badge> |
|||
</q-td> |
|||
<q-td key="iron" :props="props"> |
|||
<q-badge color="amber"> |
|||
{{ props.row.iron }} |
|||
</q-badge> |
|||
</q-td> |
|||
</q-tr> |
|||
</template> |
|||
</q-table> |
|||
</div> |
|||
<w-dialog v-model="isShow"> |
|||
<w-grid |
|||
ref="userGridRef" |
|||
:title="$t('system.user.grid.title')" |
|||
:config-button="true" |
|||
selection="multiple" |
|||
:checkbox-selection="true" |
|||
:data-url="Environment.apiContextPath('/api/system/user')" |
|||
:pagination="{ |
|||
sortBy: 'loginName', |
|||
descending: false, |
|||
}" |
|||
:query-form-cols-num="3" |
|||
:query-form-fields="[ |
|||
{ name: 'loginName', label: $t('loginName'), type: 'text' }, |
|||
{ name: 'userName', label: $t('userName'), type: 'text' }, |
|||
{ name: 'enable', label: $t('isEnable'), type: 'select' }, |
|||
]" |
|||
:toolbar-configure="{ noIcon: false }" |
|||
:toolbar-actions="[ |
|||
'query', |
|||
'refresh', |
|||
'separator', |
|||
'add', |
|||
'clone', |
|||
'edit', |
|||
'remove', |
|||
'separator', |
|||
{ |
|||
name: 'setPassword', |
|||
label: $t('system.user.grid.toolbar.setPassword'), |
|||
icon: 'bi-shield-check', |
|||
enableIf: function (arg) { |
|||
return arg.selected; |
|||
}, |
|||
click: function (arg) {}, |
|||
}, |
|||
{ |
|||
name: 'setAllPassword', |
|||
label: $t('system.user.grid.toolbar.setAllPassword'), |
|||
icon: 'bi-shield', |
|||
click: function () {}, |
|||
}, |
|||
'separator', |
|||
{ |
|||
name: 'resetPassword', |
|||
label: $t('system.user.grid.toolbar.resetPassword'), |
|||
icon: 'bi-shield-fill-check', |
|||
enableIf: function (arg) { |
|||
return arg.selected; |
|||
}, |
|||
click: function (arg) {}, |
|||
}, |
|||
{ |
|||
name: 'resetAllPassword', |
|||
label: $t('system.user.grid.toolbar.resetAllPassword'), |
|||
icon: 'bi-shield-fill', |
|||
click: function () {}, |
|||
}, |
|||
'separator', |
|||
'view', |
|||
'separator', |
|||
'export', |
|||
]" |
|||
:columns="[ |
|||
{ width: 150, name: 'loginName', label: $t('loginName') }, |
|||
{ width: '100%', name: 'userName', label: $t('userName') }, |
|||
{ |
|||
width: 150, |
|||
name: 'enable', |
|||
label: $t('status'), |
|||
}, |
|||
{ width: 100, name: 'lastModifier', label: $t('lastModifier') }, |
|||
{ width: 110, name: 'lastModifyDate', label: $t('lastModifyDate') }, |
|||
]" |
|||
></w-grid> |
|||
</w-dialog> |
|||
</template> |
|||
|
|||
<script> |
|||
import { ref } from 'vue'; |
|||
const columns = [ |
|||
{ |
|||
name: 'name', |
|||
required: true, |
|||
label: 'Dessert (100g serving)', |
|||
align: 'left', |
|||
field: (row) => row.name, |
|||
format: (val) => `${val}`, |
|||
sortable: true, |
|||
}, |
|||
{ name: 'calories', align: 'center', label: 'Calories', field: 'calories', sortable: true }, |
|||
{ name: 'fat', label: 'Fat (g)', field: 'fat', sortable: true }, |
|||
{ name: 'carbs', label: 'Carbs (g)', field: 'carbs' }, |
|||
{ name: 'protein', label: 'Protein (g)', field: 'protein' }, |
|||
{ name: 'sodium', label: 'Sodium (mg)', field: 'sodium' }, |
|||
{ name: 'calcium', label: 'Calcium (%)', field: 'calcium', sortable: true, sort: (a, b) => parseInt(a, 10) - parseInt(b, 10) }, |
|||
{ name: 'iron', label: 'Iron (%)', field: 'iron', sortable: true, sort: (a, b) => parseInt(a, 10) - parseInt(b, 10) }, |
|||
]; |
|||
const rows = []; |
|||
const rows2 = [ |
|||
{ |
|||
name: 'Frozen Yogurt', |
|||
calories: 159, |
|||
fat: 6.0, |
|||
carbs: 24, |
|||
protein: 4.0, |
|||
sodium: 87, |
|||
calcium: '14%', |
|||
iron: '1%', |
|||
}, |
|||
{ |
|||
name: 'Ice cream sandwich', |
|||
calories: 237, |
|||
fat: 9.0, |
|||
carbs: 37, |
|||
protein: 4.3, |
|||
sodium: 129, |
|||
calcium: '8%', |
|||
iron: '1%', |
|||
}, |
|||
{ |
|||
name: 'Eclair', |
|||
calories: 262, |
|||
fat: 16.0, |
|||
carbs: 23, |
|||
protein: 6.0, |
|||
sodium: 337, |
|||
calcium: '6%', |
|||
iron: '7%', |
|||
}, |
|||
{ |
|||
name: 'Cupcake', |
|||
calories: 305, |
|||
fat: 3.7, |
|||
carbs: 67, |
|||
protein: 4.3, |
|||
sodium: 413, |
|||
calcium: '3%', |
|||
iron: '8%', |
|||
}, |
|||
{ |
|||
name: 'Gingerbread', |
|||
calories: 356, |
|||
fat: 16.0, |
|||
carbs: 49, |
|||
protein: 3.9, |
|||
sodium: 327, |
|||
calcium: '7%', |
|||
iron: '16%', |
|||
}, |
|||
{ |
|||
name: 'Jelly bean', |
|||
calories: 375, |
|||
fat: 0.0, |
|||
carbs: 94, |
|||
protein: 0.0, |
|||
sodium: 50, |
|||
calcium: '0%', |
|||
iron: '0%', |
|||
}, |
|||
{ |
|||
name: 'Lollipop', |
|||
calories: 392, |
|||
fat: 0.2, |
|||
carbs: 98, |
|||
protein: 0, |
|||
sodium: 38, |
|||
calcium: '0%', |
|||
iron: '2%', |
|||
}, |
|||
{ |
|||
name: 'Honeycomb', |
|||
calories: 408, |
|||
fat: 3.2, |
|||
carbs: 87, |
|||
protein: 6.5, |
|||
sodium: 562, |
|||
calcium: '0%', |
|||
iron: '45%', |
|||
}, |
|||
{ |
|||
name: 'Donut', |
|||
calories: 452, |
|||
fat: 25.0, |
|||
carbs: 51, |
|||
protein: 4.9, |
|||
sodium: 326, |
|||
calcium: '2%', |
|||
iron: '22%', |
|||
}, |
|||
{ |
|||
name: 'KitKat', |
|||
calories: 518, |
|||
fat: 26.0, |
|||
carbs: 65, |
|||
protein: 7, |
|||
sodium: 54, |
|||
calcium: '12%', |
|||
iron: '6%', |
|||
}, |
|||
{ |
|||
name: 'KitKat', |
|||
calories: 518, |
|||
fat: 26.0, |
|||
carbs: 65, |
|||
protein: 7, |
|||
sodium: 54, |
|||
calcium: '12%', |
|||
iron: '6%', |
|||
}, |
|||
{ |
|||
name: 'KitKat', |
|||
calories: 518, |
|||
fat: 26.0, |
|||
carbs: 65, |
|||
protein: 7, |
|||
sodium: 54, |
|||
calcium: '12%', |
|||
iron: '6%', |
|||
}, |
|||
{ |
|||
name: 'KitKat', |
|||
calories: 518, |
|||
fat: 26.0, |
|||
carbs: 65, |
|||
protein: 7, |
|||
sodium: 54, |
|||
calcium: '12%', |
|||
iron: '6%', |
|||
}, |
|||
{ |
|||
name: 'KitKat', |
|||
calories: 518, |
|||
fat: 26.0, |
|||
carbs: 65, |
|||
protein: 7, |
|||
sodium: 54, |
|||
calcium: '12%', |
|||
iron: '6%', |
|||
}, |
|||
{ |
|||
name: 'KitKat', |
|||
calories: 518, |
|||
fat: 26.0, |
|||
carbs: 65, |
|||
protein: 7, |
|||
sodium: 54, |
|||
calcium: '12%', |
|||
iron: '6%', |
|||
}, |
|||
{ |
|||
name: 'KitKat', |
|||
calories: 518, |
|||
fat: 26.0, |
|||
carbs: 65, |
|||
protein: 7, |
|||
sodium: 54, |
|||
calcium: '12%', |
|||
iron: '6%', |
|||
}, |
|||
]; |
|||
<script setup lang="ts"> |
|||
import { Environment } from '@/platform'; |
|||
|
|||
export default { |
|||
setup() { |
|||
return { |
|||
columns, |
|||
rows, |
|||
}; |
|||
}, |
|||
}; |
|||
const isShow = true; |
|||
</script> |
|||
|
@ -0,0 +1,25 @@ |
|||
import { Tools } from '@/platform'; |
|||
|
|||
const PassOrNotFormater = (value) => { |
|||
if (Tools.isUndefinedOrNull(value)) { |
|||
return ''; |
|||
} |
|||
if (value === 'PASSED') { |
|||
return { |
|||
componentType: 'QIcon', |
|||
attrs: { name: 'bi-check-circle', size: '20px', color: 'green' }, |
|||
}; |
|||
} else if (value === 'UN_PASSED') { |
|||
return { |
|||
componentType: 'QIcon', |
|||
attrs: { name: 'bi-x-circle', size: '20px', color: 'red' }, |
|||
}; |
|||
} else if (value === 'ERROR') { |
|||
return { |
|||
componentType: 'QIcon', |
|||
attrs: { name: 'bi-x-circle', size: '20px', color: 'red' }, |
|||
}; |
|||
} |
|||
}; |
|||
|
|||
export default PassOrNotFormater; |
@ -1,258 +1,85 @@ |
|||
<template> |
|||
<div class="q-pa-md"> |
|||
<q-table flat bordered separator="cell" title="Treats" :rows="rows2" :columns="columns" row-key="name" hide-no-data> |
|||
<template v-if="rows && rows.length > 0" #header="props"> |
|||
<q-tr :props="props"> |
|||
<q-th v-for="col in props.cols" :key="col.name" :props="props" class="text-italic text-purple"> |
|||
{{ col.label }} |
|||
</q-th> |
|||
</q-tr> |
|||
</template> |
|||
<template v-else #header="props"> |
|||
<q-tr :props="props"> |
|||
<q-th v-for="col in props.cols" :key="col.name" :props="props" class="text-italic text-purple"> |
|||
{{ col.label }} |
|||
</q-th> |
|||
</q-tr> |
|||
<q-tr :props="props" style="height: 300px"> </q-tr> |
|||
</template> |
|||
|
|||
<template #body="props"> |
|||
<q-tr :props="props"> |
|||
<q-td key="name" :props="props"> |
|||
{{ props.row.name }} |
|||
</q-td> |
|||
<q-td key="calories" :props="props"> |
|||
<q-badge color="green"> |
|||
{{ props.row.calories }} |
|||
</q-badge> |
|||
</q-td> |
|||
<q-td key="fat" :props="props"> |
|||
<q-badge color="purple"> |
|||
{{ props.row.fat }} |
|||
</q-badge> |
|||
</q-td> |
|||
<q-td key="carbs" :props="props"> |
|||
<q-badge color="orange"> |
|||
{{ props.row.carbs }} |
|||
</q-badge> |
|||
</q-td> |
|||
<q-td key="protein" :props="props"> |
|||
<q-badge color="primary"> |
|||
{{ props.row.protein }} |
|||
</q-badge> |
|||
</q-td> |
|||
<q-td key="sodium" :props="props"> |
|||
<q-badge color="teal"> |
|||
{{ props.row.sodium }} |
|||
</q-badge> |
|||
</q-td> |
|||
<q-td key="calcium" :props="props"> |
|||
<q-badge color="accent"> |
|||
{{ props.row.calcium }} |
|||
</q-badge> |
|||
</q-td> |
|||
<q-td key="iron" :props="props"> |
|||
<q-badge color="amber"> |
|||
{{ props.row.iron }} |
|||
</q-badge> |
|||
</q-td> |
|||
</q-tr> |
|||
</template> |
|||
</q-table> |
|||
</div> |
|||
<w-dialog v-model="isShow"> |
|||
<w-grid |
|||
ref="userGridRef" |
|||
:title="$t('system.user.grid.title')" |
|||
:config-button="true" |
|||
selection="multiple" |
|||
:checkbox-selection="true" |
|||
:data-url="Environment.apiContextPath('/api/system/user')" |
|||
:pagination="{ |
|||
sortBy: 'loginName', |
|||
descending: false, |
|||
}" |
|||
:query-form-cols-num="3" |
|||
:query-form-fields="[ |
|||
{ name: 'loginName', label: $t('loginName'), type: 'text' }, |
|||
{ name: 'userName', label: $t('userName'), type: 'text' }, |
|||
{ name: 'enable', label: $t('isEnable'), type: 'select' }, |
|||
]" |
|||
:toolbar-configure="{ noIcon: false }" |
|||
:toolbar-actions="[ |
|||
'query', |
|||
'refresh', |
|||
'separator', |
|||
'add', |
|||
'clone', |
|||
'edit', |
|||
'remove', |
|||
'separator', |
|||
{ |
|||
name: 'setPassword', |
|||
label: $t('system.user.grid.toolbar.setPassword'), |
|||
icon: 'bi-shield-check', |
|||
enableIf: function (arg) { |
|||
return arg.selected; |
|||
}, |
|||
click: function (arg) {}, |
|||
}, |
|||
{ |
|||
name: 'setAllPassword', |
|||
label: $t('system.user.grid.toolbar.setAllPassword'), |
|||
icon: 'bi-shield', |
|||
click: function () {}, |
|||
}, |
|||
'separator', |
|||
{ |
|||
name: 'resetPassword', |
|||
label: $t('system.user.grid.toolbar.resetPassword'), |
|||
icon: 'bi-shield-fill-check', |
|||
enableIf: function (arg) { |
|||
return arg.selected; |
|||
}, |
|||
click: function (arg) {}, |
|||
}, |
|||
{ |
|||
name: 'resetAllPassword', |
|||
label: $t('system.user.grid.toolbar.resetAllPassword'), |
|||
icon: 'bi-shield-fill', |
|||
click: function () {}, |
|||
}, |
|||
'separator', |
|||
'view', |
|||
'separator', |
|||
'export', |
|||
]" |
|||
:columns="[ |
|||
{ width: 150, name: 'loginName', label: $t('loginName') }, |
|||
{ width: '100%', name: 'userName', label: $t('userName') }, |
|||
{ |
|||
width: 150, |
|||
name: 'enable', |
|||
label: $t('status'), |
|||
}, |
|||
{ width: 100, name: 'lastModifier', label: $t('lastModifier') }, |
|||
{ width: 110, name: 'lastModifyDate', label: $t('lastModifyDate') }, |
|||
]" |
|||
></w-grid> |
|||
</w-dialog> |
|||
</template> |
|||
|
|||
<script> |
|||
import { ref } from 'vue'; |
|||
const columns = [ |
|||
{ |
|||
name: 'name', |
|||
required: true, |
|||
label: 'Dessert (100g serving)', |
|||
align: 'left', |
|||
field: (row) => row.name, |
|||
format: (val) => `${val}`, |
|||
sortable: true, |
|||
}, |
|||
{ name: 'calories', align: 'center', label: 'Calories', field: 'calories', sortable: true }, |
|||
{ name: 'fat', label: 'Fat (g)', field: 'fat', sortable: true }, |
|||
{ name: 'carbs', label: 'Carbs (g)', field: 'carbs' }, |
|||
{ name: 'protein', label: 'Protein (g)', field: 'protein' }, |
|||
{ name: 'sodium', label: 'Sodium (mg)', field: 'sodium' }, |
|||
{ name: 'calcium', label: 'Calcium (%)', field: 'calcium', sortable: true, sort: (a, b) => parseInt(a, 10) - parseInt(b, 10) }, |
|||
{ name: 'iron', label: 'Iron (%)', field: 'iron', sortable: true, sort: (a, b) => parseInt(a, 10) - parseInt(b, 10) }, |
|||
]; |
|||
const rows = []; |
|||
const rows2 = [ |
|||
{ |
|||
name: 'Frozen Yogurt', |
|||
calories: 159, |
|||
fat: 6.0, |
|||
carbs: 24, |
|||
protein: 4.0, |
|||
sodium: 87, |
|||
calcium: '14%', |
|||
iron: '1%', |
|||
}, |
|||
{ |
|||
name: 'Ice cream sandwich', |
|||
calories: 237, |
|||
fat: 9.0, |
|||
carbs: 37, |
|||
protein: 4.3, |
|||
sodium: 129, |
|||
calcium: '8%', |
|||
iron: '1%', |
|||
}, |
|||
{ |
|||
name: 'Eclair', |
|||
calories: 262, |
|||
fat: 16.0, |
|||
carbs: 23, |
|||
protein: 6.0, |
|||
sodium: 337, |
|||
calcium: '6%', |
|||
iron: '7%', |
|||
}, |
|||
{ |
|||
name: 'Cupcake', |
|||
calories: 305, |
|||
fat: 3.7, |
|||
carbs: 67, |
|||
protein: 4.3, |
|||
sodium: 413, |
|||
calcium: '3%', |
|||
iron: '8%', |
|||
}, |
|||
{ |
|||
name: 'Gingerbread', |
|||
calories: 356, |
|||
fat: 16.0, |
|||
carbs: 49, |
|||
protein: 3.9, |
|||
sodium: 327, |
|||
calcium: '7%', |
|||
iron: '16%', |
|||
}, |
|||
{ |
|||
name: 'Jelly bean', |
|||
calories: 375, |
|||
fat: 0.0, |
|||
carbs: 94, |
|||
protein: 0.0, |
|||
sodium: 50, |
|||
calcium: '0%', |
|||
iron: '0%', |
|||
}, |
|||
{ |
|||
name: 'Lollipop', |
|||
calories: 392, |
|||
fat: 0.2, |
|||
carbs: 98, |
|||
protein: 0, |
|||
sodium: 38, |
|||
calcium: '0%', |
|||
iron: '2%', |
|||
}, |
|||
{ |
|||
name: 'Honeycomb', |
|||
calories: 408, |
|||
fat: 3.2, |
|||
carbs: 87, |
|||
protein: 6.5, |
|||
sodium: 562, |
|||
calcium: '0%', |
|||
iron: '45%', |
|||
}, |
|||
{ |
|||
name: 'Donut', |
|||
calories: 452, |
|||
fat: 25.0, |
|||
carbs: 51, |
|||
protein: 4.9, |
|||
sodium: 326, |
|||
calcium: '2%', |
|||
iron: '22%', |
|||
}, |
|||
{ |
|||
name: 'KitKat', |
|||
calories: 518, |
|||
fat: 26.0, |
|||
carbs: 65, |
|||
protein: 7, |
|||
sodium: 54, |
|||
calcium: '12%', |
|||
iron: '6%', |
|||
}, |
|||
{ |
|||
name: 'KitKat', |
|||
calories: 518, |
|||
fat: 26.0, |
|||
carbs: 65, |
|||
protein: 7, |
|||
sodium: 54, |
|||
calcium: '12%', |
|||
iron: '6%', |
|||
}, |
|||
{ |
|||
name: 'KitKat', |
|||
calories: 518, |
|||
fat: 26.0, |
|||
carbs: 65, |
|||
protein: 7, |
|||
sodium: 54, |
|||
calcium: '12%', |
|||
iron: '6%', |
|||
}, |
|||
{ |
|||
name: 'KitKat', |
|||
calories: 518, |
|||
fat: 26.0, |
|||
carbs: 65, |
|||
protein: 7, |
|||
sodium: 54, |
|||
calcium: '12%', |
|||
iron: '6%', |
|||
}, |
|||
{ |
|||
name: 'KitKat', |
|||
calories: 518, |
|||
fat: 26.0, |
|||
carbs: 65, |
|||
protein: 7, |
|||
sodium: 54, |
|||
calcium: '12%', |
|||
iron: '6%', |
|||
}, |
|||
{ |
|||
name: 'KitKat', |
|||
calories: 518, |
|||
fat: 26.0, |
|||
carbs: 65, |
|||
protein: 7, |
|||
sodium: 54, |
|||
calcium: '12%', |
|||
iron: '6%', |
|||
}, |
|||
{ |
|||
name: 'KitKat', |
|||
calories: 518, |
|||
fat: 26.0, |
|||
carbs: 65, |
|||
protein: 7, |
|||
sodium: 54, |
|||
calcium: '12%', |
|||
iron: '6%', |
|||
}, |
|||
]; |
|||
<script setup lang="ts"> |
|||
import { Environment } from '@/platform'; |
|||
|
|||
export default { |
|||
setup() { |
|||
return { |
|||
columns, |
|||
rows, |
|||
}; |
|||
}, |
|||
}; |
|||
const isShow = true; |
|||
</script> |
|||
|
@ -1 +1,11 @@ |
|||
= The HTML DOM API |
|||
= clientHeight、offsetHeight、scrollHeight |
|||
image::9999-appendix/docker/tidb/001.png[,60%] |
|||
|
|||
|=== |
|||
| clientHeight | 元素高度 + 内边距 |
|||
| offsetHeight | |
|||
| scrollHeight | |
|||
|=== |
|||
|
|||
|
|||
|
@ -0,0 +1,34 @@ |
|||
package io.sc.platform.flowable.controller; |
|||
|
|||
import io.sc.platform.flowable.service.ProcessQueryService; |
|||
import io.sc.platform.flowable.service.ProcessToolsService; |
|||
import io.sc.platform.flowable.support.*; |
|||
import io.sc.platform.mvc.support.FileDownloader; |
|||
import io.sc.platform.orm.service.support.QueryParameter; |
|||
import io.sc.platform.orm.service.support.criteria.Criteria; |
|||
import io.sc.platform.orm.service.support.criteria.impl.Equals; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.data.domain.Page; |
|||
import org.springframework.web.bind.annotation.*; |
|||
|
|||
import javax.servlet.http.HttpServletRequest; |
|||
import javax.servlet.http.HttpServletResponse; |
|||
import java.io.InputStream; |
|||
import java.util.Collections; |
|||
import java.util.List; |
|||
|
|||
@RestController |
|||
@RequestMapping("/api/flowable/tools") |
|||
public class ProcessToolsWebController { |
|||
@Autowired private ProcessToolsService service; |
|||
|
|||
@PostMapping("cleanRuntimeData") |
|||
public void cleanRuntimeData() throws Exception{ |
|||
service.cleanRuntimeData(); |
|||
} |
|||
|
|||
@PostMapping("cleanHistoryData") |
|||
public void cleanHistoryData() throws Exception{ |
|||
service.cleanHistoryData(); |
|||
} |
|||
} |
@ -0,0 +1,6 @@ |
|||
package io.sc.platform.flowable.service; |
|||
|
|||
public interface ProcessToolsService { |
|||
public void cleanRuntimeData() throws Exception; |
|||
public void cleanHistoryData() throws Exception; |
|||
} |
@ -0,0 +1,46 @@ |
|||
package io.sc.platform.flowable.service.impl; |
|||
|
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.jdbc.core.JdbcTemplate; |
|||
import org.springframework.stereotype.Service; |
|||
import io.sc.platform.flowable.service.ProcessToolsService; |
|||
|
|||
import javax.transaction.Transactional; |
|||
|
|||
@Service |
|||
public class ProcessToolsServiceImpl implements ProcessToolsService{ |
|||
@Autowired private JdbcTemplate jdbcTemplate; |
|||
|
|||
@Override |
|||
@Transactional |
|||
public void cleanRuntimeData() throws Exception { |
|||
jdbcTemplate.update("delete from ACT_RU_ACTINST"); |
|||
jdbcTemplate.update("delete from ACT_RU_DEADLETTER_JOB"); |
|||
jdbcTemplate.update("delete from ACT_RU_ENTITYLINK"); |
|||
jdbcTemplate.update("delete from ACT_RU_EVENT_SUBSCR"); |
|||
jdbcTemplate.update("delete from ACT_RU_EXTERNAL_JOB"); |
|||
jdbcTemplate.update("delete from ACT_RU_HISTORY_JOB"); |
|||
jdbcTemplate.update("delete from ACT_RU_IDENTITYLINK"); |
|||
jdbcTemplate.update("delete from ACT_RU_JOB"); |
|||
jdbcTemplate.update("delete from ACT_RU_SUSPENDED_JOB"); |
|||
jdbcTemplate.update("delete from ACT_RU_TASK"); |
|||
jdbcTemplate.update("delete from ACT_RU_TIMER_JOB"); |
|||
jdbcTemplate.update("delete from ACT_RU_VARIABLE"); |
|||
jdbcTemplate.update("delete from ACT_RU_EXECUTION"); |
|||
} |
|||
|
|||
@Override |
|||
@Transactional |
|||
public void cleanHistoryData() throws Exception { |
|||
jdbcTemplate.update("delete from ACT_HI_ACTINST"); |
|||
jdbcTemplate.update("delete from ACT_HI_ATTACHMENT"); |
|||
jdbcTemplate.update("delete from ACT_HI_COMMENT"); |
|||
jdbcTemplate.update("delete from ACT_HI_DETAIL"); |
|||
jdbcTemplate.update("delete from ACT_HI_ENTITYLINK"); |
|||
jdbcTemplate.update("delete from ACT_HI_IDENTITYLINK"); |
|||
jdbcTemplate.update("delete from ACT_HI_PROCINST"); |
|||
jdbcTemplate.update("delete from ACT_HI_TASKINST"); |
|||
jdbcTemplate.update("delete from ACT_HI_TSK_LOG"); |
|||
jdbcTemplate.update("delete from ACT_HI_VARINST"); |
|||
} |
|||
} |
@ -0,0 +1,25 @@ |
|||
package io.sc.platform.flowable.support; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
|
|||
public class SelectAssigneeWrapper { |
|||
private String activeName; |
|||
private List<Assignee> assignees =new ArrayList<>(); |
|||
|
|||
public String getActiveName() { |
|||
return activeName; |
|||
} |
|||
|
|||
public void setActiveName(String activeName) { |
|||
this.activeName = activeName; |
|||
} |
|||
|
|||
public List<Assignee> getAssignees() { |
|||
return assignees; |
|||
} |
|||
|
|||
public void setAssignees(List<Assignee> assignees) { |
|||
this.assignees = assignees; |
|||
} |
|||
} |
@ -0,0 +1,68 @@ |
|||
<template> |
|||
<w-dialog ref="dialogRef" :title="$t('lcdp.bpm.completeTask.dialog.title')" width="800px" :can-maximize="false"> |
|||
<template #buttons> |
|||
<WWorkflowAction |
|||
ref="workflowActionRef" |
|||
:task-id="taskIdRef" |
|||
:data="formModelValue" |
|||
:action-url="Environment.apiContextPath('/api/flowable/process/operation/complete')" |
|||
@after-submit="afterSubmit" |
|||
> |
|||
</WWorkflowAction> |
|||
</template> |
|||
<w-form |
|||
v-model="formModelValue" |
|||
:cols-num="1" |
|||
:fields="[ |
|||
{ name: 'variables', label: $t('lcdp.bpm.processInstance.grid.entity.variables'), type: 'code-mirror', lang: 'json', rows: 5 }, |
|||
{ name: 'transientVariables', label: $t('lcdp.bpm.processInstance.grid.entity.transientVariables'), type: 'code-mirror', lang: 'json', rows: 5 }, |
|||
]" |
|||
> |
|||
</w-form> |
|||
</w-dialog> |
|||
</template> |
|||
<script setup lang="ts"> |
|||
import { nextTick, ref, reactive } from 'vue'; |
|||
import WWorkflowAction from './WWorkflowAction.vue'; |
|||
import { Environment } from 'platform-core'; |
|||
|
|||
/** |
|||
* 定义组件支持的自定义事件 |
|||
*/ |
|||
const emit = defineEmits([ |
|||
'afterTaskCompleted', // 提交成功后 |
|||
]); |
|||
|
|||
const dialogRef = ref(); |
|||
const taskIdRef = ref<string>(''); |
|||
const workflowActionRef = ref(); |
|||
const formModelValue = reactive({ |
|||
variables: undefined, |
|||
transientVariables: undefined, |
|||
}); |
|||
|
|||
const open = (taskId: string) => { |
|||
taskIdRef.value = taskId; |
|||
formModelValue.transientVariables = JSON.stringify({ task_treatment: '此处填写处理意见' }, null, 2); |
|||
formModelValue.variables = undefined; |
|||
dialogRef.value.show(); |
|||
// nextTick(() => { |
|||
// workflowActionRef.value.setTaskId(taskId); |
|||
// workflowActionRef.value.setData(formModelValue); |
|||
// }); |
|||
}; |
|||
|
|||
const close = () => { |
|||
dialogRef.value.hide(); |
|||
}; |
|||
|
|||
const afterSubmit = () => { |
|||
close(); |
|||
emit('afterTaskCompleted'); |
|||
}; |
|||
|
|||
defineExpose({ |
|||
open, |
|||
close, |
|||
}); |
|||
</script> |
@ -0,0 +1,65 @@ |
|||
<template> |
|||
<w-dialog |
|||
ref="dialogRef" |
|||
:title="$t('lcdp.bpm.selectAssignee.dialog.title')" |
|||
width="500px" |
|||
:can-maximize="false" |
|||
:buttons="[ |
|||
{ |
|||
name: 'confirm', |
|||
label: $t('confirm'), |
|||
click: () => { |
|||
emit('assigneeSelected', formRef.getFieldValue('assignee')); |
|||
close(); |
|||
}, |
|||
}, |
|||
]" |
|||
> |
|||
<div |
|||
v-dompurify-html="$t('lcdp.bpm.selectAssignee.entity.assignee.tip', { activeName: selectAssigneeWrapperRef.activeName })" |
|||
class="text-body2 py-2" |
|||
></div> |
|||
<w-form |
|||
ref="formRef" |
|||
:cols-num="1" |
|||
:fields="[{ name: 'assignee', label: $t('lcdp.bpm.selectAssignee.entity.assignee'), type: 'select', options: assigneeOptionsRef }]" |
|||
> |
|||
</w-form> |
|||
</w-dialog> |
|||
</template> |
|||
<script setup lang="ts"> |
|||
import { reactive, ref } from 'vue'; |
|||
|
|||
/** |
|||
* 定义组件支持的自定义事件 |
|||
*/ |
|||
const emit = defineEmits([ |
|||
'assigneeSelected', // 选择了候选人 |
|||
]); |
|||
|
|||
const dialogRef = ref(); |
|||
const formRef = ref(); |
|||
const assigneeOptionsRef = ref([]); |
|||
const selectAssigneeWrapperRef = ref(); |
|||
|
|||
const open = (selectAssigneeWrapper) => { |
|||
selectAssigneeWrapperRef.value = selectAssigneeWrapper; |
|||
assigneeOptionsRef.value.splice(0, assigneeOptionsRef.value.length); |
|||
const assignees = selectAssigneeWrapper?.assignees; |
|||
if (assignees) { |
|||
for (let i = 0; i < assignees.length; i++) { |
|||
assigneeOptionsRef.value.push({ value: assignees[i].loginName, label: assignees[i].loginName + '/' + assignees[i].userName }); |
|||
} |
|||
} |
|||
dialogRef.value.show(); |
|||
}; |
|||
|
|||
const close = () => { |
|||
dialogRef.value.hide(); |
|||
}; |
|||
|
|||
defineExpose({ |
|||
open, |
|||
close, |
|||
}); |
|||
</script> |
@ -0,0 +1,158 @@ |
|||
<template> |
|||
<div :class="`flex ${align === 'right' ? 'justify-end' : ''} gap-4`"> |
|||
<q-btn v-for="action in actionsRef" :key="action.name" :label="action.title" color="primary" @click="buttonClick(action)"></q-btn> |
|||
</div> |
|||
<SelectAssigneeDialog ref="selectAssigneeDialogRef" @assignee-selected="assigneeSelected"></SelectAssigneeDialog> |
|||
</template> |
|||
<script setup lang="ts"> |
|||
import { ref, onMounted } from 'vue'; |
|||
import { useI18n } from 'vue-i18n'; |
|||
import { Environment, Tools, axios } from 'platform-core'; |
|||
import SelectAssigneeDialog from './SelectAssigneeDialog.vue'; |
|||
import { reactive } from 'vue'; |
|||
|
|||
/** |
|||
* 定义组件支持的自定义属性 |
|||
*/ |
|||
const props = defineProps({ |
|||
//任务 ID |
|||
taskId: { type: String, default: undefined }, |
|||
//数据对象 |
|||
data: { |
|||
type: Object, |
|||
default: () => { |
|||
return {}; |
|||
}, |
|||
}, |
|||
//对齐方式 |
|||
align: { type: String, default: 'center' }, |
|||
// 完成操作对应的服务器端控制器 url |
|||
actionUrl: { type: String, default: undefined }, |
|||
// 获取回退按钮列表的 url,由系统自动提供,使用者无需指定 |
|||
gobackActionUrl: { type: String, default: '/api/flowable/process/operation/getGobacks' }, |
|||
// 默认按钮 |
|||
defaultActionButtons: { type: [Array, Object], default: undefined }, |
|||
// 默认按钮放置位置 |
|||
defaultActionButtonsPlacement: { type: String, default: 'right' }, |
|||
// 是否 goback 按钮组采用下拉列表模式 |
|||
isGobackActionDropdown: { type: Boolean, default: false }, |
|||
}); |
|||
|
|||
/** |
|||
* 定义组件支持的自定义事件 |
|||
*/ |
|||
const emit = defineEmits([ |
|||
'afterSubmit', // 提交成功后 |
|||
]); |
|||
|
|||
const { t } = useI18n(); |
|||
const actionsRef = ref([]); |
|||
const selectAssigneeDialogRef = ref(); |
|||
const currentActionRef = ref(null); |
|||
|
|||
const buildActions = (taskId: string) => { |
|||
axios.get(Environment.apiContextPath(props.gobackActionUrl + '/' + taskId)).then((data) => { |
|||
buildButtons(data.data); |
|||
}); |
|||
}; |
|||
|
|||
const buildButtons = (gobacks) => { |
|||
const buttons = []; |
|||
// 添加默认按钮(左边) |
|||
if (props.defaultActionButtonsPlacement === 'left') { |
|||
if (props.defaultActionButtons && props.defaultActionButtons && props.defaultActionButtons.length > 0) { |
|||
for (let i = 0; i < props.defaultActionButtons.length; i++) { |
|||
buttons.push(props.defaultActionButtons[i]); |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 回退按钮 |
|||
if (gobacks && gobacks.length > 0) { |
|||
for (let i = 0; i < gobacks.length; i++) { |
|||
const goback = gobacks[i]; |
|||
const transientVariables = {}; |
|||
transientVariables[goback.variableName] = goback.variableValue; |
|||
buttons.push({ |
|||
title: goback.title || t('goback'), |
|||
transientVariables, |
|||
}); |
|||
} |
|||
} |
|||
|
|||
// 提交按钮 |
|||
buttons.push({ title: t('submit') }); |
|||
|
|||
// 添加默认按钮(右边) |
|||
if (props.defaultActionButtonsPlacement === 'right') { |
|||
if (props.defaultActionButtons && props.defaultActionButtons && props.defaultActionButtons.length > 0) { |
|||
for (let i = 0; i < props.defaultActionButtons.length; i++) { |
|||
buttons.push(props.defaultActionButtons[i]); |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 更新到响应式变量中 |
|||
actionsRef.value.splice(0, actionsRef.value.length); |
|||
for (let i = 0; i < buttons.length; i++) { |
|||
actionsRef.value.push(buttons[i]); |
|||
} |
|||
}; |
|||
|
|||
const buttonClick = (action) => { |
|||
currentActionRef.value = action; |
|||
const data = { |
|||
variables: {}, |
|||
transientVariables: {}, |
|||
}; |
|||
if (!Tools.isUndefinedOrNull(props.data)) { |
|||
if (!Tools.isEmpty(props.data.variables)) { |
|||
data.variables = JSON.parse(props.data.variables); |
|||
} |
|||
if (!Tools.isEmpty(props.data.transientVariables)) { |
|||
data.transientVariables = JSON.parse(props.data.transientVariables); |
|||
} |
|||
} |
|||
if (!Tools.isUndefinedOrNull(action)) { |
|||
if (!Tools.isUndefinedOrNull(action.transientVariables)) { |
|||
Tools.mergeObject(data.transientVariables, action.transientVariables); |
|||
} |
|||
} |
|||
console.log(data); |
|||
|
|||
// 如果是点击了“回退”按钮,将回退控制变量付给临时变量 |
|||
axios.post(props.actionUrl + '/' + props.taskId, data).then((response) => { |
|||
if (response.data.code === 0) { |
|||
// 操作成功 |
|||
emit('afterSubmit'); |
|||
} else if (response.data.code === 1) { |
|||
// 需要选择处理人 |
|||
selectAssigneeDialogRef.value.open(response.data); |
|||
} |
|||
}); |
|||
}; |
|||
|
|||
const assigneeSelected = (assignee) => { |
|||
const data = currentActionRef.value; |
|||
if (props.data.variables) { |
|||
data.variables = JSON.parse(props.data.variables); |
|||
} |
|||
if (props.data.transientVariables) { |
|||
data.transientVariables = JSON.parse(props.data.transientVariables); |
|||
data.transientVariables.assignee = assignee; |
|||
} |
|||
axios.post(Environment.apiContextPath(props.actionUrl + props.taskId), data).then((response) => { |
|||
const rawData = response.data; |
|||
if (rawData.code === 0) { |
|||
// 操作成功 |
|||
emit('afterSubmit'); |
|||
} |
|||
}); |
|||
}; |
|||
|
|||
onMounted(() => { |
|||
if (props.taskId) { |
|||
buildActions(props.taskId); |
|||
} |
|||
}); |
|||
</script> |
@ -1,16 +1,19 @@ |
|||
package io.sc.platform.orm.api.vo; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonProperty; |
|||
|
|||
/** |
|||
* 版本化实体 VO 类 |
|||
*/ |
|||
public abstract class VersionVo extends BaseVo { |
|||
protected int jpaVersion; |
|||
@JsonProperty(index = 10001) |
|||
protected Integer jpaVersion; |
|||
|
|||
public int getJpaVersion() { |
|||
public Integer getJpaVersion() { |
|||
return jpaVersion; |
|||
} |
|||
|
|||
public void setJpaVersion(int jpaVersion) { |
|||
public void setJpaVersion(Integer jpaVersion) { |
|||
this.jpaVersion = jpaVersion; |
|||
} |
|||
} |
|||
|
Loading…
Reference in new issue