feat: OKR模板库改造 — 弹窗模板库联动+时间分解落库+制造业包6个
This commit is contained in:
@@ -10,7 +10,8 @@
|
||||
<div class="mc-dim-label">{{ dimensionName }}</div>
|
||||
|
||||
<!-- 模板列表 -->
|
||||
<div class="mc-tmpl-list">
|
||||
<div v-if="templateLoading" class="mc-tmpl-empty">模板库加载中...</div>
|
||||
<div v-else class="mc-tmpl-list">
|
||||
<div
|
||||
v-for="(tmpl, idx) in filteredTemplates"
|
||||
:key="idx"
|
||||
@@ -20,7 +21,13 @@
|
||||
>
|
||||
<div class="mc-tmpl-header">
|
||||
<span class="mc-tmpl-name">{{ tmpl.name }}</span>
|
||||
<span v-if="selectedTemplateIdx === idx" class="mc-tmpl-check">✓</span>
|
||||
<span class="mc-tmpl-tags">
|
||||
<el-tag v-if="tmpl.source === 'industry_pack'" size="small" type="warning" style="margin-right:6px;">
|
||||
{{ industryTagLabel(tmpl.industry_tag) }}
|
||||
</el-tag>
|
||||
<el-tag v-else-if="tmpl.source === 'user'" size="small" type="success" style="margin-right:6px;">用户</el-tag>
|
||||
<span v-if="selectedTemplateIdx === idx" class="mc-tmpl-check">✓</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="mc-tmpl-desc">{{ tmpl.description }}</div>
|
||||
<div class="mc-tmpl-krs">
|
||||
@@ -104,6 +111,7 @@
|
||||
<div v-if="dialogStep === 1" class="mc-footer-buttons">
|
||||
<el-button size="small" @click="onCancel">取消</el-button>
|
||||
<el-button size="small" plain @click="skipTemplate">✏️ 跳过模板,自定义</el-button>
|
||||
<el-button size="small" text type="primary" @click="goTemplateLibrary">📚 从模板库选择更多</el-button>
|
||||
<el-button size="small" type="primary" @click="nextStep">
|
||||
下一步 {{ selectedTemplateIdx !== null ? '(已选模板)' : '(直接跳过)' }}
|
||||
</el-button>
|
||||
@@ -230,8 +238,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from "vue"
|
||||
import { ref, computed, watch, onMounted } from "vue"
|
||||
import { useRouter } from "vue-router"
|
||||
import { ElMessage } from "element-plus"
|
||||
import { okrTemplateApi } from "../../api/index"
|
||||
|
||||
// ── Props ──
|
||||
const props = defineProps<{
|
||||
@@ -433,6 +443,9 @@ const DIMENSION_LABELS: Record<string, string> = {
|
||||
const dialogStep = ref(1) // 1 = 模板选择, 2 = O+KR编辑
|
||||
const selectedTemplateIdx = ref<number | null>(null)
|
||||
const usedTemplate = ref<any | null>(null)
|
||||
const backendTemplates = ref<any[]>([]) // 后端模板库(含行业包/用户自定义)
|
||||
const templateLoading = ref(false)
|
||||
const router = useRouter()
|
||||
|
||||
// 内部表单(O+KR结构)
|
||||
const form = ref<{
|
||||
@@ -453,13 +466,48 @@ const dimensionName = computed(() => {
|
||||
|
||||
const filteredTemplates = computed(() => {
|
||||
const key = props.editingLayerKey || "finance"
|
||||
return (CMA_TEMPLATES as any)[key] || []
|
||||
const hardcoded = (CMA_TEMPLATES as any)[key] || []
|
||||
// 后端模板库(14个CMA标准 + 行业包 + 用户自定义)按维度过滤,与硬编码去重合并
|
||||
const fromBackend = backendTemplates.value
|
||||
.filter((t: any) => t.dimension === key && t.is_active !== 0)
|
||||
.map((t: any) => ({
|
||||
id: t.id,
|
||||
name: t.name,
|
||||
description: t.description,
|
||||
dimension: t.dimension,
|
||||
source: t.source,
|
||||
industry_tag: t.industry_tag,
|
||||
preset_krs: (t.preset_krs || []).map((kr: any) => ({
|
||||
name: kr.name,
|
||||
target_value: kr.target_value,
|
||||
weight: kr.weight,
|
||||
kpi_code: kr.kpi_code || "",
|
||||
})),
|
||||
}))
|
||||
const seen = new Set<string>()
|
||||
const merged: any[] = []
|
||||
for (const t of [...fromBackend, ...hardcoded]) {
|
||||
if (seen.has(t.name)) continue
|
||||
seen.add(t.name)
|
||||
merged.push(t)
|
||||
}
|
||||
return merged
|
||||
})
|
||||
|
||||
const dialogTitle = computed(() => {
|
||||
return props.isEditing ? "编辑目标" : "添加目标"
|
||||
})
|
||||
|
||||
const INDUSTRY_LABELS: Record<string, string> = {
|
||||
trading: "贸易经销",
|
||||
it_service: "IT服务",
|
||||
manufacturing: "制造业",
|
||||
general: "通用",
|
||||
}
|
||||
function industryTagLabel(tag?: string): string {
|
||||
return (tag && INDUSTRY_LABELS[tag]) || tag || "行业包"
|
||||
}
|
||||
|
||||
const kpiOptions = computed(() => {
|
||||
return props.allKpis || []
|
||||
})
|
||||
@@ -518,6 +566,44 @@ const weightSum = computed(() => {
|
||||
return form.value.krs.reduce((s, kr) => s + (parseInt(kr.weight) || 0), 0)
|
||||
})
|
||||
|
||||
// ── 从后端加载模板库(失败时静默回退到硬编码模板) ──
|
||||
async function loadBackendTemplates() {
|
||||
templateLoading.value = true
|
||||
try {
|
||||
const res: any = await okrTemplateApi.list({})
|
||||
const items = res?.items || []
|
||||
// 后端模板需要标准化为前端结构
|
||||
backendTemplates.value = items.map((t: any) => ({
|
||||
id: t.id,
|
||||
name: t.name,
|
||||
description: t.description,
|
||||
dimension: t.dimension,
|
||||
source: t.source,
|
||||
industry_tag: t.industry_tag,
|
||||
is_active: t.is_active,
|
||||
preset_krs: (t.preset_krs || []).map((kr: any) => ({
|
||||
name: kr.name,
|
||||
target_value: kr.target_value,
|
||||
weight: kr.weight,
|
||||
kpi_code: kr.kpi_code || "",
|
||||
})),
|
||||
}))
|
||||
} catch (e) {
|
||||
// 网络/权限失败:使用硬编码14个CMA模板兜底
|
||||
backendTemplates.value = []
|
||||
} finally {
|
||||
templateLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadBackendTemplates()
|
||||
})
|
||||
|
||||
function goTemplateLibrary() {
|
||||
router.push('/okr-templates')
|
||||
}
|
||||
|
||||
function addKr() {
|
||||
if (form.value.krs.length >= 5) {
|
||||
ElMessage.warning('每个目标最多5个关键结果')
|
||||
@@ -576,6 +662,7 @@ function onSave() {
|
||||
o_name: form.value.o_name.trim(),
|
||||
o_description: form.value.o_description.trim(),
|
||||
template_name: usedTemplate.value?.name || null,
|
||||
template_id: usedTemplate.value?.id || null,
|
||||
dimension: props.editingLayerKey,
|
||||
krs: form.value.krs.map((kr, i) => ({
|
||||
name: kr.name,
|
||||
@@ -588,6 +675,11 @@ function onSave() {
|
||||
name: form.value.o_name.trim(),
|
||||
description: form.value.o_description.trim(),
|
||||
})
|
||||
|
||||
// 模板使用次数 +1(仅后端来源的模板,静默失败不影响保存)
|
||||
if (usedTemplate.value?.id) {
|
||||
okrTemplateApi.incrementUse(usedTemplate.value.id).catch(() => {})
|
||||
}
|
||||
}
|
||||
|
||||
function onCancel() {
|
||||
|
||||
Reference in New Issue
Block a user