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:
393
src/views/product/setting/modules/setting-lifecycle-panel.vue
Normal file
393
src/views/product/setting/modules/setting-lifecycle-panel.vue
Normal file
@@ -0,0 +1,393 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user