优化
This commit is contained in:
@@ -10,9 +10,13 @@ import { UI_PREFERENCE_SCOPE, preferenceStorageKey } from '@/shared/utils/uiLayo
|
||||
const FieldStub = defineComponent({
|
||||
inheritAttrs: false,
|
||||
props: { placeholder: String },
|
||||
template: '<input :data-placeholder="placeholder" />',
|
||||
emits: ['keyup'],
|
||||
template: '<input :data-placeholder="placeholder" @keyup="$emit(\'keyup\', $event)" />',
|
||||
})
|
||||
const ButtonStub = defineComponent({
|
||||
props: { disabled: Boolean, loading: Boolean },
|
||||
template: '<button type="button" :disabled="disabled" :data-loading="String(loading)"><slot /></button>',
|
||||
})
|
||||
const ButtonStub = defineComponent({ template: '<button type="button"><slot /></button>' })
|
||||
const PopoverStub = defineComponent({ template: '<div><slot name="reference" /><slot /></div>' })
|
||||
|
||||
const fields: QueryField[] = [
|
||||
@@ -21,7 +25,7 @@ const fields: QueryField[] = [
|
||||
{ key: 'createdRange', type: 'daterange', label: '创建日期', placeholder: '创建日期' },
|
||||
]
|
||||
|
||||
function mountToolbar() {
|
||||
function mountToolbar(props: Record<string, unknown> = {}) {
|
||||
return shallowMount(QueryToolbar, {
|
||||
props: {
|
||||
modelValue: { keyword: '', status: '', createdRange: [] },
|
||||
@@ -29,6 +33,7 @@ function mountToolbar() {
|
||||
ariaLabel: '订单筛选',
|
||||
fields,
|
||||
preferenceKey: 'orders-query',
|
||||
...props,
|
||||
},
|
||||
global: {
|
||||
provide: { [UI_PREFERENCE_SCOPE as symbol]: computed(() => 'user-1') },
|
||||
@@ -70,4 +75,49 @@ describe('QueryToolbar layout preferences', () => {
|
||||
const source = readFileSync(resolve(process.cwd(), 'src/shared/components/QueryToolbar.vue'), 'utf8')
|
||||
expect(source).toContain('.query-toolbar__tail { display: flex; align-items: flex-end; width: auto; flex: 0 0 auto; gap: 10px; }')
|
||||
})
|
||||
|
||||
it('submits the current filters once when Enter is released in a text input', async () => {
|
||||
const wrapper = mountToolbar({ modelValue: { keyword: 'AKR1', status: 'ACTIVE', createdRange: [] } })
|
||||
const textInput = wrapper.findAll('input')[0]
|
||||
|
||||
await textInput.trigger('keyup', { key: 'Enter' })
|
||||
|
||||
expect(wrapper.emitted('search')).toEqual([[
|
||||
{ keyword: 'AKR1', status: 'ACTIVE', createdRange: [] },
|
||||
]])
|
||||
})
|
||||
|
||||
it('does not submit Enter from select or date controls, composition, or repeated key events', async () => {
|
||||
const wrapper = mountToolbar()
|
||||
const controls = wrapper.findAll('input')
|
||||
|
||||
await controls[1].trigger('keyup', { key: 'Enter' })
|
||||
await controls[2].trigger('keyup', { key: 'Enter' })
|
||||
await controls[0].trigger('keyup', { key: 'Enter', isComposing: true })
|
||||
await controls[0].trigger('keyup', { key: 'Enter', repeat: true })
|
||||
|
||||
expect(wrapper.emitted('search')).toBeUndefined()
|
||||
})
|
||||
|
||||
it('resets all filters once and blocks search and reset while loading', async () => {
|
||||
const wrapper = mountToolbar({
|
||||
modelValue: { keyword: 'AKR1', status: 'ACTIVE', createdRange: ['2026-08-01', '2026-08-14'] },
|
||||
})
|
||||
|
||||
;(wrapper.vm as unknown as { reset: () => void }).reset()
|
||||
expect(wrapper.emitted('reset')).toEqual([[
|
||||
{ keyword: '', status: '', createdRange: [] },
|
||||
]])
|
||||
expect(wrapper.emitted('search')).toEqual([[
|
||||
{ keyword: '', status: '', createdRange: [] },
|
||||
]])
|
||||
|
||||
await wrapper.setProps({ loading: true })
|
||||
;(wrapper.vm as unknown as { search: () => void; reset: () => void }).search()
|
||||
;(wrapper.vm as unknown as { search: () => void; reset: () => void }).reset()
|
||||
expect(wrapper.emitted('search')).toHaveLength(1)
|
||||
expect(wrapper.emitted('reset')).toHaveLength(1)
|
||||
expect(wrapper.findAllComponents(ButtonStub)[0].props('loading')).toBe(true)
|
||||
expect(wrapper.findAllComponents(ButtonStub)[1].props('disabled')).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -40,10 +40,12 @@ const props = withDefaults(defineProps<{
|
||||
searchText?: string
|
||||
resetText?: string
|
||||
searchOnReset?: boolean
|
||||
loading?: boolean
|
||||
}>(), {
|
||||
searchText: '查询',
|
||||
resetText: '重置',
|
||||
searchOnReset: true,
|
||||
loading: false,
|
||||
})
|
||||
|
||||
const model = defineModel<QueryModel>({ required: true })
|
||||
@@ -140,10 +142,12 @@ function resetValue(field: QueryField): QueryValue {
|
||||
}
|
||||
|
||||
function search() {
|
||||
if (props.loading) return
|
||||
emit('search', model.value)
|
||||
}
|
||||
|
||||
function reset() {
|
||||
if (props.loading) return
|
||||
const next = { ...model.value }
|
||||
for (const field of props.fields) next[field.key] = resetValue(field)
|
||||
model.value = next
|
||||
@@ -151,6 +155,11 @@ function reset() {
|
||||
if (props.searchOnReset) emit('search', next)
|
||||
}
|
||||
|
||||
function searchFromTextInput(event: KeyboardEvent) {
|
||||
if (event.isComposing || event.repeat) return
|
||||
search()
|
||||
}
|
||||
|
||||
defineExpose({ search, reset })
|
||||
</script>
|
||||
|
||||
@@ -175,6 +184,7 @@ defineExpose({ search, reset })
|
||||
clearable
|
||||
v-bind="field.props"
|
||||
@update:model-value="updateField(field.key, $event)"
|
||||
@keyup.enter.exact="searchFromTextInput"
|
||||
>
|
||||
<template #prefix><Search :size="16" /></template>
|
||||
</el-input>
|
||||
@@ -203,8 +213,8 @@ defineExpose({ search, reset })
|
||||
/>
|
||||
</div>
|
||||
<div v-if="index === orderedFields.length - 1" class="query-toolbar__actions">
|
||||
<el-button type="primary" @click="search"><Search :size="16" />{{ searchText }}</el-button>
|
||||
<el-button @click="reset"><RotateCcw :size="16" />{{ resetText }}</el-button>
|
||||
<el-button type="primary" :loading="loading" @click="search"><Search :size="16" />{{ searchText }}</el-button>
|
||||
<el-button :disabled="loading" @click="reset"><RotateCcw :size="16" />{{ resetText }}</el-button>
|
||||
<slot name="actions" />
|
||||
<el-popover v-if="preferenceEnabled" placement="bottom-end" :width="340" trigger="click" popper-class="cc-layout-preference-popper">
|
||||
<template #reference>
|
||||
@@ -216,8 +226,8 @@ defineExpose({ search, reset })
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="orderedFields.length === 0" class="query-toolbar__actions">
|
||||
<el-button type="primary" @click="search"><Search :size="16" />{{ searchText }}</el-button>
|
||||
<el-button @click="reset"><RotateCcw :size="16" />{{ resetText }}</el-button>
|
||||
<el-button type="primary" :loading="loading" @click="search"><Search :size="16" />{{ searchText }}</el-button>
|
||||
<el-button :disabled="loading" @click="reset"><RotateCcw :size="16" />{{ resetText }}</el-button>
|
||||
<slot name="actions" />
|
||||
<el-popover v-if="preferenceEnabled" placement="bottom-end" :width="340" trigger="click" popper-class="cc-layout-preference-popper">
|
||||
<template #reference>
|
||||
|
||||
Reference in New Issue
Block a user