feat(product): 新增产品管理模块与字典组件功能
- 新增产品管理相关路由和页面(dashboard、list、requirement、setting) - 实现产品基础信息编辑弹窗组件(base-info-dialog.vue) - 添加运行时字典功能(dict-select、dict-text、dict-tag组件) - 集成字典管理store和API调用 - 规范ID类型定义为string避免精度丢失问题 - 完善国际化资源文件支持中英文对照 - 新增对象上下文业务域入口页导航实现说明 - 添加Vue DevTools浮动入口注释说明 - 统一权限控制支持全局和对象作用域区分 - 规范分页查询参数类型定义与使用方式
This commit is contained in:
201
src/views/product/setting/modules/setting-team-panel.vue
Normal file
201
src/views/product/setting/modules/setting-team-panel.vue
Normal file
@@ -0,0 +1,201 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue';
|
||||
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;
|
||||
}
|
||||
|
||||
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 roleFilterOptions = computed(() => {
|
||||
const roleMap = new Map<string, string>();
|
||||
|
||||
props.roleOptions.forEach(role => {
|
||||
if (!roleMap.has(role.id)) {
|
||||
roleMap.set(role.id, role.name);
|
||||
}
|
||||
});
|
||||
|
||||
return [...roleMap.entries()].map(([value, label]) => ({
|
||||
value,
|
||||
label
|
||||
}));
|
||||
});
|
||||
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.value === 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="option in roleFilterOptions"
|
||||
:key="option.value"
|
||||
:label="option.label"
|
||||
:value="option.value"
|
||||
/>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<ElTable
|
||||
v-loading="props.loading"
|
||||
:data="filteredMembers"
|
||||
:height="teamTableHeight"
|
||||
:empty-text="hasFilter ? '未找到匹配成员' : '暂无成员'"
|
||||
border
|
||||
row-key="id"
|
||||
>
|
||||
<ElTableColumn type="index" label="序号" width="64" align="center" />
|
||||
<ElTableColumn prop="userNickname" label="成员姓名" min-width="140" />
|
||||
<ElTableColumn prop="roleName" label="当前角色" min-width="140" />
|
||||
<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;
|
||||
}
|
||||
|
||||
@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>
|
||||
Reference in New Issue
Block a user