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>
|
<script lang="ts" setup>
|
||||||
|
import { useBoolean, useLoading } from '~/packages/hooks/src';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'FlowInterveneModal'
|
name: 'FlowInterveneModal'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { bool: multiInstanceVisible, setTrue: openMultiInstanceModal } = useBoolean();
|
||||||
|
const { bool: transferVisible, setTrue: openTransferModal } = useBoolean();
|
||||||
interface Props {
|
interface Props {
|
||||||
rowData: Api.Workflow.TaskOrHisTask;
|
rowData: Api.Workflow.Task;
|
||||||
}
|
}
|
||||||
|
const { loading: btnLoading } = useLoading();
|
||||||
const props = defineProps<Props>();
|
const props = defineProps<Props>();
|
||||||
|
|
||||||
const visible = defineModel<boolean>('visible', {
|
const visible = defineModel<boolean>('visible', {
|
||||||
@ -16,7 +20,7 @@ const visible = defineModel<boolean>('visible', {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NModal v-model:show="visible" class="max-h-520px max-w-90% w-700px" title="流程干预" preset="card" size="medium">
|
<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="任务名称">
|
<NDescriptionsItem label="任务名称">
|
||||||
{{ props.rowData.nodeName }}
|
{{ props.rowData.nodeName }}
|
||||||
</NDescriptionsItem>
|
</NDescriptionsItem>
|
||||||
@ -29,6 +33,9 @@ const visible = defineModel<boolean>('visible', {
|
|||||||
<NDescriptionsItem label="流程实例ID">
|
<NDescriptionsItem label="流程实例ID">
|
||||||
{{ props.rowData.instanceId }}
|
{{ props.rowData.instanceId }}
|
||||||
</NDescriptionsItem>
|
</NDescriptionsItem>
|
||||||
|
<NDescriptionsItem label="办理人">
|
||||||
|
<GroupTag :value="props.rowData.assigneeNames" />
|
||||||
|
</NDescriptionsItem>
|
||||||
<NDescriptionsItem label="版本号">
|
<NDescriptionsItem label="版本号">
|
||||||
{{ props.rowData.version }}
|
{{ props.rowData.version }}
|
||||||
</NDescriptionsItem>
|
</NDescriptionsItem>
|
||||||
@ -36,5 +43,21 @@ const visible = defineModel<boolean>('visible', {
|
|||||||
{{ props.rowData.businessId }}
|
{{ props.rowData.businessId }}
|
||||||
</NDescriptionsItem>
|
</NDescriptionsItem>
|
||||||
</NDescriptions>
|
</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>
|
</NModal>
|
||||||
</template>
|
</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']
|
ThemeSchemaSwitch: typeof import('./../components/common/theme-schema-switch.vue')['default']
|
||||||
TinymceEditor: typeof import('./../components/custom/tinymce-editor.vue')['default']
|
TinymceEditor: typeof import('./../components/custom/tinymce-editor.vue')['default']
|
||||||
UserSelect: typeof import('./../components/custom/user-select.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']
|
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 { bool: interveneVisible, setTrue: showInterveneDrawer } = useBoolean(false);
|
||||||
const dynamicComponent = shallowRef();
|
const dynamicComponent = shallowRef();
|
||||||
|
|
||||||
|
type Task = Api.Workflow.Task;
|
||||||
|
|
||||||
const waitingStatus = ref<boolean>(true);
|
const waitingStatus = ref<boolean>(true);
|
||||||
const waitingStatusOptions = ref<WaitingStatusOption[]>([
|
const waitingStatusOptions = ref<WaitingStatusOption[]>([
|
||||||
{ label: '待办任务', value: true },
|
{ 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(
|
buttons.push(
|
||||||
<ButtonIcon
|
<ButtonIcon
|
||||||
text
|
text
|
||||||
@ -302,7 +304,7 @@ function handleIntervene(row: Api.Workflow.TaskOrHisTask) {
|
|||||||
class="sm:h-full"
|
class="sm:h-full"
|
||||||
/>
|
/>
|
||||||
<component :is="dynamicComponent" :visible="viewVisible" operate-type="detail" :business-id="businessId" />
|
<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>
|
</NCard>
|
||||||
</div>
|
</div>
|
||||||
</TableSiderLayout>
|
</TableSiderLayout>
|
||||||
|
Loading…
Reference in New Issue
Block a user