feat(zone): 三区标记M2——A区只读收口:Dashboard移除AI建议应用弹窗(收敛至/ai-suggestions)、客户/学习看板KPIListView只读模式(KPI维护收敛至/kpis) (three-zone P0-①)
This commit is contained in:
@@ -12,7 +12,7 @@
|
||||
<el-radio-button value="quarter">本季</el-radio-button>
|
||||
<el-radio-button value="year">本年</el-radio-button>
|
||||
</el-radio-group>
|
||||
<el-dropdown v-if="templates && templates.length > 0" trigger="click" @command="selectTemplate">
|
||||
<el-dropdown v-if="!readOnly && templates && templates.length > 0" trigger="click" @command="selectTemplate">
|
||||
<el-button size="small" type="success">
|
||||
<el-icon><Plus /></el-icon> 从模板创建
|
||||
</el-button>
|
||||
@@ -35,7 +35,7 @@
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<el-button type="primary" size="small" @click="openCreate">
|
||||
<el-button v-if="!readOnly" type="primary" size="small" @click="openCreate">
|
||||
+ 新建指标
|
||||
</el-button>
|
||||
<el-button size="small" @click="loadData" :loading="loading">
|
||||
@@ -76,8 +76,10 @@
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-if="kpis.length === 0" class="empty-state">
|
||||
<el-empty :description="'暂无' + statLabels.total + ',' + (templates && templates.length > 0 ? '点击「从模板创建」快速添加预设指标' : '点击「+ 新建指标」创建')">
|
||||
<el-button type="primary" @click="openCreate">新建指标</el-button>
|
||||
<el-empty :description="readOnly
|
||||
? '暂无' + statLabels.total + ',请到「KPI字典」维护指标'
|
||||
: '暂无' + statLabels.total + ',' + (templates && templates.length > 0 ? '点击「从模板创建」快速添加预设指标' : '点击「+ 新建指标」创建')">
|
||||
<el-button v-if="!readOnly" type="primary" @click="openCreate">新建指标</el-button>
|
||||
</el-empty>
|
||||
</div>
|
||||
|
||||
@@ -190,7 +192,7 @@
|
||||
<el-table-column label="频率" width="70" v-if="showFrequency">
|
||||
<template #default="{ row }">{{ freqLabel(row.frequency) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="120" fixed="right">
|
||||
<el-table-column label="操作" width="120" fixed="right" v-if="!readOnly">
|
||||
<template #default="{ row }">
|
||||
<el-button size="small" text type="primary" @click.stop="editKPI(row)">编辑</el-button>
|
||||
<el-button size="small" text type="danger" @click.stop="deleteKPI(row.id)">删除</el-button>
|
||||
@@ -301,6 +303,7 @@ const props = withDefaults(defineProps<{
|
||||
unitOptions?: string[]
|
||||
formTitleCreate?: string
|
||||
formTitleEdit?: string
|
||||
readOnly?: boolean
|
||||
}>(), {
|
||||
icon: '📊',
|
||||
statLabels: () => ({ total: '指标总数', green: '达标', yellow: '预警', red: '未达标', noData: '无数据' }),
|
||||
@@ -312,6 +315,7 @@ const props = withDefaults(defineProps<{
|
||||
unitOptions: () => ['%', '元', '家', '分', '小时', '次', '个'],
|
||||
formTitleCreate: '',
|
||||
formTitleEdit: '',
|
||||
readOnly: false,
|
||||
})
|
||||
|
||||
const formTitle = computed(() => {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
:dimension="'customer'"
|
||||
:title="'客户维度KPI看板'"
|
||||
:icon="'👥'"
|
||||
:read-only="true"
|
||||
:stat-labels="{ total: '客户指标总数', green: '达标', yellow: '预警', red: '未达标', noData: '无数据' }"
|
||||
:templates="CUSTOMER_TEMPLATES"
|
||||
:has-categories="false"
|
||||
|
||||
@@ -226,9 +226,8 @@
|
||||
<div class="sug-title">{{ s.title }}</div>
|
||||
<div class="sug-content">{{ s.content }}</div>
|
||||
<div class="sug-foot">
|
||||
<el-button v-if="s.status === 'unapplied'" size="small" type="primary" @click="openApplyDialog(s)">应用到</el-button>
|
||||
<el-button v-if="s.status === 'unapplied'" size="small" @click="dismissSuggestion(s)">忽略</el-button>
|
||||
<span v-if="s.status === 'applied'" class="sug-applied-by">由 {{ s.applied_by }} 于 {{ (s.applied_at || '').slice(0, 16) }} 应用</span>
|
||||
<span v-else class="sug-pending-hint">待审批 · 请到 AI建议中心 处理</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -243,50 +242,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 应用建议弹窗 -->
|
||||
<el-dialog v-model="showApplyDialog" :title="'应用到:' + (applySug?.title || '')" width="520px">
|
||||
<el-form label-width="100px">
|
||||
<template v-if="applySug?.suggestion_type === 'kpi_target'">
|
||||
<el-form-item label="KPI"><el-input :model-value="applyKpiName" disabled /></el-form-item>
|
||||
<el-form-item label="新目标值" required>
|
||||
<el-input-number v-model="applyForm.target_value" :precision="2" style="width:220px" />
|
||||
</el-form-item>
|
||||
<div class="apply-tip">应用后将修改KPI目标值,并写入操作日志留痕。</div>
|
||||
</template>
|
||||
<template v-else-if="applySug?.suggestion_type === 'budget_adjust'">
|
||||
<el-form-item label="KPI"><el-input :model-value="applyKpiName" disabled /></el-form-item>
|
||||
<el-form-item label="期间" required>
|
||||
<el-input v-model="applyForm.period" placeholder="2026-09" style="width:220px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="预算值" required>
|
||||
<el-input-number v-model="applyForm.budget_value" :precision="2" style="width:220px" />
|
||||
</el-form-item>
|
||||
<div class="apply-tip">应用后将新增/更新该KPI对应期间的预算,并写入操作日志留痕。</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-form-item label="关联KPI"><el-input :model-value="applyKpiName" disabled /></el-form-item>
|
||||
<el-form-item label="计划标题" required><el-input v-model="applyForm.title" /></el-form-item>
|
||||
<el-form-item label="负责人"><el-input v-model="applyForm.assignee" /></el-form-item>
|
||||
<el-form-item label="优先级">
|
||||
<el-select v-model="applyForm.priority" style="width:220px">
|
||||
<el-option label="高" value="high" />
|
||||
<el-option label="中" value="medium" />
|
||||
<el-option label="低" value="low" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="截止日期">
|
||||
<el-date-picker v-model="applyForm.due_date" type="date" value-format="YYYY-MM-DD" style="width:220px" />
|
||||
</el-form-item>
|
||||
<div class="apply-tip">应用后将创建行动方案,并写入操作日志留痕。</div>
|
||||
</template>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="showApplyDialog = false">取消</el-button>
|
||||
<el-button type="primary" :loading="applying" @click="submitApply">确认应用</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- KPI详情弹窗 -->
|
||||
<!-- KPI详情弹窗(只读,A区) -->
|
||||
<el-dialog v-model="showDetail" :title="selectedKPI?.kpi_name" width="500px">
|
||||
<div v-if="selectedKPI">
|
||||
<div class="dlg-summary">
|
||||
@@ -369,14 +325,9 @@ const enabledChannels = ref(0)
|
||||
const aiAnalysis = ref('')
|
||||
const aiLoading = ref(false)
|
||||
|
||||
// R1: AI决策建议(建议卡 + 应用到弹窗)
|
||||
// R1: AI决策建议(A区只读展示;应用/忽略/审批收敛至 B区 /ai-suggestions,P0-① 三区收口 2026-09-03)
|
||||
const suggestions = ref<any[]>([])
|
||||
const suggestionsLoading = ref(false)
|
||||
const showApplyDialog = ref(false)
|
||||
const applySug = ref<any>(null)
|
||||
const applyForm = ref<any>({})
|
||||
const applying = ref(false)
|
||||
const applyKpiName = ref('')
|
||||
|
||||
const sugTypeLabel = (t: string) => ({ kpi_target: '改KPI目标', budget_adjust: '调预算', action_plan: '建行动方案' }[t] || t)
|
||||
const sugTagType = (t: string) => ({ kpi_target: 'warning', budget_adjust: 'danger', action_plan: 'primary' }[t] || 'info')
|
||||
@@ -390,60 +341,6 @@ async function loadSuggestions() {
|
||||
suggestionsLoading.value = false
|
||||
}
|
||||
|
||||
function openApplyDialog(s: any) {
|
||||
applySug.value = s
|
||||
const sd = s.suggestion_data || {}
|
||||
applyForm.value = {
|
||||
target_value: sd.target_value ?? null,
|
||||
period: sd.period || '',
|
||||
budget_value: sd.budget_value ?? null,
|
||||
title: sd.title || '',
|
||||
assignee: sd.assignee || '',
|
||||
priority: sd.priority || 'medium',
|
||||
due_date: sd.due_date || '',
|
||||
}
|
||||
applyKpiName.value = s.target_name || ''
|
||||
showApplyDialog.value = true
|
||||
}
|
||||
|
||||
async function submitApply() {
|
||||
if (!applySug.value) return
|
||||
const action = applySug.value.suggestion_type
|
||||
const body: any = { action, kpi_id: applySug.value.target_id || applySug.value.suggestion_data?.kpi_id }
|
||||
if (action === 'kpi_target') {
|
||||
if (applyForm.value.target_value == null) { ElMessage.warning('请输入新目标值'); return }
|
||||
body.target_value = applyForm.value.target_value
|
||||
} else if (action === 'budget_adjust') {
|
||||
if (!applyForm.value.period || applyForm.value.budget_value == null) { ElMessage.warning('请填写期间和预算值'); return }
|
||||
body.period = applyForm.value.period
|
||||
body.budget_value = applyForm.value.budget_value
|
||||
} else {
|
||||
if (!applyForm.value.title) { ElMessage.warning('请输入计划标题'); return }
|
||||
body.title = applyForm.value.title
|
||||
body.assignee = applyForm.value.assignee
|
||||
body.priority = applyForm.value.priority
|
||||
body.due_date = applyForm.value.due_date
|
||||
}
|
||||
applying.value = true
|
||||
try {
|
||||
await aiSuggestionApi.apply(applySug.value.id, body)
|
||||
ElMessage.success('建议已应用并留痕')
|
||||
showApplyDialog.value = false
|
||||
await loadSuggestions()
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e?.response?.data?.detail || '应用失败')
|
||||
}
|
||||
applying.value = false
|
||||
}
|
||||
|
||||
async function dismissSuggestion(s: any) {
|
||||
try {
|
||||
await aiSuggestionApi.dismiss(s.id)
|
||||
ElMessage.success('建议已忽略')
|
||||
await loadSuggestions()
|
||||
} catch { ElMessage.error('忽略失败') }
|
||||
}
|
||||
|
||||
const aiApi = axios.create({ baseURL: '/api/cma', timeout: 30000 })
|
||||
aiApi.interceptors.request.use((config: any) => {
|
||||
const token = localStorage.getItem('cma_token')
|
||||
@@ -715,6 +612,7 @@ onMounted(() => { loadData() })
|
||||
.sug-content { font-size:12px; color:#666; line-height:1.5; margin-bottom:8px; }
|
||||
.sug-foot { display:flex; gap:6px; align-items:center; }
|
||||
.sug-applied-by { font-size:11px; color:#999; }
|
||||
.sug-pending-hint { font-size:11px; color:#b88230; }
|
||||
.apply-tip { font-size:12px; color:#999; padding:0 0 10px 100px; }
|
||||
|
||||
/* KPI详情弹窗 */
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
:dimension="'learning'"
|
||||
:title="'学习成长维度KPI看板'"
|
||||
:icon="'📚'"
|
||||
:read-only="true"
|
||||
:stat-labels="{ total: '学习指标总数', green: '达标', yellow: '预警', red: '未达标', noData: '无数据' }"
|
||||
:templates="LEARNING_TEMPLATES"
|
||||
:has-categories="true"
|
||||
|
||||
Reference in New Issue
Block a user