feat(admin): SecretField 密钥四件套组件
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -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>
|
||||||
Reference in New Issue
Block a user