Files
cn-rdms-web/src/views/project/project/execution/modules/member-log-panel.vue

261 lines
7.1 KiB
Vue

<script setup lang="ts">
import { computed, reactive, watch } from 'vue';
import { fetchGetProjectExecutionMemberLogPage } from '@/service/api';
import { useUIPaginatedTable } from '@/hooks/common/table';
import BusinessUserSelect from '@/components/custom/business-user-select.vue';
import { formatDateTime, getExecutionMemberActionName, getExecutionMemberActionTagType } from '../shared';
defineOptions({ name: 'ProjectExecutionMemberLogPanel' });
interface Props {
projectId: string;
executionId: string;
userOptions: Api.SystemManage.UserSimple[];
active: boolean;
}
const props = defineProps<Props>();
type ActionType = Api.Project.ExecutionMemberActionType;
const ACTION_TYPE_OPTIONS: Array<{ label: string; value: ActionType }> = [
{ label: getExecutionMemberActionName('join'), value: 'join' },
{ label: getExecutionMemberActionName('inactive'), value: 'inactive' },
{ label: getExecutionMemberActionName('owner_transfer_in'), value: 'owner_transfer_in' },
{ label: getExecutionMemberActionName('owner_transfer_out'), value: 'owner_transfer_out' }
];
const searchParams = reactive<{
pageNo: number;
pageSize: number;
actionTypes?: ActionType[];
userId?: string;
}>({
pageNo: 1,
pageSize: 5,
actionTypes: undefined,
userId: undefined
});
const canLoad = computed(() => Boolean(props.projectId && props.executionId));
type LogPageResponse = Awaited<ReturnType<typeof fetchGetProjectExecutionMemberLogPage>>;
function buildRequestParams(): Api.Project.ExecutionMemberLogSearchParams {
return {
pageNo: searchParams.pageNo,
pageSize: searchParams.pageSize,
actionTypes: searchParams.actionTypes?.length ? searchParams.actionTypes : undefined,
userId: searchParams.userId || undefined
};
}
function transformLogPage(response: LogPageResponse, pageNo: number, pageSize: number) {
if (!response.error && response.data) {
return {
data: response.data.list,
pageNum: pageNo,
pageSize,
total: response.data.total
};
}
return {
data: [],
pageNum: pageNo,
pageSize,
total: 0
};
}
const { data, loading, getDataByPage, mobilePagination } = useUIPaginatedTable<
LogPageResponse,
Api.Project.ExecutionMemberLog
>({
paginationProps: {
currentPage: searchParams.pageNo,
pageSize: searchParams.pageSize
},
api: () => {
if (!canLoad.value) {
return Promise.resolve({
data: { total: 0, list: [] },
error: null
} as unknown as LogPageResponse);
}
return fetchGetProjectExecutionMemberLogPage(props.projectId, props.executionId, buildRequestParams());
},
transform: response => transformLogPage(response, searchParams.pageNo ?? 1, searchParams.pageSize ?? 5),
onPaginationParamsChange: params => {
searchParams.pageNo = params.currentPage ?? 1;
searchParams.pageSize = params.pageSize ?? 5;
},
immediate: false,
columns: () => [{ prop: 'actionTime', label: '时间' }]
});
// 每次切到该 tab 都按当前筛选条件重拉第 1 页,确保用户在"当前成员" tab 操作后回到这里能看到最新事件
watch(
() => props.active,
active => {
if (active && canLoad.value) {
getDataByPage(1);
}
},
{ immediate: true }
);
// 切换到不同执行项时完全清空筛选条件
watch(
() => props.executionId,
() => {
resetSearchParams();
}
);
function resetSearchParams() {
searchParams.pageNo = 1;
searchParams.actionTypes = undefined;
searchParams.userId = undefined;
}
async function handleSearch() {
await getDataByPage(1);
}
async function handleReset() {
resetSearchParams();
await getDataByPage(1);
}
function getMemberDisplay(row: Api.Project.ExecutionMemberLog) {
return row.userNicknameSnapshot?.trim() || row.userId || '--';
}
function getOperatorDisplay(row: Api.Project.ExecutionMemberLog) {
return row.operatorNicknameSnapshot?.trim() || row.operatorUserId || '--';
}
function refresh() {
return getDataByPage(1);
}
defineExpose({ refresh });
</script>
<template>
<div class="member-log-panel">
<div class="member-log-panel__toolbar">
<ElSelect
v-model="searchParams.actionTypes"
multiple
collapse-tags
collapse-tags-tooltip
clearable
placeholder="全部事件"
class="member-log-panel__action-select"
>
<ElOption v-for="item in ACTION_TYPE_OPTIONS" :key="item.value" :label="item.label" :value="item.value" />
</ElSelect>
<BusinessUserSelect
v-model="searchParams.userId"
:options="userOptions"
placeholder="全部成员"
clearable
class="member-log-panel__user-select"
/>
<div class="member-log-panel__actions">
<ElButton @click="handleReset">重置</ElButton>
<ElButton type="primary" @click="handleSearch">查询</ElButton>
</div>
</div>
<ElTable v-loading="loading" :data="data" :height="247" border size="default">
<ElTableColumn label="时间" width="170" align="center">
<template #default="{ row }">
{{ formatDateTime(row.actionTime) }}
</template>
</ElTableColumn>
<ElTableColumn label="事件类型" width="130" align="center">
<template #default="{ row }">
<ElTag :type="getExecutionMemberActionTagType(row.actionType)" effect="light">
{{ getExecutionMemberActionName(row.actionType) }}
</ElTag>
</template>
</ElTableColumn>
<ElTableColumn label="成员" min-width="120" show-overflow-tooltip>
<template #default="{ row }">
{{ getMemberDisplay(row) }}
</template>
</ElTableColumn>
<ElTableColumn label="操作人" min-width="120" show-overflow-tooltip>
<template #default="{ row }">
{{ getOperatorDisplay(row) }}
</template>
</ElTableColumn>
<ElTableColumn label="原因" min-width="200" show-overflow-tooltip>
<template #default="{ row }">
<span v-if="row.reason">{{ row.reason }}</span>
<span v-else class="member-log-panel__empty">--</span>
</template>
</ElTableColumn>
<template #empty>
<ElEmpty description="暂无变更记录" :image-size="80" />
</template>
</ElTable>
<div class="member-log-panel__pagination">
<ElPagination
v-if="mobilePagination.total"
background
layout="total, prev, pager, next"
small
v-bind="mobilePagination"
@current-change="mobilePagination['current-change']"
@size-change="mobilePagination['size-change']"
/>
</div>
</div>
</template>
<style scoped lang="scss">
.member-log-panel {
display: flex;
flex-direction: column;
gap: 12px;
}
.member-log-panel__toolbar {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 12px;
}
.member-log-panel__action-select {
width: 200px;
}
.member-log-panel__user-select {
width: 200px;
}
.member-log-panel__actions {
display: flex;
gap: 12px;
margin-left: auto;
}
.member-log-panel__empty {
color: var(--el-text-color-placeholder);
}
.member-log-panel__pagination {
display: flex;
justify-content: flex-end;
min-height: 32px;
}
</style>