chore(portal): 补充 bitable/UI 组件的类型声明与组件文件
This commit is contained in:
parent
0979612a35
commit
0ad0ee668b
|
|
@ -189,9 +189,23 @@ declare module 'vue' {
|
|||
ToolCallIndicator: typeof import('./src/components/chat/ToolCallIndicator.vue')['default']
|
||||
TopNav: typeof import('./src/components/layout/TopNav.vue')['default']
|
||||
UiCard: typeof import('./src/components/ui/UiCard.vue')['default']
|
||||
UiCheckbox: typeof import('./src/components/ui/UiCheckbox.vue')['default']
|
||||
UiCheckboxGroup: typeof import('./src/components/ui/UiCheckboxGroup.vue')['default']
|
||||
UiDatePicker: typeof import('./src/components/ui/UiDatePicker.vue')['default']
|
||||
UiDateRangePicker: typeof import('./src/components/ui/UiDateRangePicker.vue')['default']
|
||||
UiFormField: typeof import('./src/components/ui/UiFormField.vue')['default']
|
||||
UiFormSection: typeof import('./src/components/ui/UiFormSection.vue')['default']
|
||||
UiInput: typeof import('./src/components/ui/UiInput.vue')['default']
|
||||
UiMetricCard: typeof import('./src/components/ui/UiMetricCard.vue')['default']
|
||||
UiPageHeader: typeof import('./src/components/ui/UiPageHeader.vue')['default']
|
||||
UiPageShell: typeof import('./src/components/ui/UiPageShell.vue')['default']
|
||||
UiRadio: typeof import('./src/components/ui/UiRadio.vue')['default']
|
||||
UiRadioGroup: typeof import('./src/components/ui/UiRadioGroup.vue')['default']
|
||||
UiSearchInput: typeof import('./src/components/ui/UiSearchInput.vue')['default']
|
||||
UiSelect: typeof import('./src/components/ui/UiSelect.vue')['default']
|
||||
UiSwitch: typeof import('./src/components/ui/UiSwitch.vue')['default']
|
||||
UiTabs: typeof import('./src/components/ui/UiTabs.vue')['default']
|
||||
UiTextArea: typeof import('./src/components/ui/UiTextArea.vue')['default']
|
||||
UiTrendBadge: typeof import('./src/components/ui/UiTrendBadge.vue')['default']
|
||||
UsagePanel: typeof import('./src/components/evolution/UsagePanel.vue')['default']
|
||||
UserBubble: typeof import('./src/components/chat/messages/UserBubble.vue')['default']
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
<template>
|
||||
<a-checkbox
|
||||
:checked="modelValue"
|
||||
:disabled="disabled"
|
||||
:indeterminate="indeterminate"
|
||||
class="ui-checkbox"
|
||||
@update:checked="emit('update:modelValue', $event)"
|
||||
@change="emit('change', $event)"
|
||||
>
|
||||
<slot />
|
||||
</a-checkbox>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Checkbox as ACheckbox } from 'ant-design-vue'
|
||||
|
||||
interface IProps {
|
||||
modelValue: boolean
|
||||
disabled?: boolean
|
||||
indeterminate?: boolean
|
||||
}
|
||||
|
||||
withDefaults(defineProps<IProps>(), {
|
||||
disabled: false,
|
||||
indeterminate: false,
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: boolean]
|
||||
change: [e: Event]
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ui-checkbox :deep(.ant-checkbox-inner) {
|
||||
border-radius: var(--radius-sm, 4px);
|
||||
border-color: var(--border-color-hover);
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
|
||||
.ui-checkbox :deep(.ant-checkbox:hover .ant-checkbox-inner) {
|
||||
border-color: var(--border-color-active);
|
||||
}
|
||||
|
||||
.ui-checkbox :deep(.ant-checkbox-checked .ant-checkbox-inner) {
|
||||
background: var(--color-primary);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.ui-checkbox :deep(.ant-checkbox-wrapper) {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
<template>
|
||||
<a-checkbox-group
|
||||
:value="modelValue"
|
||||
:options="options"
|
||||
:disabled="disabled"
|
||||
:class="['ui-checkbox-group', { 'ui-checkbox-group--vertical': direction === 'vertical' }]"
|
||||
@update:value="emit('update:modelValue', $event)"
|
||||
@change="emit('change', $event)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { CheckboxGroup as ACheckboxGroup } from 'ant-design-vue'
|
||||
|
||||
interface IOption {
|
||||
label: string
|
||||
value: any
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
interface IProps {
|
||||
modelValue: Array<any>
|
||||
options: IOption[]
|
||||
disabled?: boolean
|
||||
direction?: 'horizontal' | 'vertical'
|
||||
}
|
||||
|
||||
withDefaults(defineProps<IProps>(), {
|
||||
disabled: false,
|
||||
direction: 'horizontal',
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: Array<any>]
|
||||
change: [value: Array<any>]
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ui-checkbox-group :deep(.ant-checkbox-wrapper) {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.ui-checkbox-group :deep(.ant-checkbox-inner) {
|
||||
border-radius: var(--radius-sm, 4px);
|
||||
border-color: var(--border-color-hover);
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
|
||||
.ui-checkbox-group :deep(.ant-checkbox-checked .ant-checkbox-inner) {
|
||||
background: var(--color-primary);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.ui-checkbox-group--vertical {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-2, 8px);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
<template>
|
||||
<a-date-picker
|
||||
:value="modelValue"
|
||||
:placeholder="placeholder"
|
||||
:show-time="showTime"
|
||||
:format="format"
|
||||
:disabled="disabled"
|
||||
:size="antSize"
|
||||
class="ui-date-picker"
|
||||
@update:value="emit('update:modelValue', $event)"
|
||||
@change="emit('change', $event)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { DatePicker as ADatePicker } from 'ant-design-vue'
|
||||
|
||||
interface IProps {
|
||||
modelValue: any
|
||||
placeholder?: string
|
||||
showTime?: boolean
|
||||
format?: string
|
||||
disabled?: boolean
|
||||
size?: 'sm' | 'md'
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<IProps>(), {
|
||||
format: 'YYYY-MM-DD',
|
||||
disabled: false,
|
||||
size: 'md',
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: any]
|
||||
change: [value: any]
|
||||
}>()
|
||||
|
||||
const antSize = computed(() => (props.size === 'sm' ? 'small' : 'middle'))
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ui-date-picker {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ui-date-picker :deep(.ant-picker) {
|
||||
border-radius: var(--radius-md, 6px);
|
||||
border-color: var(--border-color);
|
||||
background: var(--bg-primary);
|
||||
transition: border-color var(--transition-fast, 150ms ease);
|
||||
}
|
||||
|
||||
.ui-date-picker :deep(.ant-picker:hover) {
|
||||
border-color: var(--border-color-hover);
|
||||
}
|
||||
|
||||
.ui-date-picker :deep(.ant-picker-focused) {
|
||||
border-color: var(--border-color-active);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.ui-date-picker :deep(.ant-picker-input > input) {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.ui-date-picker :deep(.ant-picker-input > input::placeholder) {
|
||||
color: var(--text-placeholder);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
<template>
|
||||
<a-range-picker
|
||||
:value="modelValue"
|
||||
:placeholder="placeholders"
|
||||
:show-time="showTime"
|
||||
:format="format"
|
||||
:disabled="disabled"
|
||||
class="ui-date-range-picker"
|
||||
@update:value="emit('update:modelValue', $event)"
|
||||
@change="emit('change', $event)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { RangePicker as ARangePicker } from 'ant-design-vue'
|
||||
|
||||
interface IProps {
|
||||
modelValue?: [any, any]
|
||||
placeholders?: [string, string]
|
||||
showTime?: boolean
|
||||
format?: string
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
withDefaults(defineProps<IProps>(), {
|
||||
disabled: false,
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: [any, any]]
|
||||
change: [value: [any, any]]
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ui-date-range-picker {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ui-date-range-picker :deep(.ant-picker) {
|
||||
border-radius: var(--radius-md, 6px);
|
||||
border-color: var(--border-color);
|
||||
background: var(--bg-primary);
|
||||
transition: border-color var(--transition-fast, 150ms ease);
|
||||
}
|
||||
|
||||
.ui-date-range-picker :deep(.ant-picker:hover) {
|
||||
border-color: var(--border-color-hover);
|
||||
}
|
||||
|
||||
.ui-date-range-picker :deep(.ant-picker-focused) {
|
||||
border-color: var(--border-color-active);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.ui-date-range-picker :deep(.ant-picker-input > input) {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.ui-date-range-picker :deep(.ant-picker-input > input::placeholder) {
|
||||
color: var(--text-placeholder);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
<template>
|
||||
<div class="ui-form-field">
|
||||
<label v-if="label" class="ui-form-field__label">
|
||||
<span v-if="required" class="ui-form-field__required">*</span>
|
||||
<span class="ui-form-field__label-text">{{ label }}</span>
|
||||
</label>
|
||||
<div class="ui-form-field__control">
|
||||
<slot />
|
||||
</div>
|
||||
<p v-if="error" class="ui-form-field__error">{{ error }}</p>
|
||||
<p v-else-if="hint" class="ui-form-field__hint">{{ hint }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
interface IProps {
|
||||
label?: string
|
||||
required?: boolean
|
||||
error?: string
|
||||
hint?: string
|
||||
}
|
||||
|
||||
withDefaults(defineProps<IProps>(), {
|
||||
required: false,
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ui-form-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-1, 4px);
|
||||
}
|
||||
|
||||
.ui-form-field__label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-1, 4px);
|
||||
font-size: var(--font-sm, 13px);
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.ui-form-field__required {
|
||||
color: var(--color-error);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.ui-form-field__label-text {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.ui-form-field__control {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.ui-form-field__error {
|
||||
margin: 0;
|
||||
font-size: var(--font-xs, 12px);
|
||||
color: var(--color-error);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.ui-form-field__hint {
|
||||
margin: 0;
|
||||
font-size: var(--font-xs, 12px);
|
||||
color: var(--text-tertiary);
|
||||
line-height: 1.5;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
<template>
|
||||
<UiCard padding="lg" class="ui-form-section">
|
||||
<div v-if="title || description" class="ui-form-section__header">
|
||||
<h3 v-if="title" class="ui-form-section__title">{{ title }}</h3>
|
||||
<p v-if="description" class="ui-form-section__description">{{ description }}</p>
|
||||
</div>
|
||||
<div class="ui-form-section__body">
|
||||
<slot />
|
||||
</div>
|
||||
</UiCard>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import UiCard from './UiCard.vue'
|
||||
|
||||
interface IProps {
|
||||
title?: string
|
||||
description?: string
|
||||
}
|
||||
|
||||
withDefaults(defineProps<IProps>(), {})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ui-form-section__header {
|
||||
margin-bottom: var(--space-4, 16px);
|
||||
}
|
||||
|
||||
.ui-form-section__title {
|
||||
margin: 0;
|
||||
font-size: var(--font-md, 16px);
|
||||
font-weight: var(--font-weight-medium, 500);
|
||||
color: var(--text-primary);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.ui-form-section__description {
|
||||
margin: var(--space-1, 4px) 0 0;
|
||||
font-size: var(--font-sm, 13px);
|
||||
color: var(--text-tertiary);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.ui-form-section__body {
|
||||
margin-top: var(--space-4, 16px);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
<template>
|
||||
<a-input
|
||||
:value="modelValue"
|
||||
:placeholder="placeholder"
|
||||
:size="antSize"
|
||||
:disabled="disabled"
|
||||
:readonly="readonly"
|
||||
:maxlength="maxLength"
|
||||
:allow-clear="allowClear"
|
||||
class="ui-input"
|
||||
@update:value="onUpdate"
|
||||
@change="emit('change', $event)"
|
||||
@press-enter="emit('pressEnter', $event)"
|
||||
>
|
||||
<template v-if="$slots.prefix" #prefix><slot name="prefix" /></template>
|
||||
<template v-if="$slots.suffix" #suffix><slot name="suffix" /></template>
|
||||
</a-input>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { Input as AInput } from 'ant-design-vue'
|
||||
|
||||
interface IProps {
|
||||
modelValue: string | number
|
||||
placeholder?: string
|
||||
size?: 'sm' | 'md'
|
||||
disabled?: boolean
|
||||
readonly?: boolean
|
||||
maxLength?: number
|
||||
allowClear?: boolean
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<IProps>(), {
|
||||
size: 'md',
|
||||
disabled: false,
|
||||
readonly: false,
|
||||
allowClear: true,
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: string]
|
||||
change: [e: Event]
|
||||
pressEnter: [e: KeyboardEvent]
|
||||
}>()
|
||||
|
||||
const antSize = computed(() => (props.size === 'sm' ? 'small' : 'middle'))
|
||||
|
||||
function onUpdate(val: string) {
|
||||
emit('update:modelValue', val)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ui-input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ui-input :deep(.ant-input) {
|
||||
border-radius: var(--radius-md, 6px);
|
||||
border-color: var(--border-color);
|
||||
color: var(--text-primary);
|
||||
background: var(--bg-primary);
|
||||
transition: border-color var(--transition-fast, 150ms ease);
|
||||
}
|
||||
|
||||
.ui-input :deep(.ant-input::placeholder) {
|
||||
color: var(--text-placeholder);
|
||||
}
|
||||
|
||||
.ui-input :deep(.ant-input:hover) {
|
||||
border-color: var(--border-color-hover);
|
||||
}
|
||||
|
||||
.ui-input :deep(.ant-input:focus),
|
||||
.ui-input :deep(.ant-input-focused) {
|
||||
border-color: var(--border-color-active);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.ui-input :deep(.ant-input-affix-wrapper) {
|
||||
border-radius: var(--radius-md, 6px);
|
||||
border-color: var(--border-color);
|
||||
background: var(--bg-primary);
|
||||
transition: border-color var(--transition-fast, 150ms ease);
|
||||
}
|
||||
|
||||
.ui-input :deep(.ant-input-affix-wrapper:hover) {
|
||||
border-color: var(--border-color-hover);
|
||||
}
|
||||
|
||||
.ui-input :deep(.ant-input-affix-wrapper:focus),
|
||||
.ui-input :deep(.ant-input-affix-wrapper-focused) {
|
||||
border-color: var(--border-color-active);
|
||||
box-shadow: none;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<template>
|
||||
<a-radio :value="value" :disabled="disabled" class="ui-radio">
|
||||
<slot />
|
||||
</a-radio>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Radio as ARadio } from 'ant-design-vue'
|
||||
|
||||
interface IProps {
|
||||
value: any
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
withDefaults(defineProps<IProps>(), {
|
||||
disabled: false,
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ui-radio :deep(.ant-radio-wrapper) {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.ui-radio :deep(.ant-radio-inner) {
|
||||
border-color: var(--border-color-hover);
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
|
||||
.ui-radio :deep(.ant-radio-checked .ant-radio-inner) {
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.ui-radio :deep(.ant-radio-inner::after) {
|
||||
background-color: var(--color-primary);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
<template>
|
||||
<a-radio-group
|
||||
:value="modelValue"
|
||||
:options="options"
|
||||
:disabled="disabled"
|
||||
:class="['ui-radio-group', { 'ui-radio-group--vertical': direction === 'vertical' }]"
|
||||
@update:value="emit('update:modelValue', $event)"
|
||||
@change="emit('change', $event)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { RadioGroup as ARadioGroup } from 'ant-design-vue'
|
||||
|
||||
interface IOption {
|
||||
label: string
|
||||
value: any
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
interface IProps {
|
||||
modelValue: any
|
||||
options: IOption[]
|
||||
disabled?: boolean
|
||||
direction?: 'horizontal' | 'vertical'
|
||||
}
|
||||
|
||||
withDefaults(defineProps<IProps>(), {
|
||||
disabled: false,
|
||||
direction: 'horizontal',
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: any]
|
||||
change: [e: Event]
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ui-radio-group :deep(.ant-radio-wrapper) {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.ui-radio-group :deep(.ant-radio-inner) {
|
||||
border-color: var(--border-color-hover);
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
|
||||
.ui-radio-group :deep(.ant-radio-checked .ant-radio-inner) {
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.ui-radio-group :deep(.ant-radio-inner::after) {
|
||||
background-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.ui-radio-group--vertical {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-2, 8px);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
<template>
|
||||
<a-input
|
||||
:value="modelValue"
|
||||
:placeholder="placeholder"
|
||||
:size="antSize"
|
||||
:allow-clear="true"
|
||||
class="ui-search-input"
|
||||
@update:value="onInput"
|
||||
>
|
||||
<template #prefix>
|
||||
<SearchOutlined class="ui-search-input__icon" />
|
||||
</template>
|
||||
</a-input>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, watch, onBeforeUnmount, ref } from 'vue'
|
||||
import { Input as AInput } from 'ant-design-vue'
|
||||
import { SearchOutlined } from '@ant-design/icons-vue'
|
||||
|
||||
interface IProps {
|
||||
modelValue: string
|
||||
placeholder?: string
|
||||
debounce?: number
|
||||
size?: 'sm' | 'md'
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<IProps>(), {
|
||||
placeholder: '搜索...',
|
||||
debounce: 300,
|
||||
size: 'md',
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: string]
|
||||
search: [value: string]
|
||||
}>()
|
||||
|
||||
const antSize = computed(() => (props.size === 'sm' ? 'small' : 'middle'))
|
||||
|
||||
let timer: ReturnType<typeof setTimeout> | null = null
|
||||
const lastValue = ref(props.modelValue)
|
||||
|
||||
function clearTimer() {
|
||||
if (timer) {
|
||||
clearTimeout(timer)
|
||||
timer = null
|
||||
}
|
||||
}
|
||||
|
||||
function onInput(val: string) {
|
||||
emit('update:modelValue', val)
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val) => {
|
||||
if (val === lastValue.value) return
|
||||
clearTimer()
|
||||
// 清空时立即触发
|
||||
if (val === '') {
|
||||
lastValue.value = val
|
||||
emit('search', val)
|
||||
return
|
||||
}
|
||||
timer = setTimeout(() => {
|
||||
lastValue.value = val
|
||||
emit('search', val)
|
||||
}, props.debounce)
|
||||
},
|
||||
)
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
clearTimer()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ui-search-input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ui-search-input__icon {
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.ui-search-input :deep(.ant-input-affix-wrapper) {
|
||||
border-radius: var(--radius-md, 6px);
|
||||
border-color: var(--border-color);
|
||||
background: var(--bg-primary);
|
||||
transition: border-color var(--transition-fast, 150ms ease);
|
||||
}
|
||||
|
||||
.ui-search-input :deep(.ant-input-affix-wrapper:hover) {
|
||||
border-color: var(--border-color-hover);
|
||||
}
|
||||
|
||||
.ui-search-input :deep(.ant-input-affix-wrapper:focus),
|
||||
.ui-search-input :deep(.ant-input-affix-wrapper-focused) {
|
||||
border-color: var(--border-color-active);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.ui-search-input :deep(.ant-input::placeholder) {
|
||||
color: var(--text-placeholder);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
<template>
|
||||
<a-select
|
||||
:value="modelValue"
|
||||
:options="options"
|
||||
:placeholder="placeholder"
|
||||
:mode="multiple ? 'multiple' : undefined"
|
||||
:show-search="searchable"
|
||||
:allow-clear="clearable"
|
||||
:disabled="disabled"
|
||||
:size="antSize"
|
||||
:loading="loading"
|
||||
class="ui-select"
|
||||
@update:value="emit('update:modelValue', $event)"
|
||||
@change="emit('change', $event)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { Select as ASelect } from 'ant-design-vue'
|
||||
|
||||
interface IOption {
|
||||
label: string
|
||||
value: any
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
interface IProps {
|
||||
modelValue: any
|
||||
options: IOption[]
|
||||
placeholder?: string
|
||||
multiple?: boolean
|
||||
searchable?: boolean
|
||||
clearable?: boolean
|
||||
disabled?: boolean
|
||||
size?: 'sm' | 'md'
|
||||
loading?: boolean
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<IProps>(), {
|
||||
placeholder: '请选择',
|
||||
multiple: false,
|
||||
searchable: false,
|
||||
clearable: true,
|
||||
disabled: false,
|
||||
size: 'md',
|
||||
loading: false,
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: any]
|
||||
change: [value: any]
|
||||
}>()
|
||||
|
||||
const antSize = computed(() => (props.size === 'sm' ? 'small' : 'middle'))
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ui-select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ui-select :deep(.ant-select-selector) {
|
||||
border-radius: var(--radius-md, 6px);
|
||||
border-color: var(--border-color);
|
||||
background: var(--bg-primary);
|
||||
transition: border-color var(--transition-fast, 150ms ease);
|
||||
}
|
||||
|
||||
.ui-select :deep(.ant-select-selector .ant-select-selection-placeholder) {
|
||||
color: var(--text-placeholder);
|
||||
}
|
||||
|
||||
.ui-select :deep(.ant-select:hover .ant-select-selector) {
|
||||
border-color: var(--border-color-hover);
|
||||
}
|
||||
|
||||
.ui-select :deep(.ant-select-focused .ant-select-selector),
|
||||
.ui-select :deep(.ant-select-selector:focus) {
|
||||
border-color: var(--border-color-active);
|
||||
box-shadow: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/* 下拉面板非 scoped,使用全局样式覆盖背景 */
|
||||
.ui-select + .ant-select-dropdown,
|
||||
.ant-select-dropdown {
|
||||
background: var(--bg-elevated);
|
||||
border-radius: var(--radius-md, 6px);
|
||||
}
|
||||
|
||||
.ant-select-dropdown .ant-select-item {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.ant-select-dropdown .ant-select-item-option-active,
|
||||
.ant-select-dropdown .ant-select-item-option-selected {
|
||||
background: var(--bg-tertiary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
<template>
|
||||
<a-switch
|
||||
:checked="modelValue"
|
||||
:disabled="disabled"
|
||||
:size="antSize"
|
||||
:checked-children="checkedText"
|
||||
:un-checked-children="uncheckedText"
|
||||
class="ui-switch"
|
||||
@update:checked="emit('update:modelValue', $event)"
|
||||
@change="emit('change', $event)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { Switch as ASwitch } from 'ant-design-vue'
|
||||
|
||||
interface IProps {
|
||||
modelValue: boolean
|
||||
disabled?: boolean
|
||||
size?: 'sm' | 'md'
|
||||
checkedText?: string
|
||||
uncheckedText?: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<IProps>(), {
|
||||
disabled: false,
|
||||
size: 'md',
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: boolean]
|
||||
change: [value: boolean]
|
||||
}>()
|
||||
|
||||
const antSize = computed(() => (props.size === 'sm' ? 'small' : 'default'))
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ui-switch :deep(.ant-switch-checked) {
|
||||
background: var(--color-primary);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
<template>
|
||||
<a-tabs
|
||||
class="ui-tabs"
|
||||
:active-key="modelValue"
|
||||
:tab-position="position"
|
||||
@change="onChange"
|
||||
>
|
||||
<a-tab-pane
|
||||
v-for="item in items"
|
||||
:key="item.key"
|
||||
:disabled="item.disabled"
|
||||
>
|
||||
<template #tab>
|
||||
<span class="ui-tabs__tab-inner">
|
||||
<component :is="item.icon" v-if="item.icon" class="ui-tabs__tab-icon" />
|
||||
<span>{{ item.label }}</span>
|
||||
</span>
|
||||
</template>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Tabs as ATabs, TabPane as ATabPane } from 'ant-design-vue'
|
||||
import type { Component } from 'vue'
|
||||
|
||||
interface ITabItem {
|
||||
key: string
|
||||
label: string
|
||||
icon?: Component
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
interface IProps {
|
||||
items: ITabItem[]
|
||||
modelValue: string
|
||||
position?: 'top' | 'left'
|
||||
}
|
||||
|
||||
withDefaults(defineProps<IProps>(), {
|
||||
position: 'top',
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: string]
|
||||
change: [value: string]
|
||||
}>()
|
||||
|
||||
function onChange(activeKey: string | number) {
|
||||
const key = String(activeKey)
|
||||
emit('update:modelValue', key)
|
||||
emit('change', key)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* ponytail: 仅作导航 tab 用,隐藏空内容区。需要内容请用 a-tabs 原生写法。 */
|
||||
.ui-tabs :deep(.ant-tabs-content-holder) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ui-tabs :deep(.ant-tabs-tab) {
|
||||
padding: var(--space-2) var(--space-4) !important;
|
||||
font-size: var(--font-sm);
|
||||
color: var(--text-secondary);
|
||||
border-radius: var(--radius-md);
|
||||
transition: color var(--transition-fast), background var(--transition-fast);
|
||||
margin: 0 var(--space-1) !important;
|
||||
}
|
||||
|
||||
.ui-tabs :deep(.ant-tabs-tab:hover) {
|
||||
background: var(--bg-tertiary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.ui-tabs :deep(.ant-tabs-tab-active) {
|
||||
color: var(--text-primary) !important;
|
||||
}
|
||||
|
||||
.ui-tabs :deep(.ant-tabs-tab-active .ant-tabs-tab-btn)
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
<template>
|
||||
<a-textarea
|
||||
:value="modelValue"
|
||||
:placeholder="placeholder"
|
||||
:rows="rows"
|
||||
:auto-size="autoSize"
|
||||
:maxlength="maxLength"
|
||||
:disabled="disabled"
|
||||
:show-count="showCount"
|
||||
class="ui-textarea"
|
||||
@update:value="emit('update:modelValue', $event)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Textarea as ATextarea } from 'ant-design-vue'
|
||||
|
||||
interface IAutoSize {
|
||||
minRows?: number
|
||||
maxRows?: number
|
||||
}
|
||||
|
||||
interface IProps {
|
||||
modelValue: string
|
||||
placeholder?: string
|
||||
rows?: number
|
||||
autoSize?: boolean | IAutoSize
|
||||
maxLength?: number
|
||||
disabled?: boolean
|
||||
showCount?: boolean
|
||||
}
|
||||
|
||||
withDefaults(defineProps<IProps>(), {
|
||||
rows: 3,
|
||||
autoSize: false,
|
||||
disabled: false,
|
||||
showCount: false,
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: string]
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ui-textarea :deep(.ant-input) {
|
||||
border-radius: var(--radius-md, 6px);
|
||||
border-color: var(--border-color);
|
||||
color: var(--text-primary);
|
||||
background: var(--bg-primary);
|
||||
transition: border-color var(--transition-fast, 150ms ease);
|
||||
}
|
||||
|
||||
.ui-textarea :deep(.ant-input::placeholder) {
|
||||
color: var(--text-placeholder);
|
||||
}
|
||||
|
||||
.ui-textarea :deep(.ant-input:hover) {
|
||||
border-color: var(--border-color-hover);
|
||||
}
|
||||
|
||||
.ui-textarea :deep(.ant-input:focus) {
|
||||
border-color: var(--border-color-active);
|
||||
box-shadow: none;
|
||||
}
|
||||
</style>
|
||||
Loading…
Reference in New Issue