For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: 将 DimensionWeightPicker 从 checkbox+slider 改为 slider+数字输入框,动态 max 限制总和 ≤ 100%
Architecture: 单 Vue 组件重写,Props/emit 接口不变,与父组件零耦合改动
Tech Stack: Vue 2 + Element UI + Options API
Design doc: docs/superpowers/specs/2026-07-02-dimension-weight-picker-redesign.md
Files:
Modify: cfc-web/src/components/DimensionWeightPicker.vue:1-30
[ ] Step 1: 替换 template 内容
将原来的 <el-checkbox-group> + 每行 checkbox + slider 的结构,替换为纯 slider + 数字输入框:
<template>
<div class="dimension-weight-picker">
<div class="dim-rows">
<div v-for="dim in dimensions" :key="dim.code" class="dim-row">
<span class="dim-label" :style="{ color: dim.color }">{{ dim.name }}</span>
<el-slider
v-model="dim.currentValue"
:min="0"
:max="getMaxForDim(dim.code)"
:disabled="dim.currentValue === 0 && getRemaining() === 0"
class="dim-slider"
@change="onWeightChange"
/>
<el-input-number
v-model="dim.currentValue"
:min="0"
:max="getMaxForDim(dim.code)"
:disabled="dim.currentValue === 0 && getRemaining() === 0"
:controls="false"
size="mini"
class="dim-input"
@change="onWeightChange"
/>
<span class="dim-unit">%</span>
</div>
</div>
<div class="sum-row" :class="{ error: sum !== 100 && sum > 0 }">
<span>合计:{{ sum }}%</span>
<span v-if="sum !== 100 && sum > 0" class="sum-tip">剩余 {{ 100 - sum }}%</span>
<span v-if="sum === 100" class="sum-ok">已分配完成</span>
</div>
<div v-if="showPresets" class="preset-row">
<el-button v-for="p in presets" :key="p.label" size="mini" @click="applyPreset(p)">{{ p.label }}</el-button>
</div>
</div>
</template>
[ ] Step 2: 确认结果
grep -n 'el-checkbox\|el-checkbox-group' cfc-web/src/components/DimensionWeightPicker.vue || echo "已无 checkbox"
Files:
Modify: cfc-web/src/components/DimensionWeightPicker.vue:32-121
[ ] Step 1: 设置数据属性 currentValue,去掉 enabledDimensions
替换整个 data() — 每个维度加 currentValue 字段替代之前的 weight:
data() {
return {
dimensions: [
{ code: 'body', name: '身·土', color: '#FF8C42', currentValue: 0 },
{ code: 'mind', name: '智·金', color: '#6366F1', currentValue: 0 },
{ code: 'action', name: '行·木', color: '#10B981', currentValue: 0 },
{ code: 'wealth', name: '富·水', color: '#F59E0B', currentValue: 0 },
{ code: 'heart', name: '心·火', color: '#FF6B9D', currentValue: 0 }
],
presets: [
{ label: '均衡', weights: { body: 20, mind: 20, action: 20, wealth: 20, heart: 20 } },
{ label: '偏身', weights: { body: 40, mind: 15, action: 15, wealth: 15, heart: 15 } },
{ label: '偏智', weights: { body: 15, mind: 40, action: 15, wealth: 15, heart: 15 } },
{ label: '偏行', weights: { body: 15, mind: 15, action: 40, wealth: 15, heart: 15 } },
{ label: '偏富', weights: { body: 15, mind: 15, action: 15, wealth: 40, heart: 15 } },
{ label: '偏心', weights: { body: 15, mind: 15, action: 15, wealth: 15, heart: 40 } }
]
}
}
删除旧的 sum 和 enabledDimensions,新增 sum 用 currentValue:
computed: {
sum() {
return this.dimensions.reduce((s, d) => s + (d.currentValue || 0), 0)
}
}
简化 — 从外部 props value 初始化 currentValue,只保留 handler:
watch: {
value: {
immediate: true,
handler(val) {
if (Array.isArray(val) && val.length > 0) {
val.forEach(w => {
const dim = this.dimensions.find(d => d.code === w.dimension)
if (dim) {
dim.currentValue = w.weight || 0
}
})
}
}
}
}
[ ] Step 4: 替换 methods — 关键逻辑
methods: {
getMaxForDim(code) {
const otherTotal = this.dimensions
.filter(d => d.code !== code && (d.currentValue || 0) > 0)
.reduce((s, d) => s + (d.currentValue || 0), 0)
return Math.max(0, 100 - otherTotal)
},
getRemaining() {
return 100 - this.sum
},
onWeightChange() {
// 当其他维度有值时,当前维度的上限已被 getMaxForDim 控制
// 但数字输入可能未 clamp,强制修正
this.dimensions.forEach(d => {
const max = this.getMaxForDim(d.code)
if (d.currentValue > max) {
d.currentValue = max
}
if (d.currentValue < 0) {
d.currentValue = 0
}
})
this.emitValue()
},
emitValue() {
const weights = this.dimensions.map(d => ({
dimension: d.code,
weight: d.currentValue || 0,
enabled: (d.currentValue || 0) > 0
}))
this.$emit('input', weights)
},
applyPreset(preset) {
this.dimensions.forEach(d => {
d.currentValue = preset.weights[d.code] || 0
})
this.onWeightChange()
}
}
[ ] Step 5: 确认 props 接口不变
props: {
value: { type: Array, default: () => [] },
showPresets: { type: Boolean, default: false }
}
[ ] Step 6: 移除旧的 isEnabled, onEnabledChange 方法(上面已替换)
Files:
Modify: cfc-web/src/components/DimensionWeightPicker.vue:124-167
[ ] Step 1: 替换 style 块
<style scoped>
.dimension-weight-picker {
max-width: 520px;
}
.dim-rows {
margin-bottom: 8px;
}
.dim-row {
display: flex;
align-items: center;
margin-bottom: 10px;
gap: 8px;
}
.dim-label {
width: 64px;
font-size: 14px;
font-weight: 600;
flex-shrink: 0;
}
.dim-slider {
flex: 1;
}
.dim-input {
width: 72px;
flex-shrink: 0;
}
.dim-input /deep/ .el-input-number__decrease,
.dim-input /deep/ .el-input-number__increase {
display: none;
}
.dim-input /deep/ .el-input__inner {
text-align: center;
padding-left: 8px;
padding-right: 8px;
}
.dim-unit {
width: 16px;
font-size: 13px;
color: #666;
flex-shrink: 0;
}
.sum-row {
margin-top: 12px;
padding-top: 10px;
border-top: 1px solid #eee;
font-size: 13px;
color: #67c23a;
display: flex;
align-items: center;
gap: 6px;
}
.sum-row.error {
color: #f56c6c;
}
.sum-tip {
font-size: 12px;
}
.sum-ok {
font-size: 12px;
}
.preset-row {
margin-top: 12px;
display: flex;
gap: 6px;
flex-wrap: wrap;
}
</style>
Run: 使用 OpenCode 的 lsp_diagnostics 检查 cfc-web/src/components/DimensionWeightPicker.vue
Expected: 无错误
[ ] Step 2: 验证接口兼容性
grep -r 'DimensionWeightPicker' cfc-web/src/ --include='*.vue' -l
确认 Activities.vue, ArticleEdit.vue, EnergyRuleManagement.vue 中的用法无需改动(props value 和 showPresets 不变)
[ ] Step 3: commit
cd /sc-data/cfc
git add cfc-web/src/components/DimensionWeightPicker.vue docs/superpowers/plans/2026-07-02-dimension-weight-picker-redesign.md docs/superpowers/specs/2026-07-02-dimension-weight-picker-redesign.md
git commit -m "feat: 重写DimensionWeightPicker为slider+数字输入,动态max限制总和≤100%"
| 规格要求 | 对应 Task |
|---|---|
| 5 个维度各 slider + 数字输入 | Task 1 (template) |
| slider 动态 max 限制 | Task 2 (getMaxForDim) |
| 数字输入与 slider 双向同步 | Task 1 + Task 2 |
| 总和显示 | Task 2 (computed sum) |
| 预设按钮 | Task 1 + Task 2 (applyPreset) |
| Props/emit 接口兼容 | Task 2 Step 5 |
| 样式统一 | Task 3 |
未发现占位符、类型不一致或循环引用问题。