Files
console-dev/app/components/PasswordField.vue
T
cieanyau e28fbbd9da
前端验证 / verify (push) Failing after 2m33s
修复密码显隐按钮遮挡文字编辑区
2026-11-06 05:23:50 +08:00

105 lines
3.1 KiB
Vue

<script setup>
/** 复用 PrimeVue 的密码/反馈状态,只统一原生输入语义与可键盘访问的显隐操作。 */
import { computed, nextTick, ref, useSlots } from "vue";
import Password from "primevue/password";
defineOptions({ inheritAttrs: false });
const props = defineProps({
autocomplete: { type: String, default: undefined },
inputProps: { type: Object, default: () => ({}) },
disabled: Boolean,
});
const slots = useSlots();
const forwardedSlots = () =>
Object.keys(slots).filter(
(name) => !["maskicon", "unmaskicon"].includes(name),
);
// autocomplete 属于真实 input,而不是 Password 的包装 DIV;保留已有 aria 等输入属性。
const nativeProps = computed(() => ({
...props.inputProps,
autocomplete: props.autocomplete ?? props.inputProps?.autocomplete ?? "off",
}));
const toggleButton = ref(null);
async function togglePassword(callback) {
callback();
// PrimeVue切换两个显隐slot时会替换按钮;把焦点交回新按钮,键盘才能连续显示/隐藏。
await nextTick();
toggleButton.value?.focus();
}
</script>
<template>
<Password
v-bind="$attrs"
class="password-field"
:input-props="nativeProps"
:disabled="disabled"
>
<template v-for="name in forwardedSlots()" #[name]="scope">
<slot :name="name" v-bind="scope" />
</template>
<template #unmaskicon="{ toggleCallback, class: iconClass }">
<button
ref="toggleButton"
type="button"
:class="['password-toggle', iconClass]"
aria-label="显示密码"
aria-pressed="false"
:disabled="disabled"
@click="togglePassword(toggleCallback)"
>
<i class="pi pi-eye" aria-hidden="true" />
</button>
</template>
<template #maskicon="{ toggleCallback, class: iconClass }">
<button
ref="toggleButton"
type="button"
:class="['password-toggle', iconClass]"
aria-label="隐藏密码"
aria-pressed="true"
:disabled="disabled"
@click="togglePassword(toggleCallback)"
>
<i class="pi pi-eye-slash" aria-hidden="true" />
</button>
</template>
</Password>
</template>
<style scoped>
.password-field {
--password-toggle-size: 44px;
}
/* PrimeVue公开的输入样式类位于子组件内;操作宽度与逻辑末端留白必须同源,避免文字被按钮截获。 */
.password-field:has(.password-toggle) :deep(.p-password-input) {
padding-inline-end: calc(
var(--password-toggle-size) + var(--p-form-field-padding-x)
);
}
.password-field .password-toggle {
position: absolute;
inset-block: 0;
inset-inline-end: 0;
display: grid;
place-items: center;
inline-size: var(--password-toggle-size);
block-size: 100%;
min-block-size: 0;
margin: 0;
padding: 0;
color: var(--p-text-muted-color);
background: transparent;
border: 0;
border-radius: var(--p-form-field-border-radius, 6px);
cursor: pointer;
}
.password-field .password-toggle:focus-visible {
outline: 2px solid var(--p-primary-color);
outline-offset: -3px;
}
.password-field .password-toggle:disabled {
cursor: not-allowed;
}
</style>