feat(admin): SecretField 密钥四件套组件

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-10-14 17:02:19 +08:00
parent 0a413526bb
commit 6881ccc3cd
@@ -0,0 +1,86 @@
<!--
密钥字段四件套(视觉基线 §4.2):状态徽章(secretFieldState 四态)+
密码输入(show-password)+ 清除/恢复保持 + 清除警告文案。
提交值计算不在本组件——父层沿用 build*Payload(form, cleared ? '' : masked)
(mask.ts secretToSubmit 契约)。
-->
<template>
<div class="secret-field">
<div class="secret-row">
<ElInput
:model-value="modelValue"
type="password"
show-password
autocomplete="new-password"
:placeholder="state.placeholder"
class="secret-input"
@update:model-value="$emit('update:modelValue', $event)"
/>
<ElTag :type="state.badgeType" size="small" effect="light" class="secret-badge">
{{ state.badgeLabel }}
</ElTag>
<ElButton
v-if="masked === MASKED_SENTINEL"
size="small"
:type="cleared ? 'default' : 'danger'"
plain
@click="$emit('update:cleared', !cleared)"
>
{{ cleared ? '恢复保持' : '清除' }}
</ElButton>
</div>
<p v-if="state.badge === 'pending-clear'" class="field-warn">
保存后将清除已存密钥(空槽回退平台默认)
</p>
</div>
</template>
<script setup lang="ts">
import { MASKED_SENTINEL } from '@/api/mask'
import { secretFieldState } from './model'
defineOptions({ name: 'SecretField' })
const props = defineProps<{
/** 用户输入(空串 = 未输入) */
modelValue: string
/** GET 掩码态('__SET__' = 库中已有值;'' = 未设置) */
masked: string
/** 清除开关(true = 提交空串显式清空) */
cleared: boolean
}>()
defineEmits<{
(e: 'update:modelValue', value: string): void
(e: 'update:cleared', value: boolean): void
}>()
const state = computed(() => secretFieldState(props.modelValue, props.masked, props.cleared))
</script>
<style scoped lang="scss">
.secret-field {
width: 100%;
.secret-row {
display: flex;
align-items: center;
gap: 8px;
.secret-input {
flex: 1;
min-width: 0;
}
.secret-badge {
flex-shrink: 0;
}
}
.field-warn {
margin: 4px 0 0;
font-size: 12px;
color: var(--el-color-danger);
}
}
</style>