Files
cn-rdms-web/src/views/system/role/modules/role-context-panel.vue
hongawen 4122dfa50d feat(product): 新增产品管理模块与字典组件功能
- 新增产品管理相关路由和页面(dashboard、list、requirement、setting)
- 实现产品基础信息编辑弹窗组件(base-info-dialog.vue)
- 添加运行时字典功能(dict-select、dict-text、dict-tag组件)
- 集成字典管理store和API调用
- 规范ID类型定义为string避免精度丢失问题
- 完善国际化资源文件支持中英文对照
- 新增对象上下文业务域入口页导航实现说明
- 添加Vue DevTools浮动入口注释说明
- 统一权限控制支持全局和对象作用域区分
- 规范分页查询参数类型定义与使用方式
2026-04-23 09:05:55 +08:00

250 lines
6.1 KiB
Vue

<script setup lang="ts">
import { computed } from 'vue';
import { objectTypeRecord, scopeTypeRecord } from '@/constants/business';
import { $t } from '@/locales';
defineOptions({ name: 'RoleContextPanel' });
interface Props {
total?: number;
loading?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
total: 0,
loading: false
});
const scopeType = defineModel<Api.SystemManage.ScopeType>('scopeType', {
required: true
});
const objectType = defineModel<Api.SystemManage.ObjectType | undefined>('objectType');
const isObjectScope = computed(() => scopeType.value === 'object');
const scopeOptions = computed(() => [
{ label: $t(scopeTypeRecord.global), value: 'global' satisfies Api.SystemManage.ScopeType },
{ label: $t(scopeTypeRecord.object), value: 'object' satisfies Api.SystemManage.ScopeType }
]);
const objectTypeOptions = computed(() => [
{ label: $t(objectTypeRecord.product), value: 'product' satisfies Api.SystemManage.ObjectType },
{ label: $t(objectTypeRecord.project), value: 'project' satisfies Api.SystemManage.ObjectType }
]);
const currentContextLabel = computed(() => {
if (!isObjectScope.value) {
return $t(scopeTypeRecord.global);
}
if (!objectType.value) {
return `${$t(scopeTypeRecord.object)} / --`;
}
return `${$t(scopeTypeRecord.object)} / ${$t(objectTypeRecord[objectType.value])}`;
});
const currentScopeSummary = computed(() => {
if (!isObjectScope.value) {
return $t('page.system.role.globalRoleSummary');
}
if (objectType.value === 'product') {
return $t('page.system.role.objectRoleSummaryProduct');
}
if (objectType.value === 'project') {
return $t('page.system.role.objectRoleSummaryProject');
}
return $t('page.system.role.objectRoleSummary');
});
</script>
<template>
<ElCard class="role-context-panel" body-class="role-context-panel__body">
<div v-loading="props.loading" class="role-context-panel__layout">
<div class="role-context-panel__controls">
<div class="role-context-panel__field role-context-panel__field--switch">
<ElSegmented v-model="scopeType" :options="scopeOptions" />
</div>
<span v-if="isObjectScope" class="role-context-panel__divider" aria-hidden="true">|</span>
<div v-if="isObjectScope" class="role-context-panel__field role-context-panel__field--inline">
<span class="role-context-panel__field-label role-context-panel__field-label--inline">
{{ $t('page.system.menu.objectType') }}
</span>
<ElSelect v-model="objectType" class="w-full" :placeholder="$t('page.system.menu.objectTypePlaceholder')">
<ElOption v-for="item in objectTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
</ElSelect>
</div>
</div>
<div class="role-context-panel__info">
<div class="role-context-panel__info-main">
<div class="role-context-panel__info-item">
<span class="role-context-panel__info-label">{{ $t('page.system.menu.currentContext') }}</span>
<strong class="role-context-panel__info-value">{{ currentContextLabel }}</strong>
</div>
<div class="role-context-panel__info-item">
<span class="role-context-panel__info-label">{{ $t('page.system.role.currentRoleCount') }}</span>
<strong class="role-context-panel__info-value">{{ props.total }}</strong>
</div>
</div>
<p class="role-context-panel__info-desc">{{ currentScopeSummary }}</p>
</div>
</div>
</ElCard>
</template>
<style lang="scss" scoped>
.role-context-panel {
border: 1px solid var(--el-border-color-light);
background: var(--el-fill-color-blank);
box-shadow: none;
}
:deep(.role-context-panel__body) {
padding: 16px 18px;
}
.role-context-panel__layout {
display: flex;
align-items: center;
gap: 20px;
}
.role-context-panel__controls {
display: flex;
align-items: center;
gap: 14px;
min-width: 0;
flex-shrink: 0;
}
.role-context-panel__info {
flex: 1;
min-width: 0;
padding-left: 20px;
border-left: 1px solid var(--el-border-color-lighter);
}
.role-context-panel__field {
display: flex;
align-items: center;
gap: 10px;
}
.role-context-panel__field--switch {
flex-shrink: 0;
}
:deep(.role-context-panel__field--switch .el-segmented) {
width: auto;
padding: 6px;
background: var(--el-fill-color-light);
border-radius: 12px;
}
:deep(.role-context-panel__field--switch .el-segmented__item) {
min-height: 40px;
min-width: 96px;
padding: 0 22px;
font-size: 14px;
}
.role-context-panel__field-label {
color: var(--el-text-color-secondary);
font-size: 13px;
line-height: 1.5;
white-space: nowrap;
}
.role-context-panel__field--inline {
min-width: 0;
}
.role-context-panel__field-label--inline {
flex-shrink: 0;
}
.role-context-panel__divider {
color: var(--el-border-color-darker);
font-size: 16px;
line-height: 1;
user-select: none;
}
:deep(.role-context-panel__field--inline .el-select) {
width: 170px;
}
.role-context-panel__info-main {
display: flex;
flex-wrap: wrap;
gap: 24px;
}
.role-context-panel__info-item {
display: flex;
align-items: baseline;
gap: 10px;
min-width: 180px;
}
.role-context-panel__info-label {
color: var(--el-text-color-secondary);
font-size: 12px;
line-height: 1.5;
}
.role-context-panel__info-value {
color: var(--el-text-color-primary);
font-size: 15px;
font-weight: 600;
line-height: 1.5;
}
.role-context-panel__info-desc {
margin-top: 10px;
color: var(--el-text-color-regular);
font-size: 13px;
line-height: 1.6;
}
@media (width <= 1200px) {
.role-context-panel__layout {
flex-direction: column;
align-items: stretch;
}
.role-context-panel__controls {
width: 100%;
justify-content: flex-start;
}
.role-context-panel__info {
padding-left: 0;
padding-top: 14px;
border-left: none;
border-top: 1px solid var(--el-border-color-lighter);
}
}
@media (width <= 768px) {
.role-context-panel__controls {
flex-wrap: wrap;
}
.role-context-panel__info-item {
width: 100%;
min-width: 0;
gap: 8px;
justify-content: space-between;
}
}
</style>