Merge remote-tracking branch 'yudao/dev' into dev
This commit is contained in:
@@ -45,7 +45,7 @@ setupVbenForm<ComponentType>({
|
||||
if (value === undefined || value === null || value.length === 0) {
|
||||
return true;
|
||||
} else if (!MOBILE_REGEX.test(value)) {
|
||||
return $t('ui.formRules.phone', [ctx.label]);
|
||||
return $t('ui.formRules.mobile', [ctx.label]);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
@@ -55,7 +55,7 @@ setupVbenForm<ComponentType>({
|
||||
return $t('ui.formRules.required', [ctx.label]);
|
||||
}
|
||||
if (!MOBILE_REGEX.test(value)) {
|
||||
return $t('ui.formRules.phone', [ctx.label]);
|
||||
return $t('ui.formRules.mobile', [ctx.label]);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* 来自 @vben/plugins/vxe-table style.css */
|
||||
/* 来自 @vben/plugins/vxe-table style.css TODO @puhui999:可以写下目的哈; */
|
||||
:root {
|
||||
--vxe-ui-font-color: hsl(var(--foreground));
|
||||
--vxe-ui-font-primary-color: hsl(var(--primary));
|
||||
|
||||
48
apps/web-antd/src/api/bpm/form/index.ts
Normal file
48
apps/web-antd/src/api/bpm/form/index.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import type { PageParam, PageResult } from '@vben/request';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export namespace BpmFormApi {
|
||||
// TODO @siye:注释加一个。。嘿嘿
|
||||
export interface FormVO {
|
||||
id?: number | undefined;
|
||||
name: string;
|
||||
conf: string;
|
||||
fields: string[];
|
||||
status: number;
|
||||
remark: string;
|
||||
createTime: string;
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取表单分页列表 */
|
||||
export async function getFormPage(params: PageParam) {
|
||||
return requestClient.get<PageResult<BpmFormApi.FormVO>>('/bpm/form/page', {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/** 获取表单详情 */
|
||||
export async function getFormDetail(id: number) {
|
||||
return requestClient.get<BpmFormApi.FormVO>(`/bpm/form/get?id=${id}`);
|
||||
}
|
||||
|
||||
/** 创建表单 */
|
||||
export async function createForm(data: BpmFormApi.FormVO) {
|
||||
return requestClient.post('/bpm/form/create', data);
|
||||
}
|
||||
|
||||
/** 更新表单 */
|
||||
export async function updateForm(data: BpmFormApi.FormVO) {
|
||||
return requestClient.put('/bpm/form/update', data);
|
||||
}
|
||||
|
||||
/** 删除表单 */
|
||||
export async function deleteForm(id: number) {
|
||||
return requestClient.delete(`/bpm/form/delete?id=${id}`);
|
||||
}
|
||||
|
||||
/** 获取表单简单列表 */
|
||||
export async function getFormSimpleList() {
|
||||
return requestClient.get<BpmFormApi.FormVO[]>('/bpm/form/simple-list');
|
||||
}
|
||||
2
apps/web-antd/src/components/table-action/index.ts
Normal file
2
apps/web-antd/src/components/table-action/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default as TableAction } from './table-action.vue';
|
||||
export * from './typing';
|
||||
268
apps/web-antd/src/components/table-action/table-action.vue
Normal file
268
apps/web-antd/src/components/table-action/table-action.vue
Normal file
@@ -0,0 +1,268 @@
|
||||
<!-- add by 星语:参考 vben2 的方式,增加 TableAction 组件 -->
|
||||
<script setup lang="ts">
|
||||
import type { ButtonType } from 'ant-design-vue/es/button';
|
||||
|
||||
import type { PropType } from 'vue';
|
||||
|
||||
import type { ActionItem, PopConfirm } from './typing';
|
||||
|
||||
import { computed, toRaw } from 'vue';
|
||||
|
||||
import { useAccess } from '@vben/access';
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
import { $t } from '@vben/locales';
|
||||
import { isBoolean, isFunction } from '@vben/utils';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Dropdown,
|
||||
Menu,
|
||||
Popconfirm,
|
||||
Space,
|
||||
Tooltip,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
const props = defineProps({
|
||||
actions: {
|
||||
type: Array as PropType<ActionItem[]>,
|
||||
default() {
|
||||
return [];
|
||||
},
|
||||
},
|
||||
dropDownActions: {
|
||||
type: Array as PropType<ActionItem[]>,
|
||||
default() {
|
||||
return [];
|
||||
},
|
||||
},
|
||||
divider: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
});
|
||||
|
||||
const { hasAccessByCodes } = useAccess();
|
||||
|
||||
function isIfShow(action: ActionItem): boolean {
|
||||
const ifShow = action.ifShow;
|
||||
|
||||
let isIfShow = true;
|
||||
|
||||
if (isBoolean(ifShow)) {
|
||||
isIfShow = ifShow;
|
||||
}
|
||||
if (isFunction(ifShow)) {
|
||||
isIfShow = ifShow(action);
|
||||
}
|
||||
return isIfShow;
|
||||
}
|
||||
|
||||
const getActions = computed(() => {
|
||||
return (toRaw(props.actions) || [])
|
||||
.filter((action) => {
|
||||
return (
|
||||
(hasAccessByCodes(action.auth || []) ||
|
||||
(action.auth || []).length === 0) &&
|
||||
isIfShow(action)
|
||||
);
|
||||
})
|
||||
.map((action) => {
|
||||
const { popConfirm } = action;
|
||||
return {
|
||||
// getPopupContainer: document.body,
|
||||
type: 'link' as ButtonType,
|
||||
...action,
|
||||
...popConfirm,
|
||||
onConfirm: popConfirm?.confirm,
|
||||
onCancel: popConfirm?.cancel,
|
||||
enable: !!popConfirm,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
const getDropdownList = computed((): any[] => {
|
||||
return (toRaw(props.dropDownActions) || [])
|
||||
.filter((action) => {
|
||||
return (
|
||||
(hasAccessByCodes(action.auth || []) ||
|
||||
(action.auth || []).length === 0) &&
|
||||
isIfShow(action)
|
||||
);
|
||||
})
|
||||
.map((action, index) => {
|
||||
const { label, popConfirm } = action;
|
||||
return {
|
||||
...action,
|
||||
...popConfirm,
|
||||
onConfirm: popConfirm?.confirm,
|
||||
onCancel: popConfirm?.cancel,
|
||||
text: label,
|
||||
divider:
|
||||
index < props.dropDownActions.length - 1 ? props.divider : false,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
function getPopConfirmProps(attrs: PopConfirm) {
|
||||
const originAttrs: any = attrs;
|
||||
delete originAttrs.icon;
|
||||
if (attrs.confirm && isFunction(attrs.confirm)) {
|
||||
originAttrs.onConfirm = attrs.confirm;
|
||||
delete originAttrs.confirm;
|
||||
}
|
||||
if (attrs.cancel && isFunction(attrs.cancel)) {
|
||||
originAttrs.onCancel = attrs.cancel;
|
||||
delete originAttrs.cancel;
|
||||
}
|
||||
return originAttrs;
|
||||
}
|
||||
|
||||
function getButtonProps(action: ActionItem) {
|
||||
const res = {
|
||||
type: action.type || 'primary',
|
||||
...action,
|
||||
};
|
||||
delete res.icon;
|
||||
return res;
|
||||
}
|
||||
|
||||
function handleMenuClick(e: any) {
|
||||
const action = getDropdownList.value[e.key];
|
||||
if (action.onClick && isFunction(action.onClick)) {
|
||||
action.onClick();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="m-table-action">
|
||||
<Space
|
||||
:size="
|
||||
getActions?.some((item: ActionItem) => item.type === 'link') ? 0 : 8
|
||||
"
|
||||
>
|
||||
<template v-for="(action, index) in getActions" :key="index">
|
||||
<Popconfirm
|
||||
v-if="action.popConfirm"
|
||||
v-bind="getPopConfirmProps(action.popConfirm)"
|
||||
>
|
||||
<template v-if="action.popConfirm.icon" #icon>
|
||||
<IconifyIcon :icon="action.popConfirm.icon" />
|
||||
</template>
|
||||
<Tooltip
|
||||
v-bind="
|
||||
typeof action.tooltip === 'string'
|
||||
? { title: action.tooltip }
|
||||
: { ...action.tooltip }
|
||||
"
|
||||
>
|
||||
<Button v-bind="getButtonProps(action)">
|
||||
<template v-if="action.icon" #icon>
|
||||
<IconifyIcon :icon="action.icon" />
|
||||
</template>
|
||||
{{ action.label }}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Popconfirm>
|
||||
<Tooltip
|
||||
v-else
|
||||
v-bind="
|
||||
typeof action.tooltip === 'string'
|
||||
? { title: action.tooltip }
|
||||
: { ...action.tooltip }
|
||||
"
|
||||
>
|
||||
<Button v-bind="getButtonProps(action)" @click="action.onClick">
|
||||
<template v-if="action.icon" #icon>
|
||||
<IconifyIcon :icon="action.icon" />
|
||||
</template>
|
||||
{{ action.label }}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</template>
|
||||
</Space>
|
||||
|
||||
<Dropdown v-if="getDropdownList.length > 0" :trigger="['hover']">
|
||||
<slot name="more">
|
||||
<Button size="small" type="link">
|
||||
<template #icon>
|
||||
{{ $t('page.action.more') }}
|
||||
<IconifyIcon class="icon-more" icon="ant-design:more-outlined" />
|
||||
</template>
|
||||
</Button>
|
||||
</slot>
|
||||
<template #overlay>
|
||||
<Menu @click="handleMenuClick">
|
||||
<Menu.Item v-for="(action, index) in getDropdownList" :key="index">
|
||||
<template v-if="action.popConfirm">
|
||||
<Popconfirm v-bind="getPopConfirmProps(action.popConfirm)">
|
||||
<template v-if="action.popConfirm.icon" #icon>
|
||||
<IconifyIcon :icon="action.popConfirm.icon" />
|
||||
</template>
|
||||
<div
|
||||
:class="
|
||||
action.disabled === true
|
||||
? 'cursor-not-allowed text-gray-300'
|
||||
: ''
|
||||
"
|
||||
>
|
||||
<IconifyIcon v-if="action.icon" :icon="action.icon" />
|
||||
<span class="ml-1">{{ action.text }}</span>
|
||||
</div>
|
||||
</Popconfirm>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div
|
||||
:class="
|
||||
action.disabled === true
|
||||
? 'cursor-not-allowed text-gray-300'
|
||||
: ''
|
||||
"
|
||||
>
|
||||
<IconifyIcon v-if="action.icon" :icon="action.icon" />
|
||||
{{ action.label }}
|
||||
</div>
|
||||
</template>
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
</template>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss">
|
||||
.m-table-action {
|
||||
.ant-btn {
|
||||
padding: 4px;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.ant-btn > .iconify + span,
|
||||
.ant-btn > span + .iconify {
|
||||
margin-inline-start: 4px;
|
||||
}
|
||||
|
||||
.iconify {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
font-style: normal;
|
||||
line-height: 0;
|
||||
vertical-align: -0.125em;
|
||||
color: inherit;
|
||||
text-align: center;
|
||||
text-transform: none;
|
||||
text-rendering: optimizelegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-popconfirm {
|
||||
.ant-popconfirm-buttons {
|
||||
.ant-btn {
|
||||
margin-inline-start: 4px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
27
apps/web-antd/src/components/table-action/typing.ts
Normal file
27
apps/web-antd/src/components/table-action/typing.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import type { ButtonProps } from 'ant-design-vue/es/button/buttonTypes';
|
||||
import type { TooltipProps } from 'ant-design-vue/es/tooltip/Tooltip';
|
||||
|
||||
export interface PopConfirm {
|
||||
title: string;
|
||||
okText?: string;
|
||||
cancelText?: string;
|
||||
confirm: () => void;
|
||||
cancel?: () => void;
|
||||
icon?: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export interface ActionItem extends ButtonProps {
|
||||
onClick?: () => void;
|
||||
label?: string;
|
||||
color?: 'error' | 'success' | 'warning';
|
||||
icon?: string;
|
||||
popConfirm?: PopConfirm;
|
||||
disabled?: boolean;
|
||||
divider?: boolean;
|
||||
// 权限编码控制是否显示
|
||||
auth?: string[];
|
||||
// 业务控制是否显示
|
||||
ifShow?: ((action: ActionItem) => boolean) | boolean;
|
||||
tooltip?: string | TooltipProps;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
// TODO @芋艿:是否有更好的组织形式?!
|
||||
import type { Key } from 'ant-design-vue/es/table/interface';
|
||||
|
||||
import type { SystemDeptApi } from '#/api/system/dept';
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { TableToolbar } from '#/components/table-toolbar';
|
||||
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
// TODO @puhui999:这里的注释、目的写下;
|
||||
export function useTableToolbar() {
|
||||
const hiddenSearchBar = ref(false); // 隐藏搜索栏
|
||||
const tableToolbarRef = ref<InstanceType<typeof TableToolbar>>();
|
||||
@@ -14,6 +15,7 @@ export function useTableToolbar() {
|
||||
const table = tableRef.value;
|
||||
const tableToolbar = tableToolbarRef.value;
|
||||
if (table && tableToolbar) {
|
||||
// TODO @puhui999:通过 nexttick 可以解决么?
|
||||
setTimeout(async () => {
|
||||
const toolbar = tableToolbar.getToolbarRef();
|
||||
if (!toolbar) {
|
||||
@@ -29,6 +31,7 @@ export function useTableToolbar() {
|
||||
() => tableRef.value,
|
||||
(val) => {
|
||||
if (!val || isBound.value) return;
|
||||
// TODO @puhui999:这里要处理下 promise 的告警么?
|
||||
bindTableToolbar();
|
||||
},
|
||||
{ immediate: true },
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
// TODO @xingyu:这个有可能 3 端复用么?想着是把 layouts 下的 components 没有这个目录哈;
|
||||
import { useVbenModal, VbenButton, VbenButtonGroup } from '@vben/common-ui';
|
||||
import { openWindow } from '@vben/utils';
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ const tenantEnable = isTenantEnable();
|
||||
const value = ref<number>(accessStore.visitTenantId ?? undefined); // 当前访问的租户 ID
|
||||
const tenants = ref<SystemTenantApi.Tenant[]>([]); // 租户列表
|
||||
|
||||
// TODO @xingyu:这个有可能 3 端复用么?
|
||||
async function handleChange(id: SelectValue) {
|
||||
// 设置访问租户 ID
|
||||
accessStore.setVisitTenantId(id as number);
|
||||
|
||||
@@ -23,7 +23,8 @@
|
||||
"cancel": "Cancel",
|
||||
"confirm": "Confirm",
|
||||
"reset": "Reset",
|
||||
"search": "Search"
|
||||
"search": "Search",
|
||||
"more": "More"
|
||||
},
|
||||
"tenant": {
|
||||
"placeholder": "Please select tenant",
|
||||
|
||||
@@ -23,7 +23,8 @@
|
||||
"cancel": "取消",
|
||||
"confirm": "确认",
|
||||
"reset": "重置",
|
||||
"search": "搜索"
|
||||
"search": "搜索",
|
||||
"more": "更多"
|
||||
},
|
||||
"tenant": {
|
||||
"placeholder": "请选择租户",
|
||||
|
||||
@@ -22,7 +22,7 @@ function setupCommonGuard(router: Router) {
|
||||
// 记录已经加载的页面
|
||||
const loadedPaths = new Set<string>();
|
||||
|
||||
router.beforeEach(async (to) => {
|
||||
router.beforeEach((to) => {
|
||||
to.meta.loaded = loadedPaths.has(to.path);
|
||||
|
||||
// 页面加载进度条
|
||||
|
||||
@@ -46,6 +46,22 @@ const routes: RouteRecordRaw[] = [
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/bpm/manager/form/edit',
|
||||
name: 'BpmFormEditor',
|
||||
component: () => import('#/views/bpm/form/editor.vue'),
|
||||
meta: {
|
||||
title: '编辑流程表单',
|
||||
activePath: '/bpm/manager/form',
|
||||
},
|
||||
props: (route) => {
|
||||
return {
|
||||
id: route.query.id,
|
||||
type: route.query.type,
|
||||
copyId: route.query.copyId,
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'manager/model/create',
|
||||
component: () => import('#/views/bpm/model/form/index.vue'),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* 针对 https://github.com/xaboy/form-create-designer 封装的工具类
|
||||
*/
|
||||
|
||||
// TODO @芋艿:后续这些 form-create 的优化;另外需要使用 form-create-helper 会好点
|
||||
import { isRef } from 'vue';
|
||||
|
||||
// 编码表单 Conf
|
||||
|
||||
143
apps/web-antd/src/views/bpm/form/data.ts
Normal file
143
apps/web-antd/src/views/bpm/form/data.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { BpmFormApi } from '#/api/bpm/form';
|
||||
|
||||
import { useAccess } from '@vben/access';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { z } from '#/adapter/form';
|
||||
import { CommonStatusEnum, DICT_TYPE, getDictOptions } from '#/utils';
|
||||
|
||||
const { hasAccessByCodes } = useAccess();
|
||||
|
||||
/** 新增/修改的表单 */
|
||||
export function useFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
fieldName: 'id',
|
||||
component: 'Input',
|
||||
dependencies: {
|
||||
triggerFields: [''],
|
||||
show: () => false,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'name',
|
||||
label: '表单名称',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: '请输入表单名称',
|
||||
},
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
fieldName: 'status',
|
||||
label: '状态',
|
||||
component: 'RadioGroup',
|
||||
componentProps: {
|
||||
options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
|
||||
buttonStyle: 'solid',
|
||||
optionType: 'button',
|
||||
},
|
||||
rules: z.number().default(CommonStatusEnum.ENABLE),
|
||||
},
|
||||
{
|
||||
fieldName: 'remark',
|
||||
label: '备注',
|
||||
component: 'Textarea',
|
||||
componentProps: {
|
||||
placeholder: '请输入备注',
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/** 列表的搜索表单 */
|
||||
export function useGridFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
fieldName: 'name',
|
||||
label: '表单名称',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: '请输入表单名称',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/** 列表的字段 */
|
||||
export function useGridColumns<T = BpmFormApi.FormVO>(
|
||||
onActionClick: OnActionClickFn<T>,
|
||||
): VxeTableGridOptions['columns'] {
|
||||
return [
|
||||
{
|
||||
field: 'id',
|
||||
title: '编号',
|
||||
minWidth: 100,
|
||||
},
|
||||
{
|
||||
field: 'name',
|
||||
title: '表单名称',
|
||||
minWidth: 200,
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
title: '状态',
|
||||
minWidth: 200,
|
||||
cellRender: {
|
||||
name: 'CellDict',
|
||||
props: { type: DICT_TYPE.COMMON_STATUS },
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'remark',
|
||||
title: '备注',
|
||||
minWidth: 200,
|
||||
},
|
||||
{
|
||||
field: 'createTime',
|
||||
title: '创建时间',
|
||||
minWidth: 180,
|
||||
formatter: 'formatDateTime',
|
||||
},
|
||||
{
|
||||
field: 'operation',
|
||||
title: '操作',
|
||||
minWidth: 150,
|
||||
align: 'center',
|
||||
fixed: 'right',
|
||||
cellRender: {
|
||||
attrs: {
|
||||
nameField: 'name',
|
||||
nameTitle: '流程名称',
|
||||
onClick: onActionClick,
|
||||
},
|
||||
name: 'CellOperation',
|
||||
options: [
|
||||
{
|
||||
code: 'copy',
|
||||
text: $t('ui.actionTitle.copy'),
|
||||
show: hasAccessByCodes(['bpm:form:update']),
|
||||
},
|
||||
{
|
||||
code: 'edit',
|
||||
text: $t('ui.actionTitle.edit'),
|
||||
show: hasAccessByCodes(['bpm:form:update']),
|
||||
},
|
||||
{
|
||||
code: 'detail',
|
||||
text: $t('ui.actionTitle.detail'),
|
||||
show: hasAccessByCodes(['bpm:form:query']),
|
||||
},
|
||||
{
|
||||
code: 'delete',
|
||||
text: $t('ui.actionTitle.delete'),
|
||||
show: hasAccessByCodes(['bpm:form:delete']),
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
159
apps/web-antd/src/views/bpm/form/editor.vue
Normal file
159
apps/web-antd/src/views/bpm/form/editor.vue
Normal file
@@ -0,0 +1,159 @@
|
||||
<script lang="ts" setup>
|
||||
// TODO @siye:editor 要不要放到独立的目录?form/designer ?
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
|
||||
import { Page, useVbenModal } from '@vben/common-ui';
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
import FcDesigner from '@form-create/antd-designer';
|
||||
import { Button, message } from 'ant-design-vue';
|
||||
|
||||
import { getFormDetail } from '#/api/bpm/form';
|
||||
import { useFormCreateDesigner } from '#/components/form-create';
|
||||
import { router } from '#/router';
|
||||
import { setConfAndFields } from '#/utils';
|
||||
|
||||
import Form from './modules/form.vue';
|
||||
|
||||
defineOptions({ name: 'BpmFormEditor' });
|
||||
|
||||
// TODO @siye:这里有 lint 告警
|
||||
const props = defineProps<Props>();
|
||||
|
||||
interface Props {
|
||||
copyId?: number;
|
||||
id?: number;
|
||||
type: 'copy' | 'create' | 'edit';
|
||||
}
|
||||
|
||||
// 流程表单详情
|
||||
const flowFormConfig = ref();
|
||||
|
||||
const [FormModal, formModalApi] = useVbenModal({
|
||||
connectedComponent: Form,
|
||||
destroyOnClose: true,
|
||||
});
|
||||
|
||||
const designerRef = ref<InstanceType<typeof FcDesigner>>();
|
||||
|
||||
// 表单设计器配置
|
||||
const designerConfig = ref({
|
||||
switchType: [], // 是否可以切换组件类型,或者可以相互切换的字段
|
||||
autoActive: true, // 是否自动选中拖入的组件
|
||||
useTemplate: false, // 是否生成vue2语法的模板组件
|
||||
formOptions: {
|
||||
form: {
|
||||
labelWidth: '100px', // 设置默认的 label 宽度为 100px
|
||||
},
|
||||
}, // 定义表单配置默认值
|
||||
fieldReadonly: false, // 配置field是否可以编辑
|
||||
hiddenDragMenu: false, // 隐藏拖拽操作按钮
|
||||
hiddenDragBtn: false, // 隐藏拖拽按钮
|
||||
hiddenMenu: [], // 隐藏部分菜单
|
||||
hiddenItem: [], // 隐藏部分组件
|
||||
hiddenItemConfig: {}, // 隐藏组件的部分配置项
|
||||
disabledItemConfig: {}, // 禁用组件的部分配置项
|
||||
showSaveBtn: false, // 是否显示保存按钮
|
||||
showConfig: true, // 是否显示右侧的配置界面
|
||||
showBaseForm: true, // 是否显示组件的基础配置表单
|
||||
showControl: true, // 是否显示组件联动
|
||||
showPropsForm: true, // 是否显示组件的属性配置表单
|
||||
showEventForm: true, // 是否显示组件的事件配置表单
|
||||
showValidateForm: true, // 是否显示组件的验证配置表单
|
||||
showFormConfig: true, // 是否显示表单配置
|
||||
showInputData: true, // 是否显示录入按钮
|
||||
showDevice: true, // 是否显示多端适配选项
|
||||
appendConfigData: [], // 定义渲染规则所需的formData
|
||||
});
|
||||
|
||||
useFormCreateDesigner(designerRef); // 表单设计器增强
|
||||
|
||||
// 计算属性:获取当前需要加载的表单ID
|
||||
const currentFormId = computed(() => {
|
||||
switch (props.type) {
|
||||
case 'copy': {
|
||||
return props.copyId;
|
||||
}
|
||||
case 'create':
|
||||
case 'edit': {
|
||||
return props.id;
|
||||
}
|
||||
default: {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 加载表单配置
|
||||
async function loadFormConfig(id: number) {
|
||||
try {
|
||||
const formDetail = await getFormDetail(id);
|
||||
flowFormConfig.value = formDetail;
|
||||
if (designerRef.value) {
|
||||
setConfAndFields(designerRef, formDetail.conf, formDetail.fields);
|
||||
}
|
||||
} catch {
|
||||
message.error('加载表单配置失败');
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化设计器
|
||||
async function initializeDesigner() {
|
||||
const id = currentFormId.value;
|
||||
|
||||
if (props.type === 'copy' && !id) {
|
||||
message.error('复制ID不能为空');
|
||||
return;
|
||||
}
|
||||
|
||||
if (id) {
|
||||
await loadFormConfig(id);
|
||||
}
|
||||
}
|
||||
|
||||
function handleSave() {
|
||||
formModalApi
|
||||
.setData({
|
||||
designer: designerRef.value,
|
||||
formConfig: flowFormConfig.value,
|
||||
action: props.type,
|
||||
})
|
||||
.open();
|
||||
}
|
||||
|
||||
// TODO @siye:一些必要的注释,稍微写写哈。保持风格的一致性;
|
||||
function onBack() {
|
||||
router.push({
|
||||
path: '/bpm/manager/form',
|
||||
query: {
|
||||
refresh: '1',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initializeDesigner();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page auto-content-height>
|
||||
<FormModal @success="onBack" />
|
||||
|
||||
<FcDesigner class="my-designer" ref="designerRef" :config="designerConfig">
|
||||
<template #handle>
|
||||
<Button size="small" type="primary" @click="handleSave">
|
||||
<IconifyIcon icon="mdi:content-save" />
|
||||
保存
|
||||
</Button>
|
||||
</template>
|
||||
</FcDesigner>
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.my-designer {
|
||||
height: 100%;
|
||||
min-height: 500px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,34 +1,216 @@
|
||||
<script lang="ts" setup>
|
||||
import { Page } from '@vben/common-ui';
|
||||
import type {
|
||||
OnActionClickParams,
|
||||
VxeTableGridOptions,
|
||||
} from '#/adapter/vxe-table';
|
||||
import type { BpmFormApi } from '#/api/bpm/form';
|
||||
|
||||
import { Button } from 'ant-design-vue';
|
||||
import { ref, watch } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
import { Page, useVbenModal } from '@vben/common-ui';
|
||||
import { Plus } from '@vben/icons';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { Button, message } from 'ant-design-vue';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { deleteForm, getFormDetail, getFormPage } from '#/api/bpm/form';
|
||||
import { DocAlert } from '#/components/doc-alert';
|
||||
import { router } from '#/router';
|
||||
import { setConfAndFields2 } from '#/utils';
|
||||
|
||||
import { useGridColumns, useGridFormSchema } from './data';
|
||||
|
||||
defineOptions({ name: 'BpmForm' });
|
||||
|
||||
const [Grid, gridApi] = useVbenVxeGrid({
|
||||
formOptions: {
|
||||
schema: useGridFormSchema(),
|
||||
},
|
||||
gridOptions: {
|
||||
columns: useGridColumns(onActionClick),
|
||||
height: 'auto',
|
||||
keepSource: true,
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
query: async ({ page }, formValues) => {
|
||||
return await getFormPage({
|
||||
pageNo: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
...formValues,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
rowConfig: {
|
||||
keyField: 'id',
|
||||
},
|
||||
toolbarConfig: {
|
||||
refresh: { code: 'query' },
|
||||
search: true,
|
||||
},
|
||||
cellConfig: {
|
||||
height: 64,
|
||||
},
|
||||
} as VxeTableGridOptions<BpmFormApi.FormVO>,
|
||||
});
|
||||
|
||||
/** 表格操作按钮的回调函数 */
|
||||
function onActionClick({ code, row }: OnActionClickParams<BpmFormApi.FormVO>) {
|
||||
switch (code) {
|
||||
case 'copy': {
|
||||
onCopy(row);
|
||||
break;
|
||||
}
|
||||
case 'delete': {
|
||||
onDelete(row);
|
||||
break;
|
||||
}
|
||||
case 'detail': {
|
||||
onDetail(row);
|
||||
break;
|
||||
}
|
||||
case 'edit': {
|
||||
onEdit(row);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
/** 复制 */
|
||||
function onCopy(row: BpmFormApi.FormVO) {
|
||||
router.push({
|
||||
name: 'BpmFormEditor',
|
||||
query: {
|
||||
copyId: row.id,
|
||||
type: 'copy',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/** 删除 */
|
||||
async function onDelete(row: BpmFormApi.FormVO) {
|
||||
const hideLoading = message.loading({
|
||||
content: $t('ui.actionMessage.deleting', [row.id]),
|
||||
duration: 0,
|
||||
key: 'action_process_msg',
|
||||
});
|
||||
try {
|
||||
await deleteForm(row.id as number);
|
||||
message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
|
||||
onRefresh();
|
||||
} finally {
|
||||
hideLoading();
|
||||
}
|
||||
}
|
||||
|
||||
/** 详情 */
|
||||
const formConfig = ref<any>({});
|
||||
async function onDetail(row: BpmFormApi.FormVO) {
|
||||
formConfig.value = await getFormDetail(row.id as number);
|
||||
|
||||
setConfAndFields2(
|
||||
formConfig.value,
|
||||
formConfig.value.conf,
|
||||
formConfig.value.fields,
|
||||
);
|
||||
detailModalApi.open();
|
||||
}
|
||||
|
||||
/** 编辑 */
|
||||
function onEdit(row: BpmFormApi.FormVO) {
|
||||
console.warn(row);
|
||||
router.push({
|
||||
name: 'BpmFormEditor',
|
||||
query: {
|
||||
id: row.id,
|
||||
type: 'edit',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/** 刷新表格 */
|
||||
function onRefresh() {
|
||||
gridApi.query();
|
||||
}
|
||||
|
||||
/** 新增 */
|
||||
function onCreate() {
|
||||
router.push({
|
||||
name: 'BpmFormEditor',
|
||||
query: {
|
||||
type: 'create',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/** 详情弹窗 */
|
||||
const [DetailModal, detailModalApi] = useVbenModal({
|
||||
destroyOnClose: true,
|
||||
footer: false,
|
||||
});
|
||||
|
||||
/** 检测路由参数 */
|
||||
const route = useRoute();
|
||||
watch(
|
||||
() => route.query.refresh,
|
||||
(val) => {
|
||||
if (val === '1') {
|
||||
onRefresh();
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page>
|
||||
<Page auto-content-height>
|
||||
<DocAlert
|
||||
title="审批接入(流程表单)"
|
||||
url="https://doc.iocoder.cn/bpm/use-bpm-form/"
|
||||
/>
|
||||
<Button
|
||||
danger
|
||||
type="link"
|
||||
target="_blank"
|
||||
href="https://github.com/yudaocode/yudao-ui-admin-vue3"
|
||||
<FormModal @success="onRefresh" />
|
||||
<Grid table-title="流程表单">
|
||||
<template #toolbar-tools>
|
||||
<Button type="primary" @click="onCreate">
|
||||
<Plus class="size-5" />
|
||||
{{ $t('ui.actionTitle.create', ['流程表单']) }}
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
<!-- 摘要 -->
|
||||
<!-- TODO @siye:这个是不是不应该有呀? -->
|
||||
<template #slot-summary="{ row }">
|
||||
<div
|
||||
class="flex flex-col py-2"
|
||||
v-if="
|
||||
row.processInstance.summary &&
|
||||
row.processInstance.summary.length > 0
|
||||
"
|
||||
>
|
||||
<div
|
||||
v-for="(item, index) in row.processInstance.summary"
|
||||
:key="index"
|
||||
>
|
||||
<span class="text-gray-500">
|
||||
{{ item.key }} : {{ item.value }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>-</div>
|
||||
</template>
|
||||
</Grid>
|
||||
|
||||
<DetailModal
|
||||
title="流程表单详情"
|
||||
class="w-[800px]"
|
||||
:body-style="{
|
||||
maxHeight: '100px',
|
||||
}"
|
||||
>
|
||||
该功能支持 Vue3 + element-plus 版本!
|
||||
</Button>
|
||||
<br />
|
||||
<Button
|
||||
type="link"
|
||||
target="_blank"
|
||||
href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/form/index"
|
||||
>
|
||||
可参考
|
||||
https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/form/index
|
||||
代码,pull request 贡献给我们!
|
||||
</Button>
|
||||
<div class="mx-4">
|
||||
<form-create :option="formConfig.option" :rule="formConfig.rule" />
|
||||
</div>
|
||||
</DetailModal>
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
110
apps/web-antd/src/views/bpm/form/modules/form.vue
Normal file
110
apps/web-antd/src/views/bpm/form/modules/form.vue
Normal file
@@ -0,0 +1,110 @@
|
||||
<script lang="ts" setup>
|
||||
import type { FcDesigner } from '@form-create/antd-designer';
|
||||
|
||||
import type { BpmFormApi } from '#/api/bpm/form';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
import { createForm, updateForm } from '#/api/bpm/form';
|
||||
import { $t } from '#/locales';
|
||||
import { encodeConf, encodeFields } from '#/utils';
|
||||
|
||||
import { useFormSchema } from '../data';
|
||||
|
||||
const emit = defineEmits(['success']);
|
||||
|
||||
const designerComponent = ref<InstanceType<typeof FcDesigner>>();
|
||||
const formData = ref<BpmFormApi.FormVO>();
|
||||
const editorAction = ref<string>();
|
||||
|
||||
const getTitle = computed(() => {
|
||||
if (!formData.value?.id) {
|
||||
return $t('ui.actionTitle.create', ['流程表单']);
|
||||
}
|
||||
return editorAction.value === 'copy'
|
||||
? $t('ui.actionTitle.copy', ['流程表单'])
|
||||
: $t('ui.actionTitle.edit', ['流程表单']);
|
||||
});
|
||||
|
||||
const [Form, formApi] = useVbenForm({
|
||||
layout: 'horizontal',
|
||||
schema: useFormSchema(),
|
||||
showDefaultActions: false,
|
||||
});
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
async onConfirm() {
|
||||
// TODO @siye:建议和别的模块,也稍微加点类似的注释哈。= = 阅读总是会有点层次感;
|
||||
const { valid } = await formApi.validate();
|
||||
if (!valid) return;
|
||||
|
||||
modalApi.lock();
|
||||
try {
|
||||
const data = (await formApi.getValues()) as BpmFormApi.FormVO;
|
||||
|
||||
data.conf = encodeConf(designerComponent);
|
||||
data.fields = encodeFields(designerComponent);
|
||||
|
||||
// TODO @siye:这个是不是不用抽方法呀,直接写逻辑就完事啦。
|
||||
const saveForm = async () => {
|
||||
if (!formData.value?.id) {
|
||||
return createForm(data);
|
||||
}
|
||||
return editorAction.value === 'copy'
|
||||
? createForm(data)
|
||||
: updateForm(data);
|
||||
};
|
||||
|
||||
await saveForm();
|
||||
await modalApi.close();
|
||||
emit('success');
|
||||
message.success($t('ui.actionMessage.operationSuccess'));
|
||||
} finally {
|
||||
modalApi.unlock();
|
||||
}
|
||||
},
|
||||
|
||||
async onOpenChange(isOpen: boolean) {
|
||||
if (!isOpen) {
|
||||
formData.value = undefined;
|
||||
designerComponent.value = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO @siye:建议和别的模块,也稍微加点类似的注释哈。= = 阅读总是会有点层次感;
|
||||
const data = modalApi.getData<any>();
|
||||
if (!data) return;
|
||||
|
||||
designerComponent.value = data.designer;
|
||||
formData.value = data.formConfig;
|
||||
editorAction.value = data.action;
|
||||
|
||||
if (editorAction.value === 'copy' && formData.value) {
|
||||
formData.value = {
|
||||
...formData.value,
|
||||
name: `${formData.value.name}_copy`,
|
||||
id: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
if (formData.value) {
|
||||
await formApi.setValues(formData.value);
|
||||
}
|
||||
} finally {
|
||||
modalApi.unlock();
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal :title="getTitle" class="w-[600px]">
|
||||
<Form class="mx-4" />
|
||||
</Modal>
|
||||
</template>
|
||||
@@ -42,6 +42,21 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||
...formValues,
|
||||
});
|
||||
},
|
||||
querySuccess: (params) => {
|
||||
// TODO @siye:getLeaderName?: (userId: number) => string | undefined, 参考这个哈。
|
||||
const { list } = params.response;
|
||||
const userMap = new Map(
|
||||
userList.value.map((user) => [user.id, user.nickname]),
|
||||
);
|
||||
list.forEach(
|
||||
(item: BpmUserGroupApi.UserGroupVO & { nicknames?: string }) => {
|
||||
item.nicknames = item.userIds
|
||||
.map((userId) => userMap.get(userId))
|
||||
.filter(Boolean)
|
||||
.join('、');
|
||||
},
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
rowConfig: {
|
||||
@@ -127,14 +142,7 @@ onMounted(async () => {
|
||||
</template>
|
||||
|
||||
<template #userIds-cell="{ row }">
|
||||
<span
|
||||
v-for="(userId, index) in row.userIds"
|
||||
:key="userId"
|
||||
class="pr-5px"
|
||||
>
|
||||
{{ userList.find((user) => user.id === userId)?.nickname }}
|
||||
<span v-if="index < row.userIds.length - 1">、</span>
|
||||
</span>
|
||||
<span>{{ row.nicknames }}</span>
|
||||
</template>
|
||||
</Grid>
|
||||
</Page>
|
||||
|
||||
@@ -8,6 +8,7 @@ import { z } from '#/adapter/form';
|
||||
import { CommonStatusEnum, DICT_TYPE, getDictOptions } from '#/utils';
|
||||
|
||||
const { hasAccessByCodes } = useAccess();
|
||||
|
||||
/** 新增/修改的表单 */
|
||||
export function useFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
|
||||
@@ -269,13 +269,14 @@ onMounted(() => {
|
||||
<img
|
||||
v-if="definition.icon"
|
||||
:src="definition.icon"
|
||||
class="h-12 w-12 object-contain"
|
||||
class="flow-icon-img object-contain"
|
||||
alt="流程图标"
|
||||
/>
|
||||
|
||||
<div v-else class="flow-icon flex-shrink-0">
|
||||
<Tooltip :title="definition.name">
|
||||
<span class="text-xs text-white">
|
||||
{{ definition.name }}
|
||||
{{ definition.name?.slice(0, 2) }}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
@@ -324,19 +325,26 @@ onMounted(() => {
|
||||
<style lang="scss" scoped>
|
||||
.process-definition-container {
|
||||
.definition-item-card {
|
||||
.flow-icon-img {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
.flow-icon {
|
||||
@apply bg-primary;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background-color: #3f73f7;
|
||||
border-radius: 50%;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
&.search-match {
|
||||
background-color: rgb(63 115 247 / 10%);
|
||||
border: 1px solid #3f73f7;
|
||||
border: 1px solid var(--primary);
|
||||
animation: bounce 0.5s ease;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,6 @@ import type { SystemUserApi } from '#/api/system/user';
|
||||
import { nextTick, onMounted, ref } from 'vue';
|
||||
|
||||
import { Page } from '@vben/common-ui';
|
||||
import {
|
||||
SvgBpmApproveIcon,
|
||||
SvgBpmCancelIcon,
|
||||
SvgBpmRejectIcon,
|
||||
SvgBpmRunningIcon,
|
||||
} from '@vben/icons';
|
||||
import { formatDateTime } from '@vben/utils';
|
||||
|
||||
import {
|
||||
@@ -38,6 +32,12 @@ import {
|
||||
registerComponent,
|
||||
setConfAndFields2,
|
||||
} from '#/utils';
|
||||
import {
|
||||
SvgBpmApproveIcon,
|
||||
SvgBpmCancelIcon,
|
||||
SvgBpmRejectIcon,
|
||||
SvgBpmRunningIcon,
|
||||
} from '#/views/bpm/processInstance/detail/modules/icons';
|
||||
|
||||
import ProcessInstanceTimeline from './modules/time-line.vue';
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { createIconifyIcon } from '@vben/icons';
|
||||
|
||||
// bpm 图标
|
||||
// TODO @siye:可以新建出一个 bpm 目录哇?icons/bpm;
|
||||
const SvgBpmRunningIcon = createIconifyIcon('svg:bpm-running');
|
||||
const SvgBpmApproveIcon = createIconifyIcon('svg:bpm-approve');
|
||||
const SvgBpmRejectIcon = createIconifyIcon('svg:bpm-reject');
|
||||
const SvgBpmCancelIcon = createIconifyIcon('svg:bpm-cancel');
|
||||
|
||||
export {
|
||||
SvgBpmApproveIcon,
|
||||
SvgBpmCancelIcon,
|
||||
SvgBpmRejectIcon,
|
||||
SvgBpmRunningIcon,
|
||||
};
|
||||
@@ -8,7 +8,7 @@ import { useRouter } from 'vue-router';
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
import { formatDateTime, isEmpty } from '@vben/utils';
|
||||
|
||||
import { Avatar, Button, Image, Tooltip } from 'ant-design-vue';
|
||||
import { Avatar, Button, Image, Timeline, Tooltip } from 'ant-design-vue';
|
||||
|
||||
import { UserSelectModal } from '#/components/user-select-modal';
|
||||
import {
|
||||
@@ -217,9 +217,9 @@ const handleUserSelectCancel = () => {
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<a-timeline class="pt-20px">
|
||||
<Timeline class="pt-20px">
|
||||
<!-- 遍历每个审批节点 -->
|
||||
<a-timeline-item
|
||||
<Timeline.Item
|
||||
v-for="(activity, index) in activityNodes"
|
||||
:key="index"
|
||||
:color="getApprovalNodeColor(activity.status)"
|
||||
@@ -448,8 +448,8 @@ const handleUserSelectCancel = () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-timeline-item>
|
||||
</a-timeline>
|
||||
</Timeline.Item>
|
||||
</Timeline>
|
||||
|
||||
<!-- 用户选择弹窗 -->
|
||||
<UserSelectModal
|
||||
|
||||
@@ -23,7 +23,7 @@ import { BpmProcessInstanceStatus, DICT_TYPE } from '#/utils';
|
||||
|
||||
import { useGridColumns, useGridFormSchema } from './data';
|
||||
|
||||
defineOptions({ name: 'BpmProcessInstanceManager' });
|
||||
defineOptions({ name: 'BpmProcessInstanceMy' });
|
||||
|
||||
const [Grid, gridApi] = useVbenVxeGrid({
|
||||
formOptions: {
|
||||
|
||||
111
apps/web-antd/src/views/bpm/task/copy/data.ts
Normal file
111
apps/web-antd/src/views/bpm/task/copy/data.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { BpmTaskApi } from '#/api/bpm/task';
|
||||
|
||||
import { useAccess } from '@vben/access';
|
||||
|
||||
import { getRangePickerDefaultProps } from '#/utils';
|
||||
|
||||
const { hasAccessByCodes } = useAccess();
|
||||
|
||||
/** 列表的搜索表单 */
|
||||
export function useGridFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
fieldName: 'name',
|
||||
label: '流程名称',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: '请输入流程名称',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'createTime',
|
||||
label: '抄送时间',
|
||||
component: 'RangePicker',
|
||||
componentProps: {
|
||||
...getRangePickerDefaultProps(),
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/** 列表的字段 */
|
||||
export function useGridColumns<T = BpmTaskApi.TaskVO>(
|
||||
onActionClick: OnActionClickFn<T>,
|
||||
): VxeTableGridOptions['columns'] {
|
||||
return [
|
||||
{
|
||||
field: 'processInstanceName',
|
||||
title: '流程名称',
|
||||
minWidth: 200,
|
||||
fixed: 'left',
|
||||
},
|
||||
{
|
||||
field: 'summary',
|
||||
title: '摘要',
|
||||
minWidth: 200,
|
||||
slots: {
|
||||
default: 'slot-summary',
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'startUser.nickname',
|
||||
title: '流程发起人',
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
field: 'processInstanceStartTime',
|
||||
title: '流程发起时间',
|
||||
minWidth: 180,
|
||||
formatter: 'formatDateTime',
|
||||
},
|
||||
{
|
||||
field: 'activityName',
|
||||
title: '抄送节点',
|
||||
minWidth: 180,
|
||||
},
|
||||
{
|
||||
field: 'createUser.nickname',
|
||||
title: '抄送人',
|
||||
minWidth: 180,
|
||||
slots: {
|
||||
default: 'slot-createUser',
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'reason',
|
||||
title: '抄送意见',
|
||||
minWidth: 180,
|
||||
},
|
||||
{
|
||||
field: 'createTime',
|
||||
title: '抄送时间',
|
||||
minWidth: 180,
|
||||
formatter: 'formatDateTime',
|
||||
},
|
||||
{
|
||||
field: 'operation',
|
||||
title: '操作',
|
||||
minWidth: 120,
|
||||
align: 'center',
|
||||
fixed: 'right',
|
||||
cellRender: {
|
||||
attrs: {
|
||||
nameField: 'name',
|
||||
nameTitle: '流程名称',
|
||||
onClick: onActionClick,
|
||||
},
|
||||
name: 'CellOperation',
|
||||
options: [
|
||||
{
|
||||
code: 'detail',
|
||||
text: '详情',
|
||||
show: hasAccessByCodes(['bpm:task:query']),
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -1,34 +1,115 @@
|
||||
<script lang="ts" setup>
|
||||
import type {
|
||||
OnActionClickParams,
|
||||
VxeTableGridOptions,
|
||||
} from '#/adapter/vxe-table';
|
||||
import type { BpmProcessInstanceApi } from '#/api/bpm/processInstance';
|
||||
|
||||
import { Page } from '@vben/common-ui';
|
||||
|
||||
import { Button } from 'ant-design-vue';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { getProcessInstanceCopyPage } from '#/api/bpm/processInstance';
|
||||
import { DocAlert } from '#/components/doc-alert';
|
||||
import { router } from '#/router';
|
||||
|
||||
import { useGridColumns, useGridFormSchema } from './data';
|
||||
|
||||
defineOptions({ name: 'BpmCopyTask' });
|
||||
|
||||
const [Grid, gridApi] = useVbenVxeGrid({
|
||||
formOptions: {
|
||||
schema: useGridFormSchema(),
|
||||
},
|
||||
gridOptions: {
|
||||
columns: useGridColumns(onActionClick),
|
||||
height: 'auto',
|
||||
keepSource: true,
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
query: async ({ page }, formValues) => {
|
||||
return await getProcessInstanceCopyPage({
|
||||
pageNo: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
...formValues,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
rowConfig: {
|
||||
keyField: 'id',
|
||||
},
|
||||
toolbarConfig: {
|
||||
refresh: { code: 'query' },
|
||||
search: true,
|
||||
},
|
||||
cellConfig: {
|
||||
height: 64,
|
||||
},
|
||||
} as VxeTableGridOptions<BpmProcessInstanceApi.ProcessInstanceVO>,
|
||||
});
|
||||
|
||||
/** 表格操作按钮的回调函数 */
|
||||
function onActionClick({
|
||||
code,
|
||||
row,
|
||||
}: OnActionClickParams<BpmProcessInstanceApi.ProcessInstanceVO>) {
|
||||
switch (code) {
|
||||
case 'detail': {
|
||||
onDetail(row);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 办理任务 */
|
||||
function onDetail(row: BpmProcessInstanceApi.ProcessInstanceVO) {
|
||||
// TODO @siye:row 的类型是不是不对哈?需要改成 copyvo 么?
|
||||
const query = {
|
||||
id: row.processInstanceId,
|
||||
...(row.activityId && { activityId: row.activityId }),
|
||||
};
|
||||
router.push({
|
||||
name: 'BpmProcessInstanceDetail',
|
||||
query,
|
||||
});
|
||||
}
|
||||
|
||||
/** 刷新表格 */
|
||||
function onRefresh() {
|
||||
gridApi.query();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page>
|
||||
<Page auto-content-height>
|
||||
<!-- TODO @siye:应该用 <template #doc>,这样高度可以被用进去哈 -->
|
||||
<DocAlert
|
||||
title="审批转办、委派、抄送"
|
||||
url="https://doc.iocoder.cn/bpm/task-delegation-and-cc/"
|
||||
/>
|
||||
<Button
|
||||
danger
|
||||
type="link"
|
||||
target="_blank"
|
||||
href="https://github.com/yudaocode/yudao-ui-admin-vue3"
|
||||
>
|
||||
该功能支持 Vue3 + element-plus 版本!
|
||||
</Button>
|
||||
<br />
|
||||
<Button
|
||||
type="link"
|
||||
target="_blank"
|
||||
href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/task/copy/index"
|
||||
>
|
||||
可参考
|
||||
https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/task/copy/index
|
||||
代码,pull request 贡献给我们!
|
||||
</Button>
|
||||
|
||||
<FormModal @success="onRefresh" />
|
||||
<Grid table-title="抄送任务">
|
||||
<!-- 摘要 -->
|
||||
<template #slot-summary="{ row }">
|
||||
<div
|
||||
class="flex flex-col py-2"
|
||||
v-if="row.summary && row.summary.length > 0"
|
||||
>
|
||||
<div v-for="(item, index) in row.summary" :key="index">
|
||||
<span class="text-gray-500">
|
||||
{{ item.key }} : {{ item.value }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>-</div>
|
||||
</template>
|
||||
<!-- 抄送人 -->
|
||||
<template #slot-createUser="{ row }">
|
||||
<span class="text-gray-500">
|
||||
{{ row.createUser.nickname || '系统' }}
|
||||
</span>
|
||||
</template>
|
||||
</Grid>
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
177
apps/web-antd/src/views/bpm/task/done/data.ts
Normal file
177
apps/web-antd/src/views/bpm/task/done/data.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { BpmTaskApi } from '#/api/bpm/task';
|
||||
|
||||
import { useAccess } from '@vben/access';
|
||||
|
||||
import { getCategorySimpleList } from '#/api/bpm/category';
|
||||
import {
|
||||
DICT_TYPE,
|
||||
formatPast2,
|
||||
getDictOptions,
|
||||
getRangePickerDefaultProps,
|
||||
} from '#/utils';
|
||||
|
||||
// TODO @siye:这个要去掉么?没用到
|
||||
const { hasAccessByCodes } = useAccess();
|
||||
|
||||
/** 列表的搜索表单 */
|
||||
export function useGridFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
fieldName: 'name',
|
||||
label: '流程名称',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: '请输入流程名称',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'processDefinitionId',
|
||||
label: '所属流程',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: '请输入流程定义的编号',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'category',
|
||||
label: '流程分类',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
placeholder: '请输入流程分类',
|
||||
allowClear: true,
|
||||
api: getCategorySimpleList,
|
||||
labelField: 'name',
|
||||
valueField: 'code',
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'status',
|
||||
label: '流程状态',
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
options: getDictOptions(
|
||||
DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS,
|
||||
'number',
|
||||
),
|
||||
placeholder: '请选择流程状态',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
// 发起时间
|
||||
{
|
||||
fieldName: 'createTime',
|
||||
label: '发起时间',
|
||||
component: 'RangePicker',
|
||||
componentProps: {
|
||||
...getRangePickerDefaultProps(),
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/** 列表的字段 */
|
||||
export function useGridColumns<T = BpmTaskApi.TaskVO>(
|
||||
onActionClick: OnActionClickFn<T>,
|
||||
): VxeTableGridOptions['columns'] {
|
||||
return [
|
||||
{
|
||||
field: 'name',
|
||||
title: '流程名称',
|
||||
minWidth: 200,
|
||||
fixed: 'left',
|
||||
},
|
||||
{
|
||||
field: 'processInstance.summary',
|
||||
title: '摘要',
|
||||
minWidth: 200,
|
||||
slots: {
|
||||
default: 'slot-summary',
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'processInstance.startUser.nickname',
|
||||
title: '发起人',
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
field: 'createTime',
|
||||
title: '发起时间',
|
||||
minWidth: 180,
|
||||
formatter: 'formatDateTime',
|
||||
},
|
||||
{
|
||||
field: 'name',
|
||||
title: '当前任务',
|
||||
minWidth: 180,
|
||||
},
|
||||
{
|
||||
field: 'createTime',
|
||||
title: '任务开始时间',
|
||||
minWidth: 180,
|
||||
formatter: 'formatDateTime',
|
||||
},
|
||||
{
|
||||
field: 'endTime',
|
||||
title: '任务结束时间',
|
||||
minWidth: 180,
|
||||
formatter: 'formatDateTime',
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
title: '审批状态',
|
||||
minWidth: 180,
|
||||
cellRender: {
|
||||
name: 'CellDict',
|
||||
props: { type: DICT_TYPE.BPM_TASK_STATUS },
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'reason',
|
||||
title: '审批建议',
|
||||
minWidth: 180,
|
||||
},
|
||||
{
|
||||
field: 'durationInMillis',
|
||||
title: '耗时',
|
||||
minWidth: 180,
|
||||
formatter: ({ row }) => {
|
||||
return `${formatPast2(row.durationInMillis)}`;
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'processInstanceId',
|
||||
title: '流程编号',
|
||||
minWidth: 280,
|
||||
},
|
||||
{
|
||||
field: 'id',
|
||||
title: '任务编号',
|
||||
minWidth: 280,
|
||||
},
|
||||
{
|
||||
field: 'operation',
|
||||
title: '操作',
|
||||
minWidth: 120,
|
||||
align: 'center',
|
||||
fixed: 'right',
|
||||
cellRender: {
|
||||
attrs: {
|
||||
nameField: 'name',
|
||||
nameTitle: '流程名称',
|
||||
onClick: onActionClick,
|
||||
},
|
||||
name: 'CellOperation',
|
||||
options: [
|
||||
{
|
||||
code: 'history',
|
||||
text: '历史',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -1,13 +1,83 @@
|
||||
<script lang="ts" setup>
|
||||
import type {
|
||||
OnActionClickParams,
|
||||
VxeTableGridOptions,
|
||||
} from '#/adapter/vxe-table';
|
||||
import type { BpmTaskApi } from '#/api/bpm/task';
|
||||
|
||||
import { Page } from '@vben/common-ui';
|
||||
|
||||
import { Button } from 'ant-design-vue';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { getTaskDonePage } from '#/api/bpm/task';
|
||||
import { DocAlert } from '#/components/doc-alert';
|
||||
import { router } from '#/router';
|
||||
|
||||
import { useGridColumns, useGridFormSchema } from './data';
|
||||
|
||||
defineOptions({ name: 'BpmDoneTask' });
|
||||
|
||||
const [Grid, gridApi] = useVbenVxeGrid({
|
||||
formOptions: {
|
||||
schema: useGridFormSchema(),
|
||||
},
|
||||
gridOptions: {
|
||||
columns: useGridColumns(onActionClick),
|
||||
height: 'auto',
|
||||
keepSource: true,
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
query: async ({ page }, formValues) => {
|
||||
return await getTaskDonePage({
|
||||
pageNo: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
...formValues,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
rowConfig: {
|
||||
keyField: 'id',
|
||||
},
|
||||
toolbarConfig: {
|
||||
refresh: { code: 'query' },
|
||||
search: true,
|
||||
},
|
||||
cellConfig: {
|
||||
height: 64,
|
||||
},
|
||||
} as VxeTableGridOptions<BpmTaskApi.TaskVO>,
|
||||
});
|
||||
|
||||
/** 表格操作按钮的回调函数 */
|
||||
function onActionClick({ code, row }: OnActionClickParams<BpmTaskApi.TaskVO>) {
|
||||
switch (code) {
|
||||
case 'history': {
|
||||
onHistory(row);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 查看历史 */
|
||||
function onHistory(row: BpmTaskApi.TaskVO) {
|
||||
console.warn(row);
|
||||
router.push({
|
||||
name: 'BpmProcessInstanceDetail',
|
||||
query: {
|
||||
id: row.processInstance.id,
|
||||
taskId: row.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/** 刷新表格 */
|
||||
function onRefresh() {
|
||||
gridApi.query();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page>
|
||||
<Page auto-content-height>
|
||||
<DocAlert
|
||||
title="审批通过、不通过、驳回"
|
||||
url="https://doc.iocoder.cn/bpm/task-todo-done/"
|
||||
@@ -18,23 +88,29 @@ import { DocAlert } from '#/components/doc-alert';
|
||||
url="https://doc.iocoder.cn/bpm/task-delegation-and-cc/"
|
||||
/>
|
||||
<DocAlert title="审批加签、减签" url="https://doc.iocoder.cn/bpm/sign/" />
|
||||
<Button
|
||||
danger
|
||||
type="link"
|
||||
target="_blank"
|
||||
href="https://github.com/yudaocode/yudao-ui-admin-vue3"
|
||||
>
|
||||
该功能支持 Vue3 + element-plus 版本!
|
||||
</Button>
|
||||
<br />
|
||||
<Button
|
||||
type="link"
|
||||
target="_blank"
|
||||
href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/task/done/index"
|
||||
>
|
||||
可参考
|
||||
https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/task/done/index
|
||||
代码,pull request 贡献给我们!
|
||||
</Button>
|
||||
|
||||
<FormModal @success="onRefresh" />
|
||||
<Grid table-title="已办任务">
|
||||
<!-- 摘要 -->
|
||||
<template #slot-summary="{ row }">
|
||||
<div
|
||||
class="flex flex-col py-2"
|
||||
v-if="
|
||||
row.processInstance.summary &&
|
||||
row.processInstance.summary.length > 0
|
||||
"
|
||||
>
|
||||
<div
|
||||
v-for="(item, index) in row.processInstance.summary"
|
||||
:key="index"
|
||||
>
|
||||
<span class="text-gray-500">
|
||||
{{ item.key }} : {{ item.value }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>-</div>
|
||||
</template>
|
||||
</Grid>
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
136
apps/web-antd/src/views/bpm/task/manager/data.ts
Normal file
136
apps/web-antd/src/views/bpm/task/manager/data.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { BpmTaskApi } from '#/api/bpm/task';
|
||||
|
||||
import { DICT_TYPE, formatPast2, getRangePickerDefaultProps } from '#/utils';
|
||||
|
||||
/** 列表的搜索表单 */
|
||||
export function useGridFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
fieldName: 'name',
|
||||
label: '任务名称',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: '请输入任务名称',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'createTime',
|
||||
label: '创建时间',
|
||||
component: 'RangePicker',
|
||||
componentProps: {
|
||||
...getRangePickerDefaultProps(),
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/** 列表的字段 */
|
||||
export function useGridColumns<T = BpmTaskApi.TaskVO>(
|
||||
onActionClick: OnActionClickFn<T>,
|
||||
): VxeTableGridOptions['columns'] {
|
||||
return [
|
||||
{
|
||||
field: 'name',
|
||||
title: '流程名称',
|
||||
minWidth: 200,
|
||||
fixed: 'left',
|
||||
},
|
||||
{
|
||||
field: 'processInstance.summary',
|
||||
title: '摘要',
|
||||
minWidth: 200,
|
||||
slots: {
|
||||
default: 'slot-summary',
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'processInstance.startUser.nickname',
|
||||
title: '发起人',
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
field: 'createTime',
|
||||
title: '发起时间',
|
||||
minWidth: 180,
|
||||
formatter: 'formatDateTime',
|
||||
},
|
||||
{
|
||||
field: 'name',
|
||||
title: '当前任务',
|
||||
minWidth: 180,
|
||||
},
|
||||
{
|
||||
field: 'createTime',
|
||||
title: '任务开始时间',
|
||||
minWidth: 180,
|
||||
formatter: 'formatDateTime',
|
||||
},
|
||||
{
|
||||
field: 'endTime',
|
||||
title: '任务结束时间',
|
||||
minWidth: 180,
|
||||
formatter: 'formatDateTime',
|
||||
},
|
||||
{
|
||||
field: 'assigneeUser.nickname',
|
||||
title: '审批人',
|
||||
minWidth: 180,
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
title: '审批状态',
|
||||
minWidth: 180,
|
||||
cellRender: {
|
||||
name: 'CellDict',
|
||||
props: { type: DICT_TYPE.BPM_TASK_STATUS },
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'reason',
|
||||
title: '审批建议',
|
||||
minWidth: 180,
|
||||
},
|
||||
{
|
||||
field: 'durationInMillis',
|
||||
title: '耗时',
|
||||
minWidth: 180,
|
||||
formatter: ({ row }) => {
|
||||
return `${formatPast2(row.durationInMillis)}`;
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'processInstanceId',
|
||||
title: '流程编号',
|
||||
minWidth: 280,
|
||||
},
|
||||
{
|
||||
field: 'id',
|
||||
title: '任务编号',
|
||||
minWidth: 280,
|
||||
},
|
||||
{
|
||||
field: 'operation',
|
||||
title: '操作',
|
||||
minWidth: 120,
|
||||
align: 'center',
|
||||
fixed: 'right',
|
||||
cellRender: {
|
||||
attrs: {
|
||||
nameField: 'name',
|
||||
nameTitle: '流程名称',
|
||||
onClick: onActionClick,
|
||||
},
|
||||
name: 'CellOperation',
|
||||
options: [
|
||||
{
|
||||
code: 'history',
|
||||
text: '历史',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -1,31 +1,108 @@
|
||||
<script lang="ts" setup>
|
||||
import type {
|
||||
OnActionClickParams,
|
||||
VxeTableGridOptions,
|
||||
} from '#/adapter/vxe-table';
|
||||
import type { BpmTaskApi } from '#/api/bpm/task';
|
||||
|
||||
import { Page } from '@vben/common-ui';
|
||||
|
||||
import { Button } from 'ant-design-vue';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { getTaskManagerPage } from '#/api/bpm/task';
|
||||
import { DocAlert } from '#/components/doc-alert';
|
||||
import { router } from '#/router';
|
||||
|
||||
import { useGridColumns, useGridFormSchema } from './data';
|
||||
|
||||
defineOptions({ name: 'BpmManagerTask' });
|
||||
|
||||
const [Grid, gridApi] = useVbenVxeGrid({
|
||||
formOptions: {
|
||||
schema: useGridFormSchema(),
|
||||
},
|
||||
gridOptions: {
|
||||
columns: useGridColumns(onActionClick),
|
||||
height: 'auto',
|
||||
keepSource: true,
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
query: async ({ page }, formValues) => {
|
||||
return await getTaskManagerPage({
|
||||
pageNo: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
...formValues,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
rowConfig: {
|
||||
keyField: 'id',
|
||||
},
|
||||
toolbarConfig: {
|
||||
refresh: { code: 'query' },
|
||||
search: true,
|
||||
},
|
||||
cellConfig: {
|
||||
height: 64,
|
||||
},
|
||||
} as VxeTableGridOptions<BpmTaskApi.TaskVO>,
|
||||
});
|
||||
|
||||
/** 表格操作按钮的回调函数 */
|
||||
function onActionClick({ code, row }: OnActionClickParams<BpmTaskApi.TaskVO>) {
|
||||
switch (code) {
|
||||
case 'history': {
|
||||
onHistory(row);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 查看历史 */
|
||||
function onHistory(row: BpmTaskApi.TaskVO) {
|
||||
console.warn(row);
|
||||
router.push({
|
||||
name: 'BpmProcessInstanceDetail',
|
||||
query: {
|
||||
// TODO @siye:数据类型,会爆红哈;
|
||||
id: row.processInstance.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/** 刷新表格 */
|
||||
function onRefresh() {
|
||||
gridApi.query();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page>
|
||||
<Page auto-content-height>
|
||||
<DocAlert title="工作流手册" url="https://doc.iocoder.cn/bpm/" />
|
||||
<Button
|
||||
danger
|
||||
type="link"
|
||||
target="_blank"
|
||||
href="https://github.com/yudaocode/yudao-ui-admin-vue3"
|
||||
>
|
||||
该功能支持 Vue3 + element-plus 版本!
|
||||
</Button>
|
||||
<br />
|
||||
<Button
|
||||
type="link"
|
||||
target="_blank"
|
||||
href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/task/manager/index"
|
||||
>
|
||||
可参考
|
||||
https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/task/manager/index
|
||||
代码,pull request 贡献给我们!
|
||||
</Button>
|
||||
|
||||
<FormModal @success="onRefresh" />
|
||||
<Grid table-title="流程任务">
|
||||
<!-- 摘要 -->
|
||||
<!-- TODO siye:这个要不要,也放到 data.ts 处理掉? -->
|
||||
<template #slot-summary="{ row }">
|
||||
<div
|
||||
class="flex flex-col py-2"
|
||||
v-if="
|
||||
row.processInstance.summary &&
|
||||
row.processInstance.summary.length > 0
|
||||
"
|
||||
>
|
||||
<div
|
||||
v-for="(item, index) in row.processInstance.summary"
|
||||
:key="index"
|
||||
>
|
||||
<span class="text-gray-500">
|
||||
{{ item.key }} : {{ item.value }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>-</div>
|
||||
</template>
|
||||
</Grid>
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
143
apps/web-antd/src/views/bpm/task/todo/data.ts
Normal file
143
apps/web-antd/src/views/bpm/task/todo/data.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { BpmTaskApi } from '#/api/bpm/task';
|
||||
|
||||
import { useAccess } from '@vben/access';
|
||||
|
||||
import { getCategorySimpleList } from '#/api/bpm/category';
|
||||
import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
|
||||
|
||||
const { hasAccessByCodes } = useAccess();
|
||||
|
||||
/** 列表的搜索表单 */
|
||||
export function useGridFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
fieldName: 'name',
|
||||
label: '流程名称',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: '请输入流程名称',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'processDefinitionId',
|
||||
label: '所属流程',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: '请输入流程定义的编号',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'category',
|
||||
label: '流程分类',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
placeholder: '请输入流程分类',
|
||||
allowClear: true,
|
||||
api: getCategorySimpleList,
|
||||
labelField: 'name',
|
||||
valueField: 'code',
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'status',
|
||||
label: '流程状态',
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
options: getDictOptions(
|
||||
DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS,
|
||||
'number',
|
||||
),
|
||||
placeholder: '请选择流程状态',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'createTime',
|
||||
label: '发起时间',
|
||||
component: 'RangePicker',
|
||||
componentProps: {
|
||||
...getRangePickerDefaultProps(),
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/** 列表的字段 */
|
||||
export function useGridColumns<T = BpmTaskApi.TaskVO>(
|
||||
onActionClick: OnActionClickFn<T>,
|
||||
): VxeTableGridOptions['columns'] {
|
||||
return [
|
||||
{
|
||||
field: 'name',
|
||||
title: '流程名称',
|
||||
minWidth: 200,
|
||||
fixed: 'left',
|
||||
},
|
||||
{
|
||||
field: 'processInstance.summary',
|
||||
title: '摘要',
|
||||
minWidth: 200,
|
||||
slots: {
|
||||
default: 'slot-summary',
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'processInstance.startUser.nickname',
|
||||
title: '发起人',
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
field: 'createTime',
|
||||
title: '发起时间',
|
||||
minWidth: 180,
|
||||
formatter: 'formatDateTime',
|
||||
},
|
||||
{
|
||||
field: 'name',
|
||||
title: '当前任务',
|
||||
minWidth: 180,
|
||||
},
|
||||
{
|
||||
field: 'createTime',
|
||||
title: '任务时间',
|
||||
minWidth: 180,
|
||||
formatter: 'formatDateTime',
|
||||
},
|
||||
{
|
||||
field: 'processInstanceId',
|
||||
title: '流程编号',
|
||||
minWidth: 280,
|
||||
},
|
||||
{
|
||||
field: 'id',
|
||||
title: '任务编号',
|
||||
minWidth: 280,
|
||||
},
|
||||
{
|
||||
field: 'operation',
|
||||
title: '操作',
|
||||
minWidth: 120,
|
||||
align: 'center',
|
||||
fixed: 'right',
|
||||
cellRender: {
|
||||
attrs: {
|
||||
nameField: 'name',
|
||||
nameTitle: '流程名称',
|
||||
onClick: onActionClick,
|
||||
},
|
||||
name: 'CellOperation',
|
||||
options: [
|
||||
{
|
||||
code: 'audit',
|
||||
text: '办理',
|
||||
show: hasAccessByCodes(['bpm:task:query']),
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -1,13 +1,83 @@
|
||||
<script lang="ts" setup>
|
||||
import type {
|
||||
OnActionClickParams,
|
||||
VxeTableGridOptions,
|
||||
} from '#/adapter/vxe-table';
|
||||
import type { BpmTaskApi } from '#/api/bpm/task';
|
||||
|
||||
import { Page } from '@vben/common-ui';
|
||||
|
||||
import { Button } from 'ant-design-vue';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { getTaskTodoPage } from '#/api/bpm/task';
|
||||
import { DocAlert } from '#/components/doc-alert';
|
||||
import { router } from '#/router';
|
||||
|
||||
import { useGridColumns, useGridFormSchema } from './data';
|
||||
|
||||
defineOptions({ name: 'BpmTodoTask' });
|
||||
|
||||
const [Grid, gridApi] = useVbenVxeGrid({
|
||||
formOptions: {
|
||||
schema: useGridFormSchema(),
|
||||
},
|
||||
gridOptions: {
|
||||
columns: useGridColumns(onActionClick),
|
||||
height: 'auto',
|
||||
keepSource: true,
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
query: async ({ page }, formValues) => {
|
||||
return await getTaskTodoPage({
|
||||
pageNo: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
...formValues,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
rowConfig: {
|
||||
keyField: 'id',
|
||||
},
|
||||
toolbarConfig: {
|
||||
refresh: { code: 'query' },
|
||||
search: true,
|
||||
},
|
||||
cellConfig: {
|
||||
height: 64,
|
||||
},
|
||||
} as VxeTableGridOptions<BpmTaskApi.TaskVO>,
|
||||
});
|
||||
|
||||
/** 表格操作按钮的回调函数 */
|
||||
function onActionClick({ code, row }: OnActionClickParams<BpmTaskApi.TaskVO>) {
|
||||
switch (code) {
|
||||
case 'audit': {
|
||||
onAudit(row);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 办理任务 */
|
||||
function onAudit(row: BpmTaskApi.TaskVO) {
|
||||
console.warn(row);
|
||||
router.push({
|
||||
name: 'BpmProcessInstanceDetail',
|
||||
query: {
|
||||
id: row.processInstance.id,
|
||||
taskId: row.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/** 刷新表格 */
|
||||
function onRefresh() {
|
||||
gridApi.query();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page>
|
||||
<Page auto-content-height>
|
||||
<DocAlert
|
||||
title="审批通过、不通过、驳回"
|
||||
url="https://doc.iocoder.cn/bpm/task-todo-done/"
|
||||
@@ -18,23 +88,30 @@ import { DocAlert } from '#/components/doc-alert';
|
||||
url="https://doc.iocoder.cn/bpm/task-delegation-and-cc/"
|
||||
/>
|
||||
<DocAlert title="审批加签、减签" url="https://doc.iocoder.cn/bpm/sign/" />
|
||||
<Button
|
||||
danger
|
||||
type="link"
|
||||
target="_blank"
|
||||
href="https://github.com/yudaocode/yudao-ui-admin-vue3"
|
||||
>
|
||||
该功能支持 Vue3 + element-plus 版本!
|
||||
</Button>
|
||||
<br />
|
||||
<Button
|
||||
type="link"
|
||||
target="_blank"
|
||||
href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/task/todo/index"
|
||||
>
|
||||
可参考
|
||||
https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/task/todo/index
|
||||
代码,pull request 贡献给我们!
|
||||
</Button>
|
||||
|
||||
<FormModal @success="onRefresh" />
|
||||
<Grid table-title="待办任务">
|
||||
<!-- 摘要 -->
|
||||
<!-- TODO siye:这个要不要,也放到 data.ts 处理掉? -->
|
||||
<template #slot-summary="{ row }">
|
||||
<div
|
||||
class="flex flex-col py-2"
|
||||
v-if="
|
||||
row.processInstance.summary &&
|
||||
row.processInstance.summary.length > 0
|
||||
"
|
||||
>
|
||||
<div
|
||||
v-for="(item, index) in row.processInstance.summary"
|
||||
:key="index"
|
||||
>
|
||||
<span class="text-gray-500">
|
||||
{{ item.key }} : {{ item.value }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>-</div>
|
||||
</template>
|
||||
</Grid>
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
@@ -150,6 +150,7 @@ export function useGridColumns(
|
||||
minWidth: 200,
|
||||
align: 'center',
|
||||
fixed: 'right',
|
||||
// TODO @puhui999:headerAlign 要使用 headerAlign: 'center' 么?看着现在分成了 align 和 headerAlign 两种
|
||||
headerAlign: 'center',
|
||||
showOverflow: false,
|
||||
cellRender: {
|
||||
|
||||
@@ -50,6 +50,7 @@ const [Modal, modalApi] = useVbenModal({
|
||||
|
||||
/** 上传前 */
|
||||
function beforeUpload(file: FileType) {
|
||||
// TODO @puhui999:研究下,看看怎么类似 antd 可以前端直传哈;通过配置切换;
|
||||
formApi.setFieldValue('file', file);
|
||||
return false;
|
||||
}
|
||||
@@ -61,6 +62,7 @@ function beforeUpload(file: FileType) {
|
||||
<template #file>
|
||||
<div class="w-full">
|
||||
<!-- 上传区域 -->
|
||||
<!-- TODO @puhui999:1)上传图片,用不了;2)底部有点遮挡 -->
|
||||
<Upload.Dragger
|
||||
name="file"
|
||||
:max-count="1"
|
||||
|
||||
@@ -323,10 +323,9 @@ export function useGridColumns(
|
||||
{
|
||||
field: 'operation',
|
||||
title: '操作',
|
||||
align: 'right',
|
||||
minWidth: 200,
|
||||
fixed: 'right',
|
||||
headerAlign: 'center',
|
||||
align: 'center',
|
||||
showOverflow: false,
|
||||
cellRender: {
|
||||
attrs: {
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { SystemRoleApi } from '#/api/system/role';
|
||||
|
||||
import { useAccess } from '@vben/access';
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
|
||||
import { z } from '#/adapter/form';
|
||||
import {
|
||||
@@ -13,8 +10,6 @@ import {
|
||||
SystemDataScopeEnum,
|
||||
} from '#/utils';
|
||||
|
||||
const { hasAccessByCodes } = useAccess();
|
||||
|
||||
/** 新增/修改的表单 */
|
||||
export function useFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
@@ -189,9 +184,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
}
|
||||
|
||||
/** 列表的字段 */
|
||||
export function useGridColumns<T = SystemRoleApi.Role>(
|
||||
onActionClick: OnActionClickFn<T>,
|
||||
): VxeTableGridOptions['columns'] {
|
||||
export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||
return [
|
||||
{
|
||||
field: 'id',
|
||||
@@ -243,41 +236,10 @@ export function useGridColumns<T = SystemRoleApi.Role>(
|
||||
formatter: 'formatDateTime',
|
||||
},
|
||||
{
|
||||
field: 'operation',
|
||||
title: '操作',
|
||||
width: 240,
|
||||
fixed: 'right',
|
||||
align: 'center',
|
||||
cellRender: {
|
||||
attrs: {
|
||||
nameField: 'name',
|
||||
nameTitle: '角色',
|
||||
onClick: onActionClick,
|
||||
},
|
||||
name: 'CellOperation',
|
||||
options: [
|
||||
{
|
||||
code: 'edit',
|
||||
show: hasAccessByCodes(['system:role:update']),
|
||||
},
|
||||
{
|
||||
code: 'delete',
|
||||
show: hasAccessByCodes(['system:role:delete']),
|
||||
},
|
||||
{
|
||||
code: 'assign-data-permission',
|
||||
text: '数据权限',
|
||||
show: hasAccessByCodes([
|
||||
'system:permission:assign-role-data-scope',
|
||||
]),
|
||||
},
|
||||
{
|
||||
code: 'assign-menu',
|
||||
text: '菜单权限',
|
||||
show: hasAccessByCodes(['system:permission:assign-role-menu']),
|
||||
},
|
||||
],
|
||||
},
|
||||
slots: { default: 'actions' },
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import type {
|
||||
OnActionClickParams,
|
||||
VxeTableGridOptions,
|
||||
} from '#/adapter/vxe-table';
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { SystemRoleApi } from '#/api/system/role';
|
||||
|
||||
import { Page, useVbenModal } from '@vben/common-ui';
|
||||
@@ -14,6 +11,7 @@ import { Button, message } from 'ant-design-vue';
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { deleteRole, exportRole, getRolePage } from '#/api/system/role';
|
||||
import { DocAlert } from '#/components/doc-alert';
|
||||
import { TableAction } from '#/components/table-action';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
import { useGridColumns, useGridFormSchema } from './data';
|
||||
@@ -67,6 +65,7 @@ async function onDelete(row: SystemRoleApi.Role) {
|
||||
});
|
||||
try {
|
||||
await deleteRole(row.id as number);
|
||||
hideLoading();
|
||||
message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
|
||||
onRefresh();
|
||||
} catch {
|
||||
@@ -84,34 +83,12 @@ function onAssignMenu(row: SystemRoleApi.Role) {
|
||||
assignMenuFormApi.setData(row).open();
|
||||
}
|
||||
|
||||
/** 表格操作按钮的回调函数 */
|
||||
function onActionClick({ code, row }: OnActionClickParams<SystemRoleApi.Role>) {
|
||||
switch (code) {
|
||||
case 'assign-data-permission': {
|
||||
onAssignDataPermission(row);
|
||||
break;
|
||||
}
|
||||
case 'assign-menu': {
|
||||
onAssignMenu(row);
|
||||
break;
|
||||
}
|
||||
case 'delete': {
|
||||
onDelete(row);
|
||||
break;
|
||||
}
|
||||
case 'edit': {
|
||||
onEdit(row);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const [Grid, gridApi] = useVbenVxeGrid({
|
||||
formOptions: {
|
||||
schema: useGridFormSchema(),
|
||||
},
|
||||
gridOptions: {
|
||||
columns: useGridColumns(onActionClick),
|
||||
columns: useGridColumns(),
|
||||
height: 'auto',
|
||||
keepSource: true,
|
||||
proxyConfig: {
|
||||
@@ -169,6 +146,44 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||
{{ $t('ui.actionTitle.export') }}
|
||||
</Button>
|
||||
</template>
|
||||
<template #actions="{ row }">
|
||||
<TableAction
|
||||
:actions="[
|
||||
{
|
||||
label: $t('common.edit'),
|
||||
type: 'link',
|
||||
icon: 'ant-design:edit-outlined',
|
||||
auth: ['system:role:update'],
|
||||
onClick: onEdit.bind(null, row),
|
||||
},
|
||||
{
|
||||
label: $t('common.delete'),
|
||||
type: 'link',
|
||||
danger: true,
|
||||
icon: 'ant-design:delete-outlined',
|
||||
auth: ['system:role:delete'],
|
||||
popConfirm: {
|
||||
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
|
||||
confirm: onDelete.bind(null, row),
|
||||
},
|
||||
},
|
||||
]"
|
||||
:drop-down-actions="[
|
||||
{
|
||||
label: '数据权限',
|
||||
type: 'link',
|
||||
auth: ['system:permission:assign-role-data-scope'],
|
||||
onClick: onAssignDataPermission.bind(null, row),
|
||||
},
|
||||
{
|
||||
label: '菜单权限',
|
||||
type: 'link',
|
||||
auth: ['system:permission:assign-role-menu'],
|
||||
onClick: onAssignMenu.bind(null, row),
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
</Grid>
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { SystemTenantApi } from '#/api/system/tenant';
|
||||
|
||||
import { useAccess } from '@vben/access';
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
|
||||
import { z } from '#/adapter/form';
|
||||
import { getTenantPackageList } from '#/api/system/tenant-package';
|
||||
@@ -13,8 +10,6 @@ import {
|
||||
getRangePickerDefaultProps,
|
||||
} from '#/utils';
|
||||
|
||||
const { hasAccessByCodes } = useAccess();
|
||||
|
||||
/** 新增/修改的表单 */
|
||||
export function useFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
@@ -162,8 +157,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
}
|
||||
|
||||
/** 列表的字段 */
|
||||
export function useGridColumns<T = SystemTenantApi.Tenant>(
|
||||
onActionClick: OnActionClickFn<T>,
|
||||
export function useGridColumns(
|
||||
getPackageName?: (packageId: number) => string | undefined,
|
||||
): VxeTableGridOptions['columns'] {
|
||||
return [
|
||||
@@ -227,29 +221,10 @@ export function useGridColumns<T = SystemTenantApi.Tenant>(
|
||||
formatter: 'formatDateTime',
|
||||
},
|
||||
{
|
||||
field: 'operation',
|
||||
title: '操作',
|
||||
minWidth: 130,
|
||||
align: 'center',
|
||||
width: 130,
|
||||
fixed: 'right',
|
||||
cellRender: {
|
||||
attrs: {
|
||||
nameField: 'name',
|
||||
nameTitle: '租户',
|
||||
onClick: onActionClick,
|
||||
},
|
||||
name: 'CellOperation',
|
||||
options: [
|
||||
{
|
||||
code: 'edit',
|
||||
show: hasAccessByCodes(['system:tenant:update']),
|
||||
},
|
||||
{
|
||||
code: 'delete',
|
||||
show: hasAccessByCodes(['system:tenant:delete']),
|
||||
},
|
||||
],
|
||||
},
|
||||
slots: { default: 'actions' },
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import type {
|
||||
OnActionClickParams,
|
||||
VxeTableGridOptions,
|
||||
} from '#/adapter/vxe-table';
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { SystemTenantApi } from '#/api/system/tenant';
|
||||
import type { SystemTenantPackageApi } from '#/api/system/tenant-package';
|
||||
|
||||
@@ -75,29 +72,12 @@ async function onDelete(row: SystemTenantApi.Tenant) {
|
||||
}
|
||||
}
|
||||
|
||||
/** 表格操作按钮的回调函数 */
|
||||
function onActionClick({
|
||||
code,
|
||||
row,
|
||||
}: OnActionClickParams<SystemTenantApi.Tenant>) {
|
||||
switch (code) {
|
||||
case 'delete': {
|
||||
onDelete(row);
|
||||
break;
|
||||
}
|
||||
case 'edit': {
|
||||
onEdit(row);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const [Grid, gridApi] = useVbenVxeGrid({
|
||||
formOptions: {
|
||||
schema: useGridFormSchema(),
|
||||
},
|
||||
gridOptions: {
|
||||
columns: useGridColumns(onActionClick, getPackageName),
|
||||
columns: useGridColumns(getPackageName),
|
||||
height: 'auto',
|
||||
keepSource: true,
|
||||
proxyConfig: {
|
||||
@@ -153,6 +133,30 @@ onMounted(async () => {
|
||||
{{ $t('ui.actionTitle.export') }}
|
||||
</Button>
|
||||
</template>
|
||||
<template #actions="{ row }">
|
||||
<TableAction
|
||||
:actions="[
|
||||
{
|
||||
label: $t('common.edit'),
|
||||
type: 'link',
|
||||
icon: 'ant-design:edit-outlined',
|
||||
auth: ['system:role:update'],
|
||||
onClick: onEdit.bind(null, row),
|
||||
},
|
||||
{
|
||||
label: $t('common.delete'),
|
||||
type: 'link',
|
||||
danger: true,
|
||||
icon: 'ant-design:delete-outlined',
|
||||
auth: ['system:role:delete'],
|
||||
popConfirm: {
|
||||
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
|
||||
confirm: onDelete.bind(null, row),
|
||||
},
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
</Grid>
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { SystemTenantPackageApi } from '#/api/system/tenant-package';
|
||||
|
||||
import { useAccess } from '@vben/access';
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
|
||||
import { z } from '#/adapter/form';
|
||||
import {
|
||||
@@ -12,8 +9,6 @@ import {
|
||||
getRangePickerDefaultProps,
|
||||
} from '#/utils';
|
||||
|
||||
const { hasAccessByCodes } = useAccess();
|
||||
|
||||
/** 新增/修改的表单 */
|
||||
export function useFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
@@ -91,9 +86,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
}
|
||||
|
||||
/** 列表的字段 */
|
||||
export function useGridColumns<T = SystemTenantPackageApi.TenantPackage>(
|
||||
onActionClick: OnActionClickFn<T>,
|
||||
): VxeTableGridOptions['columns'] {
|
||||
export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||
return [
|
||||
{
|
||||
field: 'id',
|
||||
@@ -126,29 +119,10 @@ export function useGridColumns<T = SystemTenantPackageApi.TenantPackage>(
|
||||
formatter: 'formatDateTime',
|
||||
},
|
||||
{
|
||||
field: 'operation',
|
||||
title: '操作',
|
||||
minWidth: 130,
|
||||
align: 'center',
|
||||
width: 130,
|
||||
fixed: 'right',
|
||||
cellRender: {
|
||||
attrs: {
|
||||
nameField: 'name',
|
||||
nameTitle: '套餐',
|
||||
onClick: onActionClick,
|
||||
},
|
||||
name: 'CellOperation',
|
||||
options: [
|
||||
{
|
||||
code: 'edit',
|
||||
show: hasAccessByCodes(['system:tenant-package:update']),
|
||||
},
|
||||
{
|
||||
code: 'delete',
|
||||
show: hasAccessByCodes(['system:tenant-package:delete']),
|
||||
},
|
||||
],
|
||||
},
|
||||
slots: { default: 'actions' },
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import type {
|
||||
OnActionClickParams,
|
||||
VxeTableGridOptions,
|
||||
} from '#/adapter/vxe-table';
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { SystemTenantPackageApi } from '#/api/system/tenant-package';
|
||||
|
||||
import { Page, useVbenModal } from '@vben/common-ui';
|
||||
@@ -57,30 +54,13 @@ async function onDelete(row: SystemTenantPackageApi.TenantPackage) {
|
||||
}
|
||||
}
|
||||
|
||||
/** 表格操作按钮的回调函数 */
|
||||
function onActionClick({
|
||||
code,
|
||||
row,
|
||||
}: OnActionClickParams<SystemTenantPackageApi.TenantPackage>) {
|
||||
switch (code) {
|
||||
case 'delete': {
|
||||
onDelete(row);
|
||||
break;
|
||||
}
|
||||
case 'edit': {
|
||||
onEdit(row);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const [Grid, gridApi] = useVbenVxeGrid({
|
||||
formOptions: {
|
||||
schema: useGridFormSchema(),
|
||||
// TODO @芋艿:时间筛选,后续处理;
|
||||
},
|
||||
gridOptions: {
|
||||
columns: useGridColumns(onActionClick),
|
||||
columns: useGridColumns(),
|
||||
height: 'auto',
|
||||
keepSource: true,
|
||||
proxyConfig: {
|
||||
@@ -123,6 +103,30 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||
{{ $t('ui.actionTitle.create', ['套餐']) }}
|
||||
</Button>
|
||||
</template>
|
||||
<template #actions="{ row }">
|
||||
<TableAction
|
||||
:actions="[
|
||||
{
|
||||
label: $t('common.edit'),
|
||||
type: 'link',
|
||||
icon: 'ant-design:edit-outlined',
|
||||
auth: ['system:role:update'],
|
||||
onClick: onEdit.bind(null, row),
|
||||
},
|
||||
{
|
||||
label: $t('common.delete'),
|
||||
type: 'link',
|
||||
danger: true,
|
||||
icon: 'ant-design:delete-outlined',
|
||||
auth: ['system:role:delete'],
|
||||
popConfirm: {
|
||||
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
|
||||
confirm: onDelete.bind(null, row),
|
||||
},
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
</Grid>
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { SystemUserApi } from '#/api/system/user';
|
||||
|
||||
import { useAccess } from '@vben/access';
|
||||
import { handleTree } from '@vben/utils';
|
||||
|
||||
import { z } from '#/adapter/form';
|
||||
@@ -16,8 +15,6 @@ import {
|
||||
getRangePickerDefaultProps,
|
||||
} from '#/utils';
|
||||
|
||||
const { hasAccessByCodes } = useAccess();
|
||||
|
||||
/** 新增/修改的表单 */
|
||||
export function useFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
@@ -83,7 +80,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
fieldName: 'email',
|
||||
label: '邮箱',
|
||||
component: 'Input',
|
||||
rules: z.string().email('邮箱格式不正确').optional(),
|
||||
rules: z.string().email('邮箱格式不正确').or(z.literal('')).optional(),
|
||||
},
|
||||
{
|
||||
fieldName: 'mobile',
|
||||
@@ -262,7 +259,6 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
|
||||
/** 列表的字段 */
|
||||
export function useGridColumns<T = SystemUserApi.User>(
|
||||
onActionClick: OnActionClickFn<T>,
|
||||
onStatusChange?: (
|
||||
newStatus: number,
|
||||
row: T,
|
||||
@@ -315,41 +311,10 @@ export function useGridColumns<T = SystemUserApi.User>(
|
||||
formatter: 'formatDateTime',
|
||||
},
|
||||
{
|
||||
field: 'operation',
|
||||
title: '操作',
|
||||
minWidth: 160,
|
||||
width: 160,
|
||||
fixed: 'right',
|
||||
align: 'center',
|
||||
cellRender: {
|
||||
attrs: {
|
||||
nameField: 'username',
|
||||
nameTitle: '用户',
|
||||
onClick: onActionClick,
|
||||
},
|
||||
name: 'CellOperation',
|
||||
// TODO @芋艿:后续把 delete、assign-role、reset-password 搞成"更多"
|
||||
options: [
|
||||
{
|
||||
code: 'edit',
|
||||
show: hasAccessByCodes(['system:user:update']),
|
||||
},
|
||||
{
|
||||
code: 'delete',
|
||||
show: hasAccessByCodes(['system:user:delete']),
|
||||
},
|
||||
{
|
||||
code: 'assign-role',
|
||||
text: '分配角色',
|
||||
show: hasAccessByCodes(['system:permission:assign-user-role']),
|
||||
'v-access:code': 'system:user:assign-role1',
|
||||
},
|
||||
{
|
||||
code: 'reset-password',
|
||||
text: '重置密码',
|
||||
show: hasAccessByCodes(['system:user:update-password']),
|
||||
},
|
||||
],
|
||||
},
|
||||
slots: { default: 'actions' },
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import type {
|
||||
OnActionClickParams,
|
||||
VxeTableGridOptions,
|
||||
} from '#/adapter/vxe-table';
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { SystemDeptApi } from '#/api/system/dept';
|
||||
import type { SystemUserApi } from '#/api/system/user';
|
||||
|
||||
@@ -22,6 +19,7 @@ import {
|
||||
updateUserStatus,
|
||||
} from '#/api/system/user';
|
||||
import { DocAlert } from '#/components/doc-alert';
|
||||
import { TableAction } from '#/components/table-action';
|
||||
import { $t } from '#/locales';
|
||||
import { DICT_TYPE, getDictLabel } from '#/utils';
|
||||
|
||||
@@ -137,34 +135,12 @@ async function onStatusChange(
|
||||
});
|
||||
}
|
||||
|
||||
/** 表格操作按钮的回调函数 */
|
||||
function onActionClick({ code, row }: OnActionClickParams<SystemUserApi.User>) {
|
||||
switch (code) {
|
||||
case 'assign-role': {
|
||||
onAssignRole(row);
|
||||
break;
|
||||
}
|
||||
case 'delete': {
|
||||
onDelete(row);
|
||||
break;
|
||||
}
|
||||
case 'edit': {
|
||||
onEdit(row);
|
||||
break;
|
||||
}
|
||||
case 'reset-password': {
|
||||
onResetPassword(row);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const [Grid, gridApi] = useVbenVxeGrid({
|
||||
formOptions: {
|
||||
schema: useGridFormSchema(),
|
||||
},
|
||||
gridOptions: {
|
||||
columns: useGridColumns(onActionClick, onStatusChange),
|
||||
columns: useGridColumns(onStatusChange),
|
||||
height: 'auto',
|
||||
keepSource: true,
|
||||
proxyConfig: {
|
||||
@@ -242,6 +218,44 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||
{{ $t('ui.actionTitle.import', ['用户']) }}
|
||||
</Button>
|
||||
</template>
|
||||
<template #actions="{ row }">
|
||||
<TableAction
|
||||
:actions="[
|
||||
{
|
||||
label: $t('common.edit'),
|
||||
type: 'link',
|
||||
icon: 'ant-design:edit-outlined',
|
||||
auth: ['system:user:update'],
|
||||
onClick: onEdit.bind(null, row),
|
||||
},
|
||||
{
|
||||
label: $t('common.delete'),
|
||||
type: 'link',
|
||||
danger: true,
|
||||
icon: 'ant-design:delete-outlined',
|
||||
auth: ['system:user:delete'],
|
||||
popConfirm: {
|
||||
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
|
||||
confirm: onDelete.bind(null, row),
|
||||
},
|
||||
},
|
||||
]"
|
||||
:drop-down-actions="[
|
||||
{
|
||||
label: '数据权限',
|
||||
type: 'link',
|
||||
auth: ['system:permission:assign-user-role'],
|
||||
onClick: onAssignRole.bind(null, row),
|
||||
},
|
||||
{
|
||||
label: '菜单权限',
|
||||
type: 'link',
|
||||
auth: ['system:user:update-password'],
|
||||
onClick: onResetPassword.bind(null, row),
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
</Grid>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user