|
@@ -260,6 +260,20 @@
|
|
|
<el-input v-model="newOkr.title" :placeholder="currentHints.oHint" size="large" maxlength="35" show-word-limit />
|
|
<el-input v-model="newOkr.title" :placeholder="currentHints.oHint" size="large" maxlength="35" show-word-limit />
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
|
|
+ <!-- Parent objective alignment -->
|
|
|
|
|
+ <div class="parent-align-section">
|
|
|
|
|
+ <div class="parent-align-label">对齐目标</div>
|
|
|
|
|
+ <el-tree-select
|
|
|
|
|
+ v-model="newOkr.parentObjectiveId"
|
|
|
|
|
+ :data="alignmentOptions"
|
|
|
|
|
+ :props="{ children: 'children', label: 'title', value: 'id' }"
|
|
|
|
|
+ placeholder="选择对齐的上级目标(可选)"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ check-strictly
|
|
|
|
|
+ style="width:100%"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
<!-- Dynamic KRs with source reference -->
|
|
<!-- Dynamic KRs with source reference -->
|
|
|
<div class="kr-section">
|
|
<div class="kr-section">
|
|
|
<div class="kr-section-title">
|
|
<div class="kr-section-title">
|
|
@@ -332,6 +346,16 @@ const showForm = ref(false)
|
|
|
const editingOkr = ref(false)
|
|
const editingOkr = ref(false)
|
|
|
const submitting = ref(false)
|
|
const submitting = ref(false)
|
|
|
|
|
|
|
|
|
|
+const alignmentOptions = ref([])
|
|
|
|
|
+
|
|
|
|
|
+async function loadAlignmentOptions() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ alignmentOptions.value = await okrApi.getTree(selectedPeriodId.value)
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ alignmentOptions.value = []
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
// Position-specific OKR hints
|
|
// Position-specific OKR hints
|
|
|
const okrHints = {
|
|
const okrHints = {
|
|
|
'CEO / 总经理': {
|
|
'CEO / 总经理': {
|
|
@@ -403,7 +427,8 @@ const newOkr = reactive({
|
|
|
{ title: '', targetValue: 100, weight: 33, assignedTo: null },
|
|
{ title: '', targetValue: 100, weight: 33, assignedTo: null },
|
|
|
{ title: '', targetValue: 100, weight: 33, assignedTo: null }
|
|
{ title: '', targetValue: 100, weight: 33, assignedTo: null }
|
|
|
],
|
|
],
|
|
|
- alignedIds: []
|
|
|
|
|
|
|
+ alignedIds: [],
|
|
|
|
|
+ parentObjectiveId: null
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
const weightSum = computed(() => newOkr.krs.reduce((s, k) => s + (k.weight || 0), 0))
|
|
const weightSum = computed(() => newOkr.krs.reduce((s, k) => s + (k.weight || 0), 0))
|
|
@@ -530,6 +555,7 @@ async function load() {
|
|
|
try { myOkr.value = await okrApi.getMyOkr(pid) } catch { myOkr.value = null }
|
|
try { myOkr.value = await okrApi.getMyOkr(pid) } catch { myOkr.value = null }
|
|
|
try { const r = await okrApi.canSubmit(pid); canSubmit.value = r.canSubmit } catch { canSubmit.value = false }
|
|
try { const r = await okrApi.canSubmit(pid); canSubmit.value = r.canSubmit } catch { canSubmit.value = false }
|
|
|
try { pendingReviews.value = await okrApi.getPendingReviews(pid) } catch { pendingReviews.value = [] }
|
|
try { pendingReviews.value = await okrApi.getPendingReviews(pid) } catch { pendingReviews.value = [] }
|
|
|
|
|
+ loadAlignmentOptions()
|
|
|
if (myOkr.value) {
|
|
if (myOkr.value) {
|
|
|
myOkr.value.keyResults?.forEach(kr => { kr._newValue = kr.currentValue; kr._newStatus = kr.status })
|
|
myOkr.value.keyResults?.forEach(kr => { kr._newValue = kr.currentValue; kr._newStatus = kr.status })
|
|
|
}
|
|
}
|
|
@@ -593,7 +619,8 @@ async function handleSubmit() {
|
|
|
periodId: selectedPeriodId.value,
|
|
periodId: selectedPeriodId.value,
|
|
|
title: newOkr.title,
|
|
title: newOkr.title,
|
|
|
keyResults: filledKrs.map(k => ({ title: k.title, targetValue: k.targetValue, weight: k.weight, assignedTo: k.assignedTo, sourceKrId: k._sourceKrId || null })),
|
|
keyResults: filledKrs.map(k => ({ title: k.title, targetValue: k.targetValue, weight: k.weight, assignedTo: k.assignedTo, sourceKrId: k._sourceKrId || null })),
|
|
|
- alignedObjectiveIds: newOkr.alignedIds
|
|
|
|
|
|
|
+ alignedObjectiveIds: newOkr.alignedIds,
|
|
|
|
|
+ parentObjectiveId: newOkr.parentObjectiveId || null
|
|
|
}
|
|
}
|
|
|
if (editingOkr.value && myOkr.value) {
|
|
if (editingOkr.value && myOkr.value) {
|
|
|
await okrApi.update(myOkr.value.id, payload)
|
|
await okrApi.update(myOkr.value.id, payload)
|
|
@@ -615,6 +642,7 @@ function resetForm() {
|
|
|
{ title: '', targetValue: 100, weight: 33, assignedTo: null }
|
|
{ title: '', targetValue: 100, weight: 33, assignedTo: null }
|
|
|
]
|
|
]
|
|
|
newOkr.alignedIds = []
|
|
newOkr.alignedIds = []
|
|
|
|
|
+ newOkr.parentObjectiveId = null
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function updateProgress(kr) {
|
|
async function updateProgress(kr) {
|
|
@@ -756,6 +784,9 @@ function truncate(s, n) { if (!s) return ''; return s.length > n ? s.slice(0, n)
|
|
|
.align-footer { padding: 12px 22px; background: #f9fafb; border-top: 1px solid #f3f4f6; font-size: 13px; color: #6b7280; text-align: center; }
|
|
.align-footer { padding: 12px 22px; background: #f9fafb; border-top: 1px solid #f3f4f6; font-size: 13px; color: #6b7280; text-align: center; }
|
|
|
.align-footer strong { color: #2b1f99; }
|
|
.align-footer strong { color: #2b1f99; }
|
|
|
|
|
|
|
|
|
|
+.parent-align-section { margin-bottom: 20px; }
|
|
|
|
|
+.parent-align-label { font-weight: 600; font-size: 15px; color: #1a1a2e; margin-bottom: 8px; }
|
|
|
|
|
+
|
|
|
.o-section { margin-bottom: 20px; }
|
|
.o-section { margin-bottom: 20px; }
|
|
|
.o-label { font-weight: 600; font-size: 15px; color: #1a1a2e; margin-bottom: 8px; }
|
|
.o-label { font-weight: 600; font-size: 15px; color: #1a1a2e; margin-bottom: 8px; }
|
|
|
.o-hint { font-size: 12px; color: #aaa; font-weight: 400; }
|
|
.o-hint { font-size: 12px; color: #aaa; font-weight: 400; }
|