feat(projects): 新增用户选择器组件,添加流程干预按钮
This commit is contained in:
parent
81449ea77a
commit
b8c771cd1d
308
src/components/custom/user-select-modal.vue
Normal file
308
src/components/custom/user-select-modal.vue
Normal file
@ -0,0 +1,308 @@
|
||||
<script setup lang="tsx">
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { NButton } from 'naive-ui';
|
||||
import { useLoading } from '@sa/hooks';
|
||||
import { fetchGetDeptTree, fetchGetUserList } from '@/service/api/system';
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
import { useTable, useTableOperate } from '@/hooks/common/table';
|
||||
import { useDict } from '@/hooks/business/dict';
|
||||
import { $t } from '@/locales';
|
||||
import UserSearch from '@/views/system/user/modules/user-search.vue';
|
||||
import DictTag from './dict-tag.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'UserSelectModal'
|
||||
});
|
||||
|
||||
interface Props {
|
||||
title?: string;
|
||||
multiple?: boolean;
|
||||
/** 禁选用户ID */
|
||||
disabledIds?: CommonType.IdType[];
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
title: '用户选择',
|
||||
multiple: false,
|
||||
disabledIds: () => []
|
||||
});
|
||||
|
||||
const visible = defineModel<boolean>('visible', {
|
||||
default: false
|
||||
});
|
||||
|
||||
useDict('sys_normal_disable');
|
||||
|
||||
const appStore = useAppStore();
|
||||
|
||||
const {
|
||||
columns,
|
||||
columnChecks,
|
||||
data,
|
||||
getData,
|
||||
getDataByPage,
|
||||
loading,
|
||||
mobilePagination,
|
||||
searchParams,
|
||||
resetSearchParams
|
||||
} = useTable({
|
||||
apiFn: fetchGetUserList,
|
||||
apiParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
// if you want to use the searchParams in Form, you need to define the following properties, and the value is null
|
||||
// the value can not be undefined, otherwise the property in Form will not be reactive
|
||||
deptId: null,
|
||||
userName: null,
|
||||
nickName: null,
|
||||
phonenumber: null,
|
||||
status: null,
|
||||
params: {}
|
||||
},
|
||||
immediate: false,
|
||||
columns: () => [
|
||||
{
|
||||
type: 'selection',
|
||||
multiple: props.multiple,
|
||||
align: 'center',
|
||||
width: 48,
|
||||
disabled: row => props.disabledIds.includes(row.userId.toString())
|
||||
},
|
||||
{
|
||||
key: 'index',
|
||||
title: $t('common.index'),
|
||||
align: 'center',
|
||||
width: 64
|
||||
},
|
||||
{
|
||||
key: 'userName',
|
||||
title: $t('page.system.user.userName'),
|
||||
align: 'center',
|
||||
minWidth: 120,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
key: 'nickName',
|
||||
title: $t('page.system.user.nickName'),
|
||||
align: 'center',
|
||||
minWidth: 120,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
key: 'deptName',
|
||||
title: $t('page.system.user.deptName'),
|
||||
align: 'center',
|
||||
minWidth: 120,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
key: 'phonenumber',
|
||||
title: $t('page.system.user.phonenumber'),
|
||||
align: 'center',
|
||||
minWidth: 120,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
key: 'status',
|
||||
title: $t('page.system.user.status'),
|
||||
align: 'center',
|
||||
minWidth: 80,
|
||||
render(row) {
|
||||
return <DictTag dict-code="sys_normal_disable" value={row.status} />;
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'createTime',
|
||||
title: $t('page.system.user.createTime'),
|
||||
align: 'center',
|
||||
minWidth: 120
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const { checkedRowKeys } = useTableOperate(data, getData);
|
||||
|
||||
const { loading: treeLoading, startLoading: startTreeLoading, endLoading: endTreeLoading } = useLoading();
|
||||
const deptPattern = ref<string>();
|
||||
const deptData = ref<Api.Common.CommonTreeRecord>([]);
|
||||
const selectedKeys = ref<string[]>([]);
|
||||
|
||||
async function getTreeData() {
|
||||
startTreeLoading();
|
||||
const { data: tree, error } = await fetchGetDeptTree();
|
||||
if (!error) {
|
||||
deptData.value = tree;
|
||||
}
|
||||
endTreeLoading();
|
||||
}
|
||||
|
||||
function handleClickTree(keys: string[]) {
|
||||
searchParams.deptId = keys.length ? keys[0] : null;
|
||||
checkedRowKeys.value = [];
|
||||
getDataByPage();
|
||||
}
|
||||
|
||||
function handleResetTreeData() {
|
||||
deptPattern.value = undefined;
|
||||
getTreeData();
|
||||
}
|
||||
|
||||
const expandedKeys = ref<CommonType.IdType[]>([100]);
|
||||
|
||||
const selectable = computed(() => {
|
||||
return !loading.value;
|
||||
});
|
||||
|
||||
function handleResetSearch() {
|
||||
resetSearchParams();
|
||||
selectedKeys.value = [];
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
function getRowProps(row: Api.System.User) {
|
||||
return {
|
||||
onClick: () => {
|
||||
if (props.disabledIds.includes(row.userId.toString())) {
|
||||
return;
|
||||
}
|
||||
if (props.multiple) {
|
||||
const index = checkedRowKeys.value.findIndex(key => key === row.userId);
|
||||
if (index > -1) {
|
||||
checkedRowKeys.value.splice(index, 1);
|
||||
} else {
|
||||
checkedRowKeys.value.push(row.userId);
|
||||
}
|
||||
} else {
|
||||
checkedRowKeys.value = [row.userId];
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
watch(visible, () => {
|
||||
if (visible.value) {
|
||||
getTreeData();
|
||||
getData();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NModal
|
||||
v-model:show="visible"
|
||||
class="user-select-modal max-h-800px max-w-90% w-1400px"
|
||||
preset="card"
|
||||
size="medium"
|
||||
:title="props.title"
|
||||
>
|
||||
<TableSiderLayout :sider-title="$t('page.system.dept.title')">
|
||||
<template #header-extra>
|
||||
<NButton size="small" text class="h-18px" @click.stop="() => handleResetTreeData()">
|
||||
<template #icon>
|
||||
<SvgIcon icon="ic:round-refresh" />
|
||||
</template>
|
||||
</NButton>
|
||||
</template>
|
||||
<template #sider>
|
||||
<NInput v-model:value="deptPattern" clearable :placeholder="$t('common.keywordSearch')" />
|
||||
<NSpin class="dept-tree" :show="treeLoading">
|
||||
<NTree
|
||||
v-model:expanded-keys="expandedKeys"
|
||||
v-model:selected-keys="selectedKeys"
|
||||
block-node
|
||||
show-line
|
||||
:data="deptData as []"
|
||||
:show-irrelevant-nodes="false"
|
||||
:pattern="deptPattern"
|
||||
class="infinite-scroll h-full min-h-200px py-3"
|
||||
key-field="id"
|
||||
label-field="label"
|
||||
virtual-scroll
|
||||
:selectable="selectable"
|
||||
@update:selected-keys="handleClickTree"
|
||||
>
|
||||
<template #empty>
|
||||
<NEmpty :description="$t('page.system.dept.empty')" class="h-full min-h-200px justify-center" />
|
||||
</template>
|
||||
</NTree>
|
||||
</NSpin>
|
||||
</template>
|
||||
<div class="h-full flex-col-stretch gap-12px overflow-hidden lt-sm:max-h-500px lt-sm:overflow-auto">
|
||||
<UserSearch v-model:model="searchParams" @reset="handleResetSearch" @search="getDataByPage" />
|
||||
<TableRowCheckAlert v-model:checked-row-keys="checkedRowKeys" />
|
||||
<NAlert v-if="props.disabledIds.length > 0" type="warning">
|
||||
<span>已存在的用户无法被选择</span>
|
||||
</NAlert>
|
||||
<NCard
|
||||
:title="$t('page.system.user.title')"
|
||||
:bordered="false"
|
||||
size="small"
|
||||
class="sm:flex-1-hidden card-wrapper lt-sm:overflow-auto"
|
||||
>
|
||||
<template #header-extra>
|
||||
<TableHeaderOperation
|
||||
v-model:columns="columnChecks"
|
||||
:loading="loading"
|
||||
:show-add="false"
|
||||
:show-delete="false"
|
||||
:show-export="false"
|
||||
@refresh="getData"
|
||||
></TableHeaderOperation>
|
||||
</template>
|
||||
<NDataTable
|
||||
v-model:checked-row-keys="checkedRowKeys"
|
||||
:columns="columns"
|
||||
:data="data"
|
||||
size="small"
|
||||
:flex-height="!appStore.isMobile"
|
||||
:scroll-x="962"
|
||||
:loading="loading"
|
||||
:row-props="getRowProps"
|
||||
remote
|
||||
:row-key="row => row.userId"
|
||||
:pagination="mobilePagination"
|
||||
class="h-full lt-sm:max-h-300px"
|
||||
/>
|
||||
</NCard>
|
||||
</div>
|
||||
</TableSiderLayout>
|
||||
<template #footer>
|
||||
<NSpace justify="end" :size="16">
|
||||
<NButton @click="closeModal">{{ $t('common.cancel') }}</NButton>
|
||||
<NButton type="primary">{{ $t('common.confirm') }}</NButton>
|
||||
</NSpace>
|
||||
</template>
|
||||
</NModal>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
:deep(.n-layout) {
|
||||
height: 600px;
|
||||
|
||||
@media (max-width: 639px) {
|
||||
height: auto;
|
||||
max-height: 500px;
|
||||
}
|
||||
}
|
||||
|
||||
.user-select-modal {
|
||||
@media (max-width: 639px) {
|
||||
:deep(.n-card-content) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:deep(.n-data-table) {
|
||||
max-height: 300px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.n-alert {
|
||||
--n-padding: 5px 13px !important;
|
||||
--n-icon-margin: 6px 8px 0 12px !important;
|
||||
--n-icon-size: 20px !important;
|
||||
}
|
||||
</style>
|
@ -1,12 +1,16 @@
|
||||
<script lang="ts" setup>
|
||||
import { useBoolean, useLoading } from '~/packages/hooks/src';
|
||||
|
||||
defineOptions({
|
||||
name: 'FlowInterveneModal'
|
||||
});
|
||||
|
||||
const { bool: multiInstanceVisible, setTrue: openMultiInstanceModal } = useBoolean();
|
||||
const { bool: transferVisible, setTrue: openTransferModal } = useBoolean();
|
||||
interface Props {
|
||||
rowData: Api.Workflow.TaskOrHisTask;
|
||||
rowData: Api.Workflow.Task;
|
||||
}
|
||||
|
||||
const { loading: btnLoading } = useLoading();
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const visible = defineModel<boolean>('visible', {
|
||||
@ -16,7 +20,7 @@ const visible = defineModel<boolean>('visible', {
|
||||
|
||||
<template>
|
||||
<NModal v-model:show="visible" class="max-h-520px max-w-90% w-700px" title="流程干预" preset="card" size="medium">
|
||||
<NDescriptions label-placement="left" :column="2" size="small" bordered>
|
||||
<NDescriptions :title="props.rowData.flowName" label-placement="left" :column="2" size="small" bordered>
|
||||
<NDescriptionsItem label="任务名称">
|
||||
{{ props.rowData.nodeName }}
|
||||
</NDescriptionsItem>
|
||||
@ -29,6 +33,9 @@ const visible = defineModel<boolean>('visible', {
|
||||
<NDescriptionsItem label="流程实例ID">
|
||||
{{ props.rowData.instanceId }}
|
||||
</NDescriptionsItem>
|
||||
<NDescriptionsItem label="办理人">
|
||||
<GroupTag :value="props.rowData.assigneeNames" />
|
||||
</NDescriptionsItem>
|
||||
<NDescriptionsItem label="版本号">
|
||||
{{ props.rowData.version }}
|
||||
</NDescriptionsItem>
|
||||
@ -36,5 +43,21 @@ const visible = defineModel<boolean>('visible', {
|
||||
{{ props.rowData.businessId }}
|
||||
</NDescriptionsItem>
|
||||
</NDescriptions>
|
||||
<template #footer>
|
||||
<NSpace justify="end" :size="16">
|
||||
<NButton :disabled="btnLoading" type="primary" @click="openTransferModal">转办</NButton>
|
||||
<NButton :disabled="btnLoading" type="primary" @click="openMultiInstanceModal">加签</NButton>
|
||||
<NButton :disabled="btnLoading" type="primary">减签</NButton>
|
||||
<NButton :disabled="btnLoading" type="error">中止</NButton>
|
||||
</NSpace>
|
||||
</template>
|
||||
<!-- 加签用户选择器 -->
|
||||
<UserSelectModal
|
||||
v-model:visible="multiInstanceVisible"
|
||||
multiple
|
||||
:disabled-ids="props.rowData.assigneeIds.split(',')"
|
||||
/>
|
||||
<!-- 转办用户选择器 -->
|
||||
<UserSelectModal v-model:visible="transferVisible" :disabled-ids="props.rowData.assigneeIds.split(',')" />
|
||||
</NModal>
|
||||
</template>
|
||||
|
1
src/typings/components.d.ts
vendored
1
src/typings/components.d.ts
vendored
@ -169,6 +169,7 @@ declare module 'vue' {
|
||||
ThemeSchemaSwitch: typeof import('./../components/common/theme-schema-switch.vue')['default']
|
||||
TinymceEditor: typeof import('./../components/custom/tinymce-editor.vue')['default']
|
||||
UserSelect: typeof import('./../components/custom/user-select.vue')['default']
|
||||
UserSelectModal: typeof import('./../components/custom/user-select-modal.vue')['default']
|
||||
WaveBg: typeof import('./../components/custom/wave-bg.vue')['default']
|
||||
}
|
||||
}
|
||||
|
@ -30,6 +30,8 @@ const { bool: viewVisible, setTrue: showViewDrawer } = useBoolean(false);
|
||||
const { bool: interveneVisible, setTrue: showInterveneDrawer } = useBoolean(false);
|
||||
const dynamicComponent = shallowRef();
|
||||
|
||||
type Task = Api.Workflow.Task;
|
||||
|
||||
const waitingStatus = ref<boolean>(true);
|
||||
const waitingStatusOptions = ref<WaitingStatusOption[]>([
|
||||
{ label: '待办任务', value: true },
|
||||
@ -114,7 +116,7 @@ const operateColumns = ref<NaiveUI.TableColumn<Api.Workflow.TaskOrHisTask>[]>([
|
||||
/>
|
||||
];
|
||||
|
||||
if (waitingStatus.value) {
|
||||
if (waitingStatus.value && row.flowStatus !== 'draft') {
|
||||
buttons.push(
|
||||
<ButtonIcon
|
||||
text
|
||||
@ -302,7 +304,7 @@ function handleIntervene(row: Api.Workflow.TaskOrHisTask) {
|
||||
class="sm:h-full"
|
||||
/>
|
||||
<component :is="dynamicComponent" :visible="viewVisible" operate-type="detail" :business-id="businessId" />
|
||||
<FlowInterveneModal v-model:visible="interveneVisible" :row-data="interveneRowData!" />
|
||||
<FlowInterveneModal v-model:visible="interveneVisible" :row-data="interveneRowData as Task" />
|
||||
</NCard>
|
||||
</div>
|
||||
</TableSiderLayout>
|
||||
|
Loading…
Reference in New Issue
Block a user