- 新增产品管理相关路由和页面(dashboard、list、requirement、setting) - 实现产品基础信息编辑弹窗组件(base-info-dialog.vue) - 添加运行时字典功能(dict-select、dict-text、dict-tag组件) - 集成字典管理store和API调用 - 规范ID类型定义为string避免精度丢失问题 - 完善国际化资源文件支持中英文对照 - 新增对象上下文业务域入口页导航实现说明 - 添加Vue DevTools浮动入口注释说明 - 统一权限控制支持全局和对象作用域区分 - 规范分页查询参数类型定义与使用方式
868 lines
24 KiB
Vue
868 lines
24 KiB
Vue
<script setup lang="ts">
|
||
import { computed, ref, watch } from 'vue';
|
||
import { OBJECT_CONTEXT_QUERY_KEY } from '@/constants/object-context';
|
||
import { fetchGetProduct, fetchGetProductMembers, fetchGetProductSettings } from '@/service/api';
|
||
import { useRouterPush } from '@/hooks/common/router';
|
||
import { useCurrentProduct } from '../shared/use-current-product';
|
||
import { getProductLifecycleStatusSummary } from '../setting/shared';
|
||
import { getProductStatusLabel, getProductStatusTagType } from '../shared/product-master-data';
|
||
import {
|
||
getProductDashboardActivityItems,
|
||
getProductDashboardGrowthModules,
|
||
getProductDashboardMetricCards,
|
||
getProductDashboardQuickLinks,
|
||
getProductDashboardRdMilestonePlaceholder,
|
||
getProductDashboardRecentActivityPlaceholder,
|
||
getProductDashboardTeamSummary
|
||
} from './shared';
|
||
|
||
defineOptions({ name: 'ProductDashboard' });
|
||
|
||
const { currentObjectId } = useCurrentProduct();
|
||
const { routerPush, routerPushByKey } = useRouterPush();
|
||
|
||
const pageLoading = ref(false);
|
||
const productDetail = ref<Api.Product.Product | null>(null);
|
||
const settings = ref<Api.Product.ProductSettings | null>(null);
|
||
const members = ref<Api.Product.ProductMember[]>([]);
|
||
|
||
const recentActivityPlaceholder = getProductDashboardRecentActivityPlaceholder();
|
||
const rdMilestonePlaceholder = getProductDashboardRdMilestonePlaceholder();
|
||
const growthModules = getProductDashboardGrowthModules();
|
||
const quickLinks = getProductDashboardQuickLinks();
|
||
const lifecycleTrackItems: Api.Product.ProductStatusCode[] = ['active', 'paused', 'archived', 'abandoned'];
|
||
|
||
const metricCards = computed(() => getProductDashboardMetricCards(settings.value, members.value));
|
||
const statusMetricCard = computed(() => metricCards.value.find(item => item.key === 'status') || null);
|
||
const secondaryMetricCards = computed(() => metricCards.value.filter(item => item.key !== 'status'));
|
||
const teamSummary = computed(() => getProductDashboardTeamSummary(settings.value, members.value));
|
||
const activityItems = computed(() =>
|
||
getProductDashboardActivityItems(productDetail.value, settings.value, members.value)
|
||
);
|
||
const lifecycle = computed(() => settings.value?.lifecycle || null);
|
||
const lifecycleSummary = computed(() =>
|
||
lifecycle.value ? getProductLifecycleStatusSummary(lifecycle.value.statusCode) : null
|
||
);
|
||
const lifecycleReason = computed(
|
||
() => lifecycle.value?.lastStatusReason || settings.value?.baseInfo.lastStatusReason || ''
|
||
);
|
||
|
||
async function loadDashboardData(objectId: string) {
|
||
pageLoading.value = true;
|
||
|
||
try {
|
||
const [productResult, settingsResult, membersResult] = await Promise.all([
|
||
fetchGetProduct(objectId),
|
||
fetchGetProductSettings(objectId),
|
||
fetchGetProductMembers(objectId)
|
||
]);
|
||
|
||
productDetail.value = productResult.error ? null : productResult.data || null;
|
||
settings.value = settingsResult.error ? null : settingsResult.data || null;
|
||
members.value = membersResult.error ? [] : membersResult.data || [];
|
||
} finally {
|
||
pageLoading.value = false;
|
||
}
|
||
}
|
||
|
||
async function goToQuickLink(target: 'requirement' | 'setting' | 'list') {
|
||
if (target === 'list') {
|
||
await routerPush({
|
||
path: '/product/list'
|
||
});
|
||
|
||
return;
|
||
}
|
||
|
||
if (!currentObjectId.value) {
|
||
return;
|
||
}
|
||
|
||
if (target === 'requirement') {
|
||
await routerPushByKey('product_requirement', {
|
||
query: {
|
||
[OBJECT_CONTEXT_QUERY_KEY]: currentObjectId.value
|
||
}
|
||
});
|
||
|
||
return;
|
||
}
|
||
|
||
await routerPushByKey('product_setting', {
|
||
query: {
|
||
[OBJECT_CONTEXT_QUERY_KEY]: currentObjectId.value
|
||
}
|
||
});
|
||
}
|
||
|
||
watch(
|
||
() => currentObjectId.value,
|
||
async objectId => {
|
||
if (!objectId) {
|
||
productDetail.value = null;
|
||
settings.value = null;
|
||
members.value = [];
|
||
return;
|
||
}
|
||
|
||
await loadDashboardData(objectId);
|
||
},
|
||
{ immediate: true }
|
||
);
|
||
</script>
|
||
|
||
<template>
|
||
<div v-loading="pageLoading" class="product-dashboard-page">
|
||
<section class="product-dashboard-page__metrics">
|
||
<article
|
||
v-if="statusMetricCard"
|
||
class="dashboard-status-hero"
|
||
:class="[lifecycle ? `dashboard-status-hero--${lifecycle.statusCode}` : 'dashboard-status-hero--default']"
|
||
>
|
||
<div class="dashboard-status-hero__head">
|
||
<span class="dashboard-status-hero__label">{{ statusMetricCard.label }}</span>
|
||
<span class="dashboard-status-hero__meta-chip">{{ lifecycle?.availableActions.length || 0 }} 项动作</span>
|
||
</div>
|
||
|
||
<div class="dashboard-status-hero__body">
|
||
<strong class="dashboard-status-hero__value">{{ statusMetricCard.value }}</strong>
|
||
<p class="dashboard-status-hero__reason">
|
||
{{ lifecycleReason || '当前没有补充状态原因' }}
|
||
</p>
|
||
</div>
|
||
</article>
|
||
|
||
<div class="dashboard-metric-stack">
|
||
<article v-for="card in secondaryMetricCards" :key="card.key" class="dashboard-metric-card">
|
||
<span class="dashboard-metric-card__label">{{ card.label }}</span>
|
||
<strong class="dashboard-metric-card__value">{{ card.value }}</strong>
|
||
</article>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="product-dashboard-page__main">
|
||
<div class="product-dashboard-page__primary">
|
||
<ElCard class="card-wrapper">
|
||
<template #header>
|
||
<div>
|
||
<h3 class="text-16px text-[#0f172a] font-700">{{ recentActivityPlaceholder.title }}</h3>
|
||
<p class="mt-4px text-13px text-[#64748b]">当前先展示产品主数据、状态与团队关系可确认的已知动态。</p>
|
||
</div>
|
||
</template>
|
||
|
||
<div v-if="activityItems.length" class="dashboard-activity-list">
|
||
<div
|
||
v-for="item in activityItems"
|
||
:key="item.key"
|
||
class="dashboard-activity-item"
|
||
:class="[`dashboard-activity-item--${item.tone}`]"
|
||
>
|
||
<div class="dashboard-activity-item__meta">
|
||
<span class="dashboard-activity-item__tag">{{ item.tag }}</span>
|
||
<span class="dashboard-activity-item__time">{{ item.time }}</span>
|
||
</div>
|
||
<strong class="dashboard-activity-item__title">{{ item.title }}</strong>
|
||
<p class="dashboard-activity-item__content">{{ item.content }}</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-else class="dashboard-placeholder-panel">
|
||
<p class="dashboard-placeholder-panel__description">{{ recentActivityPlaceholder.description }}</p>
|
||
<div class="dashboard-placeholder-panel__items">
|
||
<div
|
||
v-for="item in recentActivityPlaceholder.items"
|
||
:key="item"
|
||
class="dashboard-placeholder-panel__item"
|
||
>
|
||
<span class="dashboard-placeholder-panel__dot" />
|
||
<span>{{ item }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</ElCard>
|
||
|
||
<ElCard class="card-wrapper">
|
||
<template #header>
|
||
<div>
|
||
<h3 class="text-16px text-[#0f172a] font-700">生命周期概览</h3>
|
||
<p class="mt-4px text-13px text-[#64748b]">
|
||
{{ lifecycleSummary?.description || '当前未获取到生命周期信息。' }}
|
||
</p>
|
||
</div>
|
||
</template>
|
||
|
||
<div v-if="lifecycle" class="dashboard-lifecycle">
|
||
<div class="dashboard-lifecycle__summary">
|
||
<div class="dashboard-lifecycle__summary-main">
|
||
<ElTag :type="getProductStatusTagType(lifecycle.statusCode)" round effect="light">
|
||
{{ getProductStatusLabel(lifecycle.statusCode) }}
|
||
</ElTag>
|
||
<strong class="dashboard-lifecycle__summary-title">
|
||
{{ lifecycleSummary?.caption || '当前状态待确认' }}
|
||
</strong>
|
||
</div>
|
||
<p class="dashboard-lifecycle__reason">最近状态原因:{{ lifecycleReason || '暂无记录' }}</p>
|
||
</div>
|
||
|
||
<div class="dashboard-lifecycle__actions">
|
||
<div
|
||
v-for="action in lifecycle.availableActions"
|
||
:key="action.actionCode"
|
||
class="dashboard-lifecycle__action-card"
|
||
>
|
||
<strong class="dashboard-lifecycle__action-name">{{ action.actionName }}</strong>
|
||
<span class="dashboard-lifecycle__action-hint">
|
||
{{ action.needReason ? '提交时需填写原因' : '提交时原因可选' }}
|
||
</span>
|
||
</div>
|
||
|
||
<ElEmpty
|
||
v-if="!lifecycle.availableActions.length"
|
||
description="当前状态下没有可执行动作"
|
||
:image-size="68"
|
||
/>
|
||
</div>
|
||
|
||
<div class="dashboard-lifecycle__track">
|
||
<div
|
||
v-for="status in lifecycleTrackItems"
|
||
:key="status"
|
||
class="dashboard-lifecycle__track-item"
|
||
:class="[{ 'dashboard-lifecycle__track-item--active': lifecycle.statusCode === status }]"
|
||
>
|
||
{{ getProductStatusLabel(status) }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<ElEmpty v-else description="未获取到产品生命周期信息" :image-size="76" />
|
||
</ElCard>
|
||
</div>
|
||
|
||
<div class="product-dashboard-page__secondary">
|
||
<ElCard class="card-wrapper">
|
||
<template #header>
|
||
<div>
|
||
<h3 class="text-16px text-[#0f172a] font-700">团队摘要</h3>
|
||
<p class="mt-4px text-13px text-[#64748b]">当前先展示有效成员、负责人和角色分布摘要。</p>
|
||
</div>
|
||
</template>
|
||
|
||
<div class="dashboard-team-card">
|
||
<div class="dashboard-team-card__stat-grid">
|
||
<div class="dashboard-team-card__stat">
|
||
<span class="dashboard-team-card__stat-label">当前经理</span>
|
||
<strong class="dashboard-team-card__stat-value">{{ teamSummary.managerDisplayName }}</strong>
|
||
</div>
|
||
<div class="dashboard-team-card__stat">
|
||
<span class="dashboard-team-card__stat-label">有效成员</span>
|
||
<strong class="dashboard-team-card__stat-value">{{ teamSummary.activeMemberCount }} 人</strong>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="dashboard-team-card__detail">
|
||
<span class="dashboard-team-card__detail-label">最近加入</span>
|
||
<strong class="dashboard-team-card__detail-value">{{ teamSummary.latestJoinedMemberLabel }}</strong>
|
||
</div>
|
||
|
||
<div class="dashboard-team-card__roles">
|
||
<span class="dashboard-team-card__detail-label">角色分布</span>
|
||
<div v-if="teamSummary.roleSummaries.length" class="dashboard-team-card__role-list">
|
||
<span v-for="item in teamSummary.roleSummaries" :key="item" class="dashboard-team-card__role-chip">
|
||
{{ item }}
|
||
</span>
|
||
</div>
|
||
<ElEmpty v-else description="当前暂无有效团队成员" :image-size="64" />
|
||
</div>
|
||
</div>
|
||
</ElCard>
|
||
|
||
<ElCard class="card-wrapper">
|
||
<template #header>
|
||
<div>
|
||
<h3 class="text-16px text-[#0f172a] font-700">快捷入口</h3>
|
||
<p class="mt-4px text-13px text-[#64748b]">首页只做导流,不在这里承接重表单和重列表。</p>
|
||
</div>
|
||
</template>
|
||
|
||
<div class="dashboard-link-list">
|
||
<button
|
||
v-for="link in quickLinks"
|
||
:key="link.key"
|
||
type="button"
|
||
class="dashboard-link-list__item"
|
||
@click="goToQuickLink(link.key)"
|
||
>
|
||
<strong class="dashboard-link-list__title">{{ link.label }}</strong>
|
||
<span class="dashboard-link-list__desc">{{ link.description }}</span>
|
||
</button>
|
||
</div>
|
||
</ElCard>
|
||
|
||
<ElCard class="card-wrapper">
|
||
<template #header>
|
||
<div>
|
||
<h3 class="text-16px text-[#0f172a] font-700">{{ rdMilestonePlaceholder.title }}</h3>
|
||
<p class="mt-4px text-13px text-[#64748b]">对象档案补充位,后续接真实聚合数据后直接替换。</p>
|
||
</div>
|
||
</template>
|
||
|
||
<div class="dashboard-placeholder-panel dashboard-placeholder-panel--compact">
|
||
<p class="dashboard-placeholder-panel__description">
|
||
{{ rdMilestonePlaceholder.description }}
|
||
</p>
|
||
<div class="dashboard-placeholder-panel__items">
|
||
<div v-for="item in rdMilestonePlaceholder.items" :key="item" class="dashboard-placeholder-panel__item">
|
||
<span class="dashboard-placeholder-panel__dot" />
|
||
<span>{{ item }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</ElCard>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="product-dashboard-page__growth">
|
||
<ElCard v-for="module in growthModules" :key="module.key" class="card-wrapper">
|
||
<template #header>
|
||
<div>
|
||
<h3 class="text-16px text-[#0f172a] font-700">{{ module.title }}</h3>
|
||
<p class="mt-4px text-13px text-[#64748b]">当前保留正式布局位,后续可直接接入真实统计。</p>
|
||
</div>
|
||
</template>
|
||
|
||
<div class="dashboard-growth-card">
|
||
<p class="dashboard-growth-card__description">{{ module.description }}</p>
|
||
<div class="dashboard-growth-card__indicators">
|
||
<div v-for="item in module.indicators" :key="item" class="dashboard-growth-card__indicator">
|
||
<span class="dashboard-growth-card__indicator-label">{{ item }}</span>
|
||
<strong class="dashboard-growth-card__indicator-value">--</strong>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</ElCard>
|
||
</section>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.product-dashboard-page {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 16px;
|
||
}
|
||
|
||
.product-dashboard-page__metrics {
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
|
||
gap: 12px;
|
||
}
|
||
|
||
.dashboard-status-hero {
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: space-between;
|
||
min-height: 152px;
|
||
padding: 18px 20px;
|
||
border: 1px solid rgb(226 232 240 / 88%);
|
||
border-radius: 18px;
|
||
}
|
||
|
||
.dashboard-status-hero--default {
|
||
background: linear-gradient(135deg, rgb(248 250 252 / 98%), rgb(255 255 255 / 98%));
|
||
}
|
||
|
||
.dashboard-status-hero--active {
|
||
border-color: rgb(167 243 208 / 92%);
|
||
background:
|
||
radial-gradient(circle at top right, rgb(16 185 129 / 16%), transparent 38%),
|
||
linear-gradient(135deg, rgb(236 253 245 / 98%), rgb(255 255 255 / 98%));
|
||
}
|
||
|
||
.dashboard-status-hero--paused {
|
||
border-color: rgb(253 230 138 / 92%);
|
||
background:
|
||
radial-gradient(circle at top right, rgb(245 158 11 / 16%), transparent 38%),
|
||
linear-gradient(135deg, rgb(255 251 235 / 98%), rgb(255 255 255 / 98%));
|
||
}
|
||
|
||
.dashboard-status-hero--archived {
|
||
border-color: rgb(203 213 225 / 92%);
|
||
background:
|
||
radial-gradient(circle at top right, rgb(100 116 139 / 14%), transparent 38%),
|
||
linear-gradient(135deg, rgb(248 250 252 / 98%), rgb(255 255 255 / 98%));
|
||
}
|
||
|
||
.dashboard-status-hero--abandoned {
|
||
border-color: rgb(254 205 211 / 92%);
|
||
background:
|
||
radial-gradient(circle at top right, rgb(244 63 94 / 14%), transparent 38%),
|
||
linear-gradient(135deg, rgb(255 241 242 / 98%), rgb(255 255 255 / 98%));
|
||
}
|
||
|
||
.dashboard-status-hero__head {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 12px;
|
||
}
|
||
|
||
.dashboard-status-hero__label {
|
||
color: rgb(100 116 139 / 92%);
|
||
font-size: 12px;
|
||
line-height: 1;
|
||
}
|
||
|
||
.dashboard-status-hero__meta-chip {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
min-height: 28px;
|
||
padding: 0 10px;
|
||
border-radius: 999px;
|
||
background-color: rgb(255 255 255 / 78%);
|
||
color: rgb(51 65 85 / 92%);
|
||
font-size: 12px;
|
||
}
|
||
|
||
.dashboard-status-hero__body {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
}
|
||
|
||
.dashboard-status-hero__value {
|
||
color: rgb(15 23 42 / 98%);
|
||
font-size: 38px;
|
||
line-height: 1.1;
|
||
letter-spacing: -0.02em;
|
||
}
|
||
|
||
.dashboard-status-hero__reason {
|
||
color: rgb(51 65 85 / 92%);
|
||
font-size: 14px;
|
||
line-height: 1.75;
|
||
}
|
||
|
||
.dashboard-metric-stack {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||
gap: 12px;
|
||
}
|
||
|
||
.dashboard-metric-card {
|
||
display: flex;
|
||
min-height: 152px;
|
||
flex-direction: column;
|
||
justify-content: space-between;
|
||
padding: 18px;
|
||
border: 1px solid rgb(226 232 240 / 88%);
|
||
border-radius: 18px;
|
||
background: linear-gradient(180deg, rgb(255 255 255 / 99%), rgb(248 250 252 / 97%));
|
||
}
|
||
|
||
.dashboard-metric-card__label {
|
||
color: rgb(100 116 139 / 92%);
|
||
font-size: 12px;
|
||
line-height: 1;
|
||
}
|
||
|
||
.dashboard-metric-card__value {
|
||
color: rgb(15 23 42 / 96%);
|
||
font-size: 22px;
|
||
line-height: 1.2;
|
||
word-break: break-word;
|
||
}
|
||
|
||
.dashboard-metric-card--manager .dashboard-metric-card__value {
|
||
font-size: 18px;
|
||
}
|
||
|
||
.product-dashboard-page__main {
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 2fr) minmax(320px, 1fr);
|
||
gap: 16px;
|
||
}
|
||
|
||
.product-dashboard-page__primary,
|
||
.product-dashboard-page__secondary {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 16px;
|
||
}
|
||
|
||
.dashboard-placeholder-panel {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 14px;
|
||
padding: 4px 2px;
|
||
}
|
||
|
||
.dashboard-placeholder-panel--compact {
|
||
gap: 12px;
|
||
}
|
||
|
||
.dashboard-placeholder-panel__description {
|
||
color: rgb(71 85 105 / 95%);
|
||
font-size: 14px;
|
||
line-height: 1.75;
|
||
}
|
||
|
||
.dashboard-placeholder-panel__items {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
}
|
||
|
||
.dashboard-placeholder-panel__item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
padding: 12px 14px;
|
||
border-radius: 14px;
|
||
background-color: rgb(248 250 252 / 94%);
|
||
color: rgb(15 23 42 / 92%);
|
||
font-size: 14px;
|
||
}
|
||
|
||
.dashboard-placeholder-panel__dot {
|
||
width: 8px;
|
||
height: 8px;
|
||
border-radius: 999px;
|
||
background-color: rgb(14 165 233 / 86%);
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.dashboard-activity-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
}
|
||
|
||
.dashboard-activity-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
padding: 16px;
|
||
border: 1px solid rgb(226 232 240 / 90%);
|
||
border-left-width: 4px;
|
||
border-radius: 16px;
|
||
background-color: rgb(255 255 255 / 96%);
|
||
}
|
||
|
||
.dashboard-activity-item--sky {
|
||
border-left-color: rgb(14 165 233 / 88%);
|
||
}
|
||
|
||
.dashboard-activity-item--emerald {
|
||
border-left-color: rgb(5 150 105 / 88%);
|
||
}
|
||
|
||
.dashboard-activity-item--amber {
|
||
border-left-color: rgb(217 119 6 / 88%);
|
||
}
|
||
|
||
.dashboard-activity-item--rose {
|
||
border-left-color: rgb(225 29 72 / 88%);
|
||
}
|
||
|
||
.dashboard-activity-item--slate {
|
||
border-left-color: rgb(71 85 105 / 88%);
|
||
}
|
||
|
||
.dashboard-activity-item__meta {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 8px;
|
||
}
|
||
|
||
.dashboard-activity-item__tag {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
height: 26px;
|
||
padding: 0 10px;
|
||
border-radius: 999px;
|
||
background-color: rgb(241 245 249 / 96%);
|
||
color: rgb(51 65 85 / 94%);
|
||
font-size: 12px;
|
||
}
|
||
|
||
.dashboard-activity-item__time {
|
||
color: rgb(100 116 139 / 90%);
|
||
font-size: 12px;
|
||
}
|
||
|
||
.dashboard-activity-item__title {
|
||
color: rgb(15 23 42 / 96%);
|
||
font-size: 15px;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.dashboard-activity-item__content {
|
||
color: rgb(71 85 105 / 94%);
|
||
font-size: 14px;
|
||
line-height: 1.75;
|
||
}
|
||
|
||
.dashboard-lifecycle {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 16px;
|
||
}
|
||
|
||
.dashboard-lifecycle__summary {
|
||
padding: 16px;
|
||
border-radius: 16px;
|
||
background-color: rgb(248 250 252 / 96%);
|
||
}
|
||
|
||
.dashboard-lifecycle__summary-main {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.dashboard-lifecycle__summary-title {
|
||
color: rgb(15 23 42 / 96%);
|
||
font-size: 16px;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.dashboard-lifecycle__reason {
|
||
color: rgb(71 85 105 / 94%);
|
||
font-size: 14px;
|
||
line-height: 1.7;
|
||
}
|
||
|
||
.dashboard-lifecycle__actions {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||
gap: 12px;
|
||
}
|
||
|
||
.dashboard-lifecycle__action-card {
|
||
display: flex;
|
||
min-height: 96px;
|
||
flex-direction: column;
|
||
justify-content: space-between;
|
||
padding: 14px;
|
||
border: 1px solid rgb(226 232 240 / 92%);
|
||
border-radius: 16px;
|
||
background-color: rgb(255 255 255 / 94%);
|
||
}
|
||
|
||
.dashboard-lifecycle__action-name {
|
||
color: rgb(15 23 42 / 95%);
|
||
font-size: 15px;
|
||
}
|
||
|
||
.dashboard-lifecycle__action-hint {
|
||
color: rgb(100 116 139 / 90%);
|
||
font-size: 12px;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.dashboard-lifecycle__track {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 10px;
|
||
}
|
||
|
||
.dashboard-lifecycle__track-item {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
height: 36px;
|
||
padding: 0 14px;
|
||
border-radius: 999px;
|
||
background-color: rgb(241 245 249 / 98%);
|
||
color: rgb(71 85 105 / 96%);
|
||
font-size: 13px;
|
||
}
|
||
|
||
.dashboard-lifecycle__track-item--active {
|
||
background-color: rgb(15 23 42 / 92%);
|
||
color: #fff;
|
||
}
|
||
|
||
.dashboard-team-card {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 16px;
|
||
}
|
||
|
||
.dashboard-team-card__stat-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
gap: 12px;
|
||
}
|
||
|
||
.dashboard-team-card__stat {
|
||
display: flex;
|
||
min-height: 92px;
|
||
flex-direction: column;
|
||
justify-content: space-between;
|
||
padding: 14px;
|
||
border-radius: 16px;
|
||
background-color: rgb(248 250 252 / 96%);
|
||
}
|
||
|
||
.dashboard-team-card__stat-label,
|
||
.dashboard-team-card__detail-label {
|
||
color: rgb(100 116 139 / 90%);
|
||
font-size: 12px;
|
||
}
|
||
|
||
.dashboard-team-card__stat-value,
|
||
.dashboard-team-card__detail-value {
|
||
color: rgb(15 23 42 / 96%);
|
||
font-size: 18px;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.dashboard-team-card__detail {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6px;
|
||
}
|
||
|
||
.dashboard-team-card__roles {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
}
|
||
|
||
.dashboard-team-card__role-list {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 10px;
|
||
}
|
||
|
||
.dashboard-team-card__role-chip {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
min-height: 34px;
|
||
padding: 0 12px;
|
||
border-radius: 999px;
|
||
background-color: rgb(239 246 255 / 96%);
|
||
color: rgb(30 64 175 / 92%);
|
||
font-size: 13px;
|
||
}
|
||
|
||
.dashboard-link-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
}
|
||
|
||
.dashboard-link-list__item {
|
||
display: flex;
|
||
width: 100%;
|
||
flex-direction: column;
|
||
gap: 6px;
|
||
padding: 14px 16px;
|
||
border: 1px solid rgb(226 232 240 / 90%);
|
||
border-radius: 16px;
|
||
background-color: rgb(255 255 255 / 98%);
|
||
text-align: left;
|
||
cursor: pointer;
|
||
transition:
|
||
border-color 0.2s ease,
|
||
transform 0.2s ease,
|
||
box-shadow 0.2s ease;
|
||
}
|
||
|
||
.dashboard-link-list__item:hover {
|
||
border-color: rgb(125 211 252 / 92%);
|
||
box-shadow: 0 12px 24px rgb(148 163 184 / 12%);
|
||
transform: translateY(-1px);
|
||
}
|
||
|
||
.dashboard-link-list__title {
|
||
color: rgb(15 23 42 / 96%);
|
||
font-size: 15px;
|
||
}
|
||
|
||
.dashboard-link-list__desc {
|
||
color: rgb(100 116 139 / 90%);
|
||
font-size: 13px;
|
||
line-height: 1.7;
|
||
}
|
||
|
||
.product-dashboard-page__growth {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||
gap: 16px;
|
||
}
|
||
|
||
.dashboard-growth-card {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 16px;
|
||
}
|
||
|
||
.dashboard-growth-card__description {
|
||
color: rgb(71 85 105 / 95%);
|
||
font-size: 14px;
|
||
line-height: 1.75;
|
||
}
|
||
|
||
.dashboard-growth-card__indicators {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||
gap: 12px;
|
||
}
|
||
|
||
.dashboard-growth-card__indicator {
|
||
display: flex;
|
||
min-height: 92px;
|
||
flex-direction: column;
|
||
justify-content: space-between;
|
||
padding: 14px;
|
||
border-radius: 14px;
|
||
background-color: rgb(248 250 252 / 96%);
|
||
}
|
||
|
||
.dashboard-growth-card__indicator-label {
|
||
color: rgb(100 116 139 / 92%);
|
||
font-size: 12px;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.dashboard-growth-card__indicator-value {
|
||
color: rgb(15 23 42 / 96%);
|
||
font-size: 20px;
|
||
}
|
||
|
||
@media (width <= 1280px) {
|
||
.product-dashboard-page__main {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.product-dashboard-page__metrics {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.dashboard-metric-stack,
|
||
.product-dashboard-page__growth {
|
||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||
}
|
||
}
|
||
|
||
@media (width <= 768px) {
|
||
.dashboard-metric-stack,
|
||
.product-dashboard-page__growth,
|
||
.dashboard-lifecycle__actions,
|
||
.dashboard-growth-card__indicators,
|
||
.dashboard-team-card__stat-grid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.dashboard-lifecycle__summary-main {
|
||
align-items: flex-start;
|
||
flex-direction: column;
|
||
}
|
||
}
|
||
</style>
|