Files
cn-rdms-web/src/views/product/setting/modules/setting-lifecycle-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

394 lines
11 KiB
Vue

<script setup lang="ts">
import { computed } from 'vue';
import { getProductStatusLabel } from '../../shared/product-master-data';
import { getProductLifecycleActionCardMeta, getProductLifecycleStatusSummary } from '../shared';
defineOptions({ name: 'SettingLifecyclePanel' });
interface Props {
lifecycle: Api.Product.ProductLifecycleInfo | null;
}
interface Emits {
(e: 'action', action: Api.Product.ProductLifecycleAction): void;
}
const props = defineProps<Props>();
const emit = defineEmits<Emits>();
const statusSummary = computed(() => {
if (!props.lifecycle) {
return null;
}
return getProductLifecycleStatusSummary(props.lifecycle.statusCode);
});
const actionCards = computed(() =>
(props.lifecycle?.availableActions || []).map(action => ({
...action,
...getProductLifecycleActionCardMeta(action.actionCode)
}))
);
</script>
<template>
<ElCard class="card-wrapper">
<template #header>
<div>
<h3 class="text-16px text-[#0f172a] font-700">生命周期管理</h3>
</div>
</template>
<template v-if="lifecycle">
<div class="setting-lifecycle-panel__layout">
<section
class="setting-lifecycle-panel__hero"
:class="[`setting-lifecycle-panel__hero--${statusSummary?.tone || 'slate'}`]"
>
<div class="setting-lifecycle-panel__hero-top">
<div class="setting-lifecycle-panel__hero-main">
<div class="setting-lifecycle-panel__hero-status-row">
<span class="setting-lifecycle-panel__hero-status-label">当前状态</span>
<span class="setting-lifecycle-panel__hero-status-chip">
{{ getProductStatusLabel(lifecycle.statusCode) }}
</span>
</div>
<h4 class="setting-lifecycle-panel__hero-title">{{ statusSummary?.caption }}</h4>
</div>
</div>
<p class="setting-lifecycle-panel__hero-desc">
{{ statusSummary?.description }}
</p>
<div class="setting-lifecycle-panel__reason-card">
<span class="setting-lifecycle-panel__reason-label">最近状态原因</span>
<strong class="setting-lifecycle-panel__reason-value">
{{ lifecycle.lastStatusReason || '当前没有记录状态原因。' }}
</strong>
</div>
</section>
<section class="setting-lifecycle-panel__action-panel">
<div class="setting-lifecycle-panel__action-head">
<h4 class="setting-lifecycle-panel__action-title">可执行动作</h4>
</div>
<div v-if="actionCards.length > 0" class="setting-lifecycle-panel__action-grid">
<button
v-for="action in actionCards"
:key="action.actionCode"
type="button"
class="setting-lifecycle-panel__action-card"
:class="[`setting-lifecycle-panel__action-card--${action.tone}`]"
@click="emit('action', action)"
>
<div class="setting-lifecycle-panel__action-card-top">
<span class="setting-lifecycle-panel__action-dot" aria-hidden="true"></span>
<strong class="setting-lifecycle-panel__action-name">{{ action.actionName }}</strong>
</div>
<p class="setting-lifecycle-panel__action-desc">{{ action.description }}</p>
</button>
</div>
<div v-else class="setting-lifecycle-panel__empty-tip">当前状态下暂无可执行生命周期动作</div>
</section>
</div>
</template>
<ElEmpty v-else description="未获取到生命周期信息" />
</ElCard>
</template>
<style scoped>
.setting-lifecycle-panel__layout {
display: grid;
grid-template-columns: minmax(0, 1.1fr) minmax(320px, 0.9fr);
gap: 16px;
align-items: start;
}
.setting-lifecycle-panel__hero,
.setting-lifecycle-panel__action-panel {
display: flex;
flex-direction: column;
gap: 14px;
min-height: 100%;
padding: 18px;
border: 1px solid rgb(226 232 240 / 92%);
border-radius: 20px;
background-color: rgb(248 250 252 / 96%);
}
.setting-lifecycle-panel__hero {
overflow: hidden;
background:
radial-gradient(circle at top left, rgb(15 118 110 / 10%), transparent 34%),
linear-gradient(180deg, rgb(255 255 255 / 99%), rgb(248 250 252 / 97%));
}
.setting-lifecycle-panel__hero--emerald {
border-color: rgb(16 185 129 / 22%);
}
.setting-lifecycle-panel__hero--amber {
border-color: rgb(245 158 11 / 22%);
background:
radial-gradient(circle at top left, rgb(245 158 11 / 10%), transparent 34%),
linear-gradient(180deg, rgb(255 255 255 / 99%), rgb(255 251 235 / 97%));
}
.setting-lifecycle-panel__hero--slate {
border-color: rgb(100 116 139 / 22%);
background:
radial-gradient(circle at top left, rgb(100 116 139 / 10%), transparent 34%),
linear-gradient(180deg, rgb(255 255 255 / 99%), rgb(248 250 252 / 97%));
}
.setting-lifecycle-panel__hero--rose {
border-color: rgb(244 63 94 / 22%);
background:
radial-gradient(circle at top left, rgb(244 63 94 / 10%), transparent 34%),
linear-gradient(180deg, rgb(255 255 255 / 99%), rgb(255 241 242 / 97%));
}
.setting-lifecycle-panel__hero-top,
.setting-lifecycle-panel__action-head {
display: flex;
align-items: center;
gap: 16px;
}
.setting-lifecycle-panel__hero-main {
display: flex;
flex-direction: column;
gap: 10px;
}
.setting-lifecycle-panel__hero-status-row {
display: flex;
align-items: center;
gap: 10px;
}
.setting-lifecycle-panel__hero-status-label {
color: rgb(71 85 105 / 94%);
font-size: 12px;
font-weight: 600;
}
.setting-lifecycle-panel__hero-status-chip {
display: inline-flex;
align-items: center;
padding: 4px 12px;
border: 1px solid transparent;
border-radius: 999px;
font-size: 13px;
font-weight: 700;
line-height: 1.2;
}
.setting-lifecycle-panel__hero-title {
color: rgb(15 23 42 / 96%);
font-size: 22px;
font-weight: 700;
line-height: 1.25;
}
.setting-lifecycle-panel__action-title {
color: rgb(15 23 42 / 96%);
font-size: 18px;
font-weight: 700;
line-height: 1.25;
}
.setting-lifecycle-panel__hero-desc {
max-width: 560px;
color: rgb(71 85 105 / 94%);
font-size: 14px;
line-height: 1.7;
}
.setting-lifecycle-panel__reason-card {
display: flex;
flex-direction: column;
gap: 6px;
padding: 14px 16px;
border: 1px solid rgb(226 232 240 / 88%);
border-radius: 16px;
background-color: rgb(255 255 255 / 82%);
}
.setting-lifecycle-panel__reason-label {
color: rgb(100 116 139 / 92%);
font-size: 12px;
}
.setting-lifecycle-panel__reason-value {
color: rgb(15 23 42 / 94%);
font-size: 15px;
line-height: 1.7;
}
.setting-lifecycle-panel__action-panel {
background:
radial-gradient(circle at top right, rgb(59 130 246 / 7%), transparent 32%),
linear-gradient(180deg, rgb(255 255 255 / 99%), rgb(248 250 252 / 97%));
}
.setting-lifecycle-panel__action-grid {
display: grid;
grid-template-columns: repeat(1, minmax(0, 1fr));
gap: 10px;
}
.setting-lifecycle-panel__action-card {
display: flex;
flex-direction: column;
gap: 8px;
width: 100%;
padding: 14px 16px;
border: 1px solid rgb(226 232 240 / 92%);
border-radius: 18px;
background-color: rgb(255 255 255 / 96%);
text-align: left;
transition:
transform 0.2s ease,
border-color 0.2s ease,
box-shadow 0.2s ease;
}
.setting-lifecycle-panel__action-card:hover {
transform: translateY(-1px);
box-shadow: 0 10px 22px rgb(15 23 42 / 6%);
}
.setting-lifecycle-panel__action-card-top {
display: flex;
align-items: center;
gap: 10px;
}
.setting-lifecycle-panel__action-dot {
width: 10px;
height: 10px;
border-radius: 999px;
background-color: currentcolor;
flex: 0 0 auto;
}
.setting-lifecycle-panel__action-name {
color: rgb(15 23 42 / 96%);
font-size: 16px;
font-weight: 700;
}
.setting-lifecycle-panel__action-desc {
color: rgb(71 85 105 / 94%);
font-size: 13px;
line-height: 1.6;
}
.setting-lifecycle-panel__empty-tip {
padding: 18px 16px;
border: 1px dashed rgb(203 213 225 / 92%);
border-radius: 16px;
color: rgb(100 116 139 / 92%);
font-size: 13px;
line-height: 1.7;
}
.setting-lifecycle-panel__hero--emerald .setting-lifecycle-panel__hero-status-chip {
border-color: rgb(16 185 129 / 24%);
background-color: rgb(236 253 245 / 90%);
color: rgb(4 120 87 / 96%);
}
.setting-lifecycle-panel__hero--amber .setting-lifecycle-panel__hero-status-chip {
border-color: rgb(245 158 11 / 24%);
background-color: rgb(255 247 237 / 94%);
color: rgb(180 83 9 / 96%);
}
.setting-lifecycle-panel__hero--slate .setting-lifecycle-panel__hero-status-chip {
border-color: rgb(148 163 184 / 28%);
background-color: rgb(241 245 249 / 94%);
color: rgb(71 85 105 / 96%);
}
.setting-lifecycle-panel__hero--rose .setting-lifecycle-panel__hero-status-chip {
border-color: rgb(244 63 94 / 24%);
background-color: rgb(255 241 242 / 94%);
color: rgb(190 24 93 / 96%);
}
.setting-lifecycle-panel__action-card--emerald {
border-color: rgb(16 185 129 / 22%);
background: linear-gradient(90deg, rgb(236 253 245 / 90%), rgb(255 255 255 / 96%) 26%);
color: rgb(4 120 87 / 96%);
}
.setting-lifecycle-panel__action-card--amber {
border-color: rgb(245 158 11 / 22%);
background: linear-gradient(90deg, rgb(255 247 237 / 92%), rgb(255 255 255 / 96%) 26%);
color: rgb(180 83 9 / 96%);
}
.setting-lifecycle-panel__action-card--slate {
border-color: rgb(148 163 184 / 26%);
background: linear-gradient(90deg, rgb(241 245 249 / 92%), rgb(255 255 255 / 96%) 26%);
color: rgb(71 85 105 / 96%);
}
.setting-lifecycle-panel__action-card--rose {
border-color: rgb(244 63 94 / 22%);
background: linear-gradient(90deg, rgb(255 241 242 / 92%), rgb(255 255 255 / 96%) 26%);
color: rgb(190 24 93 / 96%);
}
.setting-lifecycle-panel__action-card--emerald:hover {
box-shadow: 0 10px 22px rgb(16 185 129 / 12%);
}
.setting-lifecycle-panel__action-card--amber:hover {
box-shadow: 0 10px 22px rgb(245 158 11 / 12%);
}
.setting-lifecycle-panel__action-card--slate:hover {
box-shadow: 0 10px 22px rgb(100 116 139 / 10%);
}
.setting-lifecycle-panel__action-card--rose:hover {
box-shadow: 0 10px 22px rgb(244 63 94 / 12%);
}
.setting-lifecycle-panel__action-card--emerald .setting-lifecycle-panel__action-name {
color: rgb(6 95 70 / 96%);
}
.setting-lifecycle-panel__action-card--amber .setting-lifecycle-panel__action-name {
color: rgb(146 64 14 / 96%);
}
.setting-lifecycle-panel__action-card--slate .setting-lifecycle-panel__action-name {
color: rgb(51 65 85 / 96%);
}
.setting-lifecycle-panel__action-card--rose .setting-lifecycle-panel__action-name {
color: rgb(159 18 57 / 96%);
}
@media (width <= 1280px) {
.setting-lifecycle-panel__layout {
grid-template-columns: 1fr;
}
}
@media (width <= 640px) {
.setting-lifecycle-panel__hero-top,
.setting-lifecycle-panel__action-head {
align-items: flex-start;
}
}
</style>