This commit is contained in:
oneao committed 2025-09-02 21:57:27 +08:00
1 parent 261a263180
commit de2351e1b2
10 files changed
+269 -119

No files matched your search

@@ -3,3 +3,4 @@ export { default as Button } from './button'
export { default as Icon } from './icon' export { default as Icon } from './icon'
export { default as Input } from './input' export { default as Input } from './input'
export { default as Spin } from './spin' export { default as Spin } from './spin'
export { default as Textarea } from './textarea'
@@ -6,27 +6,16 @@ import './index.scss'
export default defineComponent({ export default defineComponent({
name: 'AIcon', name: 'AIcon',
inheritAttrs: false,
props: { props: {
icon: { type: String as PropType<string>, required: true }, icon: { type: String as PropType<string>, required: true },
size: { type: Number, default: 16 }, size: { type: Number, default: 16 },
color: { type: String, default: 'currentColor' }, color: { type: String, default: 'currentColor' },
spin: { type: Boolean, default: false }, spin: { type: Boolean, default: false },
rotate: { type: Number, default: 360 }, rotate: { type: Number, default: 360 },
onClick: Function as PropType<(e: MouseEvent) => void>, // 明确 MouseEvent 类型
}, },
setup(props, { attrs }) { setup(props) {
const prefixCls = getPrefixCls('icon') const prefixCls = getPrefixCls('icon')
// 安全调用 onClick
const handleClick = (e: MouseEvent) => {
props.onClick?.(e)
// 如果 attrs 上也传了 onClick,做类型断言
if (attrs.onClick) {
(attrs.onClick as (e: MouseEvent) => void)(e)
}
}
return () => ( return () => (
<i <i
class={props.spin ? `${prefixCls}-spin` : ''} class={props.spin ? `${prefixCls}-spin` : ''}
@@ -37,10 +26,8 @@ export default defineComponent({
'color': props.color, 'color': props.color,
'--rotate-angle': `${props.rotate}deg`, '--rotate-angle': `${props.rotate}deg`,
}} }}
onClick={handleClick}
> >
<Icon <Icon
{...attrs}
icon={props.icon} icon={props.icon}
width={props.size} width={props.size}
height={props.size} height={props.size}
@@ -1,5 +1,6 @@
@use './button/index.scss' as *; @use './button/index.scss' as *;
@use './input/index.scss' as *; @use './input/index.scss' as *;
@use './textarea/index.scss' as *;
input, input,
button, button,
@@ -0,0 +1,56 @@
<template>
<div
style="
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
"
>
<!-- 内层保留原来的布局 -->
<div
style="
display: flex;
gap: 12px;
flex-wrap: wrap;
align-items: center;
justify-content: center;
width: 300px;
"
>
<AInput size="small" placeholder="Small 输入框" password />
<AInput
size="medium"
placeholder="Medium 输入框"
clearable
/>
<AInput
size="large"
placeholder="Large 输入框"
/>
<AInput
size="small"
placeholder="Large 输入框"
prepend="http"
append=".com"
/>
<AInput
size="medium"
placeholder="Large 输入框"
prepend="http"
append=".com"
/>
<AInput
size="large"
placeholder="Large 输入框"
prepend="http"
append=".com"
/>
</div>
</div>
</template>
@@ -3,16 +3,81 @@
$input-prefix: #{$prefix}-input; $input-prefix: #{$prefix}-input;
.#{$input-prefix}-adornment {
display: flex;
width: 100%;
box-sizing: border-box;
margin: 0;
padding: 0;
list-style: none;
&.#{$input-prefix}-size {
&-small {
height: 24px;
font-size: 12px;
}
&-medium {
height: 28px;
font-size: 13px;
}
&-large {
height: 32px;
font-size: 14px;
}
}
.#{$input-prefix}-adornment-prepend,
.#{$input-prefix}-adornment-append {
display: inline-flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
background-color: #f3f3f3;
color: rgba(0, 0, 0, 0.9);
border: 1px solid #ddd;
span {
white-space: nowrap;
word-break: keep-all;
padding: 0 8px;
}
}
.#{$input-prefix}-adornment-prepend {
border-right: none;
}
.#{$input-prefix}-adornment-append {
border-left: none;
}
.#{$input-prefix}-wrapper {
border-radius: 0px;
}
> :first-child {
border-top-left-radius: 2px;
border-bottom-left-radius: 2px;
}
> :last-child {
border-top-right-radius: 2px;
border-bottom-right-radius: 2px;
}
}
.#{$input-prefix}-wrapper { .#{$input-prefix}-wrapper {
display: flex; display: flex;
align-items: center; align-items: center;
width: 100%; width: 100%;
border: 1px solid #ddd; border: 1px solid #ddd;
z-index: 2;
box-sizing: border-box; box-sizing: border-box;
border-radius: 2px;
padding: 0 8px; padding: 0 8px;
transition: transition:
border cubic-bezier(0.38, 0, 0.24, 1) 0.2s, border cubic-bezier(0.38, 0, 0.24, 1) 0.2s,
box-shadow cubic-bezier(0.38, 0, 0.24, 1) 0.2s, box-shadow cubic-bezier(0.38, 0, 0.24, 1) 0.2s,
@@ -45,23 +110,6 @@ $input-prefix: #{$prefix}-input;
box-shadow: $input-focus-shadow; box-shadow: $input-focus-shadow;
} }
&.#{$input-prefix}-size {
&-small {
height: 24px;
font-size: 12px;
}
&-medium {
height: 28px;
font-size: 13px;
}
&-large {
height: 32px;
font-size: 14px;
}
}
.#{$input-prefix}-align { .#{$input-prefix}-align {
&-left { &-left {
text-align: left; text-align: left;
@@ -119,7 +167,6 @@ $input-prefix: #{$prefix}-input;
.#{$input-prefix}-clear { .#{$input-prefix}-clear {
cursor: pointer; cursor: pointer;
transition: color 0.2s; transition: color 0.2s;
margin-left: 4px;
&:hover { &:hover {
color: red !important; color: red !important;
@@ -132,14 +179,7 @@ $input-prefix: #{$prefix}-input;
white-space: nowrap; white-space: nowrap;
} }
.#{$input-prefix}-password-toggle { .#{$input-prefix}-password {
display: inline-flex; cursor: pointer !important;
align-items: center;
cursor: pointer;
margin-left: 4px;
.hidden {
display: none;
}
} }
} }
@@ -4,7 +4,7 @@ import { getPrefixCls } from '../_utils/getPrefixCls'
import Icon from '../icon' import Icon from '../icon'
export default defineComponent({ export default defineComponent({
name: 'AInput', name: 'Input',
props: { props: {
modelValue: String, modelValue: String,
size: { size: {
@@ -24,6 +24,8 @@ export default defineComponent({
showCount: Boolean, showCount: Boolean,
maxlength: Number, maxlength: Number,
password: Boolean, password: Boolean,
prepend: String,
append: String,
}, },
emits: ['update:modelValue'], emits: ['update:modelValue'],
setup(props, { slots, emit }) { setup(props, { slots, emit }) {
@@ -51,65 +53,89 @@ export default defineComponent({
return showPassword.value ? 'text' : 'password' return showPassword.value ? 'text' : 'password'
}) })
return () => ( const inputWrapper = () => {
<div return (
class={[ <div
`${prefixCls}-wrapper`, class={[
`${prefixCls}-size-${props.size}`, `${prefixCls}-wrapper`,
props.disabled && `${prefixCls}-disabled`, props.disabled && `${prefixCls}-disabled`,
props.readonly && `${prefixCls}-readonly`, props.readonly && `${prefixCls}-readonly`,
]} ]}
> >
{(slots.prefix || props.prefixIcon) && ( {(slots.prefix || props.prefixIcon) && (
<div class={`${prefixCls}-prefix`}> <div class={`${prefixCls}-prefix`}>
{slots.prefix?.() ?? <Icon icon={props.prefixIcon!} size={14} />} {slots.prefix?.() ?? <Icon icon={props.prefixIcon!} size={14} />}
</div> </div>
)} )}
<input <input
class={`${prefixCls}-input ${prefixCls}-align-${props.align}`} class={`${prefixCls}-input ${prefixCls}-align-${props.align}`}
type={inputType.value} type={inputType.value}
value={inputValue.value} value={inputValue.value}
placeholder={props.placeholder} placeholder={props.placeholder}
disabled={props.disabled} disabled={props.disabled}
readonly={props.readonly} readonly={props.readonly}
onInput={(e: Event) => updateValue((e.target as HTMLInputElement).value)} onInput={(e: Event) => updateValue((e.target as HTMLInputElement).value)}
/>
{props.clearable && inputValue.value && !props.disabled && !props.readonly && (
<Icon
icon="streamline:interface-delete-circle-button-delete-remove-add-circle-buttons"
class={`${prefixCls}-clear`}
onClick={() => updateValue('')}
/> />
)}
{/* 密码切换图标 */} {props.clearable && inputValue.value && !props.disabled && !props.readonly && (
{props.password && ( <span
<div class={`${prefixCls}-password-toggle`}> class={`${prefixCls}-suffix`}
<Icon onClick={() => updateValue('')}
icon="mdi:eye" >
class={{ hidden: showPassword.value }} <Icon
onClick={() => (showPassword.value = true)} class={`${prefixCls}-clear`}
/> icon="material-symbols:cancel-outline-rounded"
<Icon />
icon="mdi:eye-off" </span>
class={{ hidden: !showPassword.value }}
onClick={() => (showPassword.value = false)}
/>
</div>
)}
{(slots.suffix || props.suffixIcon) && !props.password && ( )}
<div class={`${prefixCls}-suffix`}>
{slots.suffix?.() ?? <Icon icon={props.suffixIcon!} size={14} />}
</div>
)}
{props.showCount && ( {/* 密码切换图标 */}
<span class={`${prefixCls}-count`}>{countText.value}</span> {props.password && (
)} <span
</div> class={`${prefixCls}-suffix`}
) onClick={() => (showPassword.value = !showPassword.value)}
>
<Icon icon={showPassword.value ? 'mage:eye-off' : 'mage:eye'} class={`${prefixCls}-password`} />
</span>
)}
{(slots.suffix || props.suffixIcon) && !props.password && (
<div class={`${prefixCls}-suffix`}>
{slots.suffix?.() ?? <Icon icon={props.suffixIcon!} size={14} />}
</div>
)}
{props.showCount && (
<span class={`${prefixCls}-count`}>{countText.value}</span>
)}
</div>
)
}
return () => {
return (
<div class={`${prefixCls}-adornment ${prefixCls}-size-${props.size}`}>
{props.prepend || slots.prepend
? (
<div class={`${prefixCls}-adornment-prepend`}>
{slots.prepend?.() ?? (props.prepend && <span>{props.prepend}</span>)}
</div>
)
: null}
{inputWrapper()}
{props.append || slots.append
? (
<div class={`${prefixCls}-adornment-append`}>
{slots.append?.() ?? (props.append && <span>{props.append}</span>)}
</div>
)
: null}
</div>
)
}
}, },
}) })
@@ -0,0 +1,38 @@
@use '../_styles/theme/index' as *;
@use 'sass:color';
$textarea-prefix: #{$prefix}-textarea;
.#{$textarea-prefix}-wrapper {
width: 100%;
border: 1px solid #ddd;
border-radius: 2px;
box-sizing: border-box;
position: relative;
padding: 0;
&:hover {
border-color: $input-hover-color;
}
&:focus-within {
border-color: $input-focus-color;
box-shadow: $input-focus-shadow;
}
.#{$textarea-prefix}-inner {
width: 100%;
min-height: 80px; // 可根据需要
padding: 8px; // 内边距
border: none; // 去掉默认边框
outline: none;
resize: vertical;
box-sizing: border-box;
font-size: 13px;
color: #000;
&::placeholder {
color: #999999;
}
}
}
@@ -0,0 +1,19 @@
import type { PropType } from 'vue'
import { computed, defineComponent, ref } from 'vue'
import { getPrefixCls } from '../_utils/getPrefixCls'
export default defineComponent({
name: 'Textarea',
emits: ['update:modelValue'],
setup() {
const prefixCls = getPrefixCls('input')
return () => {
return (
<div class={`${prefixCls}-wrapper`}>
<textarea class={`${prefixCls}-inner`} />
</div>
)
}
},
})
+1 -20
View File
@@ -1,5 +1,4 @@
<script setup lang="ts"> <script setup lang="ts">
import { NButton, NInput } from 'naive-ui'
</script> </script>
<template> <template>
@@ -23,25 +22,7 @@ import { NButton, NInput } from 'naive-ui'
width: 300px; width: 300px;
" "
> >
<AButton icon="solar:map-point-search-linear"> <ATextarea />
ABC
</AButton>
<AButton icon="solar:map-point-search-linear" />
<AInput size="small" placeholder="Small 输入框" password />
<AInput
size="medium"
placeholder="Medium 输入框"
clearable
/>
<AInput
size="large"
placeholder="Large 输入框"
suffix-icon="solar:map-point-search-linear"
>
<template #prefix />
</AInput>
</div> </div>
</div> </div>
</template> </template>
+1
View File
@@ -15,5 +15,6 @@ declare module 'vue' {
ALoading: typeof import('@ao/ao-design')['Loading'] ALoading: typeof import('@ao/ao-design')['Loading']
ALoding: typeof import('@ao/ao-design')['Loding'] ALoding: typeof import('@ao/ao-design')['Loding']
ASpin: typeof import('@ao/ao-design')['Spin'] ASpin: typeof import('@ao/ao-design')['Spin']
ATextarea: typeof import('@ao/ao-design')['Textarea']
} }
} }