Files
cn-rdms-web/src/views/product/setting/modules/setting-team-panel.vue

278 lines
8.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup lang="ts">
import { computed, ref, watch } from 'vue';
import type { TableInstance } from 'element-plus';
import { filterProductMembers, formatProductMemberDate, getProductTeamTableHeight } from '../shared';
defineOptions({ name: 'SettingTeamPanel' });
interface Props {
members: Api.Product.ProductMember[];
roleOptions?: Api.SystemManage.RoleSimple[];
loading?: boolean;
readonly?: boolean;
}
interface Emits {
(e: 'create'): void;
(e: 'edit', member: Api.Product.ProductMember): void;
(e: 'remove', member: Api.Product.ProductMember): void;
(e: 'batch-remove', members: Api.Product.ProductMember[]): void;
}
const props = withDefaults(defineProps<Props>(), {
loading: false,
readonly: false,
roleOptions: () => []
});
const emit = defineEmits<Emits>();
const searchKeyword = ref('');
const selectedRoleId = ref('');
const teamTableHeight = getProductTeamTableHeight(5);
const tableRef = ref<TableInstance | null>(null);
const selectedRows = ref<Api.Product.ProductMember[]>([]);
const selectedCount = computed(() => selectedRows.value.length);
function isRowSelectable(row: Api.Product.ProductMember) {
return row.status === 0 && !row.managerFlag;
}
function handleSelectionChange(rows: Api.Product.ProductMember[]) {
selectedRows.value = rows;
}
function handleBatchRemove() {
if (!selectedRows.value.length) return;
emit('batch-remove', [...selectedRows.value]);
}
function clearSelection() {
tableRef.value?.clearSelection();
selectedRows.value = [];
}
defineExpose({ clearSelection });
const roleFilterOptions = computed(() => {
const seen = new Set<string>();
const result: Api.SystemManage.RoleSimple[] = [];
props.roleOptions.forEach(role => {
if (role.visible === 0) return;
if (seen.has(role.id)) return;
seen.add(role.id);
result.push(role);
});
return result;
});
const filteredMembers = computed(() =>
filterProductMembers(props.members, {
keyword: searchKeyword.value,
roleId: selectedRoleId.value
})
);
const hasFilter = computed(() => Boolean(searchKeyword.value.trim() || selectedRoleId.value));
watch(roleFilterOptions, options => {
if (selectedRoleId.value && !options.some(item => item.id === selectedRoleId.value)) {
selectedRoleId.value = '';
}
});
function getMemberStatusLabel(status: Api.Product.ProductMemberStatus) {
return status === 0 ? '有效' : '失效';
}
function getMemberStatusTagType(status: Api.Product.ProductMemberStatus) {
return status === 0 ? 'success' : 'info';
}
</script>
<template>
<ElCard class="card-wrapper">
<template #header>
<div class="setting-team-panel__header">
<div>
<h3 class="text-16px text-[#0f172a] font-700">团队管理</h3>
</div>
<div class="setting-team-panel__toolbar">
<ElSelect v-model="selectedRoleId" clearable placeholder="筛选角色" class="setting-team-panel__role-filter">
<ElOption v-for="role in roleFilterOptions" :key="role.id" :label="role.name" :value="role.id">
<div class="setting-team-panel__role-option">
<span class="setting-team-panel__role-option-name">{{ role.name }}</span>
<ElTooltip v-if="role.remark" :content="role.remark" placement="right" :show-after="120">
<icon-ep:info-filled class="setting-team-panel__role-option-info" @click.stop />
</ElTooltip>
</div>
</ElOption>
</ElSelect>
<ElInput v-model="searchKeyword" clearable placeholder="搜索成员姓名" class="setting-team-panel__search" />
<ElButton
v-if="!props.readonly"
v-auth="{ code: 'project:product:update', source: 'object' }"
type="primary"
plain
@click="emit('create')"
>
新增成员
</ElButton>
<ElButton
v-if="!props.readonly"
v-auth="{ code: 'project:product:update', source: 'object' }"
type="danger"
plain
:disabled="selectedCount === 0"
@click="handleBatchRemove"
>
批量移出{{ selectedCount > 0 ? `${selectedCount}` : '' }}
</ElButton>
</div>
</div>
</template>
<ElTable
ref="tableRef"
v-loading="props.loading"
:data="filteredMembers"
:height="teamTableHeight"
:empty-text="hasFilter ? '未找到匹配成员' : '暂无成员'"
border
row-key="id"
@selection-change="handleSelectionChange"
>
<ElTableColumn
v-if="!props.readonly"
type="selection"
width="48"
align="center"
:selectable="(row: Api.Product.ProductMember) => isRowSelectable(row)"
/>
<ElTableColumn type="index" label="序号" width="64" align="center" />
<ElTableColumn prop="userNickname" label="成员姓名" min-width="140" />
<ElTableColumn label="当前角色" min-width="180">
<template #default="{ row }">
{{ row.roleName || '--' }}
</template>
</ElTableColumn>
<ElTableColumn label="成员状态" width="110" align="center">
<template #default="{ row }">
<ElTag :type="getMemberStatusTagType(row.status)">{{ getMemberStatusLabel(row.status) }}</ElTag>
</template>
</ElTableColumn>
<ElTableColumn prop="joinedTime" label="加入时间" min-width="132" align="center">
<template #default="{ row }">
{{ formatProductMemberDate(row.joinedTime) }}
</template>
</ElTableColumn>
<ElTableColumn prop="leftTime" label="退出时间" min-width="170">
<template #default="{ row }">
{{ formatProductMemberDate(row.leftTime) }}
</template>
</ElTableColumn>
<ElTableColumn prop="remark" label="备注" min-width="180" show-overflow-tooltip>
<template #default="{ row }">
{{ row.remark || '--' }}
</template>
</ElTableColumn>
<ElTableColumn v-if="!props.readonly" label="操作" width="180" fixed="right" align="center">
<template #default="{ row }">
<div class="setting-team-panel__actions">
<ElButton
v-auth="{ code: 'project:product:update', source: 'object' }"
link
type="primary"
:disabled="row.status !== 0 || row.managerFlag"
@click="emit('edit', row)"
>
编辑
</ElButton>
<ElButton
v-auth="{ code: 'project:product:update', source: 'object' }"
link
type="danger"
:disabled="row.status !== 0 || row.managerFlag"
@click="emit('remove', row)"
>
移出成员
</ElButton>
</div>
</template>
</ElTableColumn>
</ElTable>
</ElCard>
</template>
<style scoped>
.setting-team-panel__header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.setting-team-panel__toolbar {
display: inline-flex;
align-items: center;
gap: 12px;
}
.setting-team-panel__search {
width: 220px;
}
.setting-team-panel__role-filter {
width: 180px;
}
.setting-team-panel__actions {
display: inline-flex;
align-items: center;
gap: 12px;
}
.setting-team-panel__role-option {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
width: 100%;
}
.setting-team-panel__role-option-name {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.setting-team-panel__role-option-info {
flex-shrink: 0;
font-size: 14px;
color: var(--el-text-color-placeholder);
cursor: help;
}
.setting-team-panel__role-option-info:hover {
color: var(--el-color-primary);
}
@media (width <= 768px) {
.setting-team-panel__header {
align-items: flex-start;
flex-direction: column;
}
.setting-team-panel__toolbar {
width: 100%;
}
.setting-team-panel__search {
width: 100%;
}
.setting-team-panel__role-filter {
width: 100%;
}
}
</style>