update: 优化Search组件查询以及重置

This commit is contained in:
xieyonghong 2023-01-17 11:41:58 +08:00
parent b54d1082ca
commit f3120719c5
6 changed files with 286 additions and 71 deletions

View File

@ -36,4 +36,10 @@ export const getCodecs = () => server.get<{id: string, name: string}>('/device/p
* @param id ID
* @returns
*/
export const detail = (id: string) => server.get<ProductItem>(`/device-product/${id}`)
export const detail = (id: string) => server.get<ProductItem>(`/device-product/${id}`)
/**
*
* @param data
*/
export const category = (data: any) => server.post('/device/category/_tree', data)

View File

@ -6,6 +6,7 @@
:options='typeOptions'
v-model:value='termsModel.type'
style='width: 100%;'
@change='valueChange'
/>
<span v-else>
{{
@ -17,75 +18,86 @@
class='JSearch-item--column'
:options='columnOptions'
v-model:value='termsModel.column'
@change='columnChange'
/>
<a-select
class='JSearch-item--termType'
:options='termTypeOptions'
:options='termTypeOptions.option'
v-model:value='termsModel.termType'
@change='termTypeChange'
/>
<div class='JSearch-item--value'>
<a-input
v-if='component === componentType.input'
v-model:value='termsModel.value'
style='width: 100%'
@change='(v) => valueChange(v)'
@change='valueChange'
/>
<a-select
v-else-if='component === componentType.select'
showSearch
:loading='optionLoading'
v-model:value='termsModel.value'
:options='options'
style='width: 100%'
@change='(v) => valueChange(v)'
:filterOption='(v, option) => filterTreeSelectNode(v, option, "label")'
@change='valueChange'
/>
<a-input-number
v-else-if='component === componentType.inputNumber'
v-model:value='termsModel.value'
style='width: 100%'
@change='(v) => valueChange(v)'
@change='valueChange'
/>
<a-input-password
v-else-if='component === componentType.password'
v-model:value='termsModel.value'
style='width: 100%'
@change='(v) => valueChange(v)'
@change='valueChange'
/>
<a-switch
v-else-if='component === componentType.switch'
v-model:checked='termsModel.value'
style='width: 100%'
@change='(v) => valueChange(v)'
@change='valueChange'
/>
<a-radio-group
v-else-if='component === componentType.radio'
v-model:value='termsModel.value'
style='width: 100%'
@change='(v) => valueChange(v)'
@change='valueChange'
/>
<a-checkbox-group
v-else-if='component === componentType.checkbox'
v-model:value='termsModel.value'
:options='options'
style='width: 100%'
@change='(v) => valueChange(v)'
@change='valueChange'
/>
<a-time-picker
v-else-if='component === componentType.time'
valueFormat='HH:mm:ss'
v-model:value='termsModel.value'
style='width: 100%'
@change='(v) => valueChange(v)'
@change='valueChange'
/>
<a-date-picker
v-else-if='component === componentType.date'
showTime
v-model:value='termsModel.value'
valueFormat='YYYY-MM-DD HH:mm:ss'
style='width: 100%'
@change='(v) => valueChange(v)'
@change='valueChange'
/>
<a-tree-select
v-else-if='component === componentType.treeSelect'
showSearch
v-model:value='termsModel.value'
:tree-data='options'
style='width: 100%'
@change='(v) => valueChange(v)'
:fieldNames='{ label: "name", value: "id" }'
@change='valueChange'
:filterTreeNode='(v, option) => filterSelectNode(v, option)'
/>
</div>
</div>
@ -95,20 +107,19 @@
import { componentType } from 'components/Form'
import { typeOptions, termType } from './util'
import { PropType } from 'vue'
import type { SearchItemProps, SearchItemData, SearchProps } from './types'
import { cloneDeep, isArray, isFunction } from 'lodash-es'
type ItemDataProps = Omit<SearchItemData, 'title'>
import type { SearchItemData, SearchProps, Terms } from './types'
import { cloneDeep, get, isArray, isFunction } from 'lodash-es'
import { filterTreeSelectNode, filterSelectNode } from '@/utils/comm'
type ItemType = SearchProps['type']
interface Emit {
(e: 'change', data: ItemDataProps): void
(e: 'change', data: SearchItemData): void
}
const props = defineProps({
columns: {
type: Array as PropType<SearchItemProps[]>,
type: Array as PropType<SearchProps[]>,
default: () => [],
required: true
},
@ -119,12 +130,16 @@ const props = defineProps({
expand: {
type: Boolean,
default: false
},
termsItem: {
type: Object as PropType<Terms>,
default: {}
}
})
const emit = defineEmits<Emit>()
const termsModel = reactive<ItemDataProps>({
const termsModel = reactive<SearchItemData>({
type: 'or',
value: '',
termType: 'eq',
@ -138,21 +153,34 @@ const options = ref<any[]>([])
const columnOptions = ref<({ label: string, value: string})[]>([])
const columnOptionMap = new Map()
const termTypeOptions = reactive(termType)
const termTypeOptions = reactive({option: termType})
const optionLoading = ref(false)
/**
* 根据类型切换默termType值
* @param type
*/
const getTermType = (type?: ItemType) => {
termTypeOptions.option = termType
switch (type) {
case 'select':
case 'treeSelect':
return 'eq'
case 'date':
case 'time':
//
termTypeOptions.option = termType.filter(item => ['gt','lt'].includes(item.value))
return 'gt'
default:
return 'like'
}
}
/**
* 根据类型返回组件
* @param type
*/
const getComponent = (type?: ItemType) => {
switch (type) {
case 'select':
@ -181,34 +209,41 @@ const handleItemOptions = (option?: any[] | Function) => {
if (isArray(option)) {
options.value = option
} else if (isFunction(option)) {
optionLoading.value = true
option().then((res: any[]) => {
optionLoading.value = false
options.value = res
}).catch((_: any) => {
optionLoading.value = false
})
}
}
const columnChange = (value: string, isChange: boolean) => {
const item = columnOptionMap.get(value)
// valueundefined
termsModel.column = value
termsModel.termType = item.defaultTermType || getTermType(item.type)
getComponent(item.type) // Item
// options request
if ('options' in item) {
handleItemOptions(item.options)
}
termsModel.value = undefined
if (isChange) {
valueChange()
}
}
const handleItem = () => {
columnOptionMap.clear()
columnOptions.value = []
if (!props.columns.length) return
//
const sortColumn = cloneDeep(props.columns)
sortColumn?.sort((a, b) => a.sortIndex! - b.sortIndex!)
const _index = props.index > sortColumn.length ? sortColumn.length - 1 : props.index
const _itemColumn = sortColumn[_index - 1]
termsModel.column = _itemColumn.column
termsModel.termType = _itemColumn.defaultTermType || getTermType(_itemColumn.type)
getComponent(_itemColumn.type) // Item
// options request
if ('options' in _itemColumn) {
handleItemOptions(_itemColumn.options)
}
columnOptions.value = props.columns.map(item => { // columnsMap
columnOptionMap.set(item.column, item)
return {
@ -216,9 +251,23 @@ const handleItem = () => {
value: item.column
}
})
//
const sortColumn = cloneDeep(props.columns)
sortColumn?.sort((a, b) => a.sortIndex! - b.sortIndex!)
const _index = props.index > sortColumn.length ? sortColumn.length - 1 : props.index
const _itemColumn = sortColumn[_index - 1]
columnChange(_itemColumn.column, false)
}
const valueChange = (value: any) => {
const termTypeChange = () => {
valueChange()
}
const valueChange = () => {
emit('change', {
type: termsModel.type,
value: termsModel.value,
@ -229,6 +278,27 @@ const valueChange = (value: any) => {
handleItem()
watch( props.termsItem, (newValue) => {
const path = props.index < 4 ? [0, 'terms', props.index - 1] : [1, 'terms', props.index - 4]
const itemData: SearchItemData = get(newValue.terms, path)
if (itemData) {
termsModel.type = itemData.type
termsModel.column = itemData.column
termsModel.termType = itemData.termType
termsModel.value = itemData.value
const item = columnOptionMap.get(itemData.column)
getComponent(item.type) // Item
// options request
if ('options' in item) {
handleItemOptions(item.options)
}
} else {
handleItem()
}
}, { immediate: true, deep: true })
</script>
<style scoped lang='less'>

View File

@ -4,9 +4,9 @@
<div v-if='props.type === "advanced"' :class='["JSearch-content senior", expand ? "senior-expand" : "", screenSize ? "big" : "small"]'>
<div :class='["JSearch-items", expand ? "items-expand" : "", layout]'>
<div class='left'>
<SearchItem :expand='expand' :index='1' :columns='searchItems' />
<SearchItem v-if='expand' :expand='expand' :index='2' :columns='searchItems' />
<SearchItem v-if='expand' :expand='expand' :index='3' :columns='searchItems' />
<SearchItem :expand='expand' :index='1' :columns='searchItems' @change='(v) => itemValueChange(v, 1)' :termsItem='terms'/>
<SearchItem v-if='expand' :expand='expand' :index='2' :columns='searchItems' @change='(v) => itemValueChange(v, 2)' :termsItem='terms'/>
<SearchItem v-if='expand' :expand='expand' :index='3' :columns='searchItems' @change='(v) => itemValueChange(v, 3)' :termsItem='terms'/>
</div>
<div class='center' v-if='expand'>
<a-select
@ -16,14 +16,14 @@
/>
</div>
<div class='right' v-if='expand'>
<SearchItem :expand='expand' :index='4' :columns='searchItems' />
<SearchItem :expand='expand' :index='5' :columns='searchItems' />
<SearchItem :expand='expand' :index='6' :columns='searchItems' />
<SearchItem :expand='expand' :index='4' :columns='searchItems' @change='(v) => itemValueChange(v, 4)' :termsItem='terms'/>
<SearchItem :expand='expand' :index='5' :columns='searchItems' @change='(v) => itemValueChange(v, 5)' :termsItem='terms'/>
<SearchItem :expand='expand' :index='6' :columns='searchItems' @change='(v) => itemValueChange(v, 6)' :termsItem='terms'/>
</div>
</div>
<div :class='["JSearch-footer", expand ? "expand" : ""]'>
<div class='JSearch-footer--btns'>
<a-dropdown-button type="primary">
<a-dropdown-button type="primary" @click='searchSubmit'>
搜索
<template #overlay>
<a-menu v-if='!!historyList.length'>
@ -39,7 +39,7 @@
<template #icon><PoweroffOutlined /></template>
保存
</a-button>
<a-button>
<a-button @click='reset'>
<template #icon><PoweroffOutlined /></template>
重置
</a-button>
@ -54,16 +54,16 @@
<div v-else class='JSearch-content simple big'>
<div class='JSearch-items'>
<div class='left'>
<SearchItem :expand='false' :index='1' :columns='searchItems' />
<SearchItem :expand='false' :index='1' :columns='searchItems' @change='(v) => itemValueChange(v, 1)' :termsItem='terms'/>
</div>
</div>
<div class='JSearch-footer'>
<div class='JSearch-footer--btns'>
<a-button type="primary">
<a-button type="primary" @click='searchSubmit'>
<template #icon><SearchOutlined /></template>
搜索
</a-button>
<a-button>
<a-button @click='reset'>
<template #icon><PoweroffOutlined /></template>
重置
</a-button>
@ -76,18 +76,23 @@
<script setup lang='ts' name='Search'>
import SearchItem from './Item.vue'
import { typeOptions } from './util'
import { useElementSize } from '@vueuse/core'
import { omit } from 'lodash-es'
import { useElementSize, useUrlSearchParams } from '@vueuse/core'
import { cloneDeep, isFunction, set } from 'lodash-es'
import { SearchOutlined, DownOutlined } from '@ant-design/icons-vue';
import type { SearchItemProps } from './types'
import { PropType } from 'vue'
import { JColumnsProps } from 'components/Table/types'
import type { SearchItemData, SearchProps, Terms } from './types'
type UrlParam = {
q: string | null
target: string | null
}
interface Emit {
(e: 'search', data: Terms): void
}
const props = defineProps({
defaultParams: {
type: Object,
default: () => ({})
},
columns: {
type: Array as PropType<JColumnsProps[]>,
default: () => [],
@ -97,7 +102,7 @@ const props = defineProps({
type: String,
default: 'advanced'
},
key: {
target: {
type: String,
default: '',
required: true
@ -107,11 +112,13 @@ const props = defineProps({
const searchRef = ref(null)
const { width } = useElementSize(searchRef)
const urlParams = useUrlSearchParams<UrlParam>('hash')
//
const expand = ref(false)
//
const termType = ref('or')
const termType = ref('and')
//
const historyList = ref([])
@ -120,7 +127,13 @@ const layout = ref('horizontal')
// true 1000
const screenSize = ref(true)
const searchItems = ref<SearchItemProps[]>([])
const searchItems = ref<SearchProps[]>([])
//
const terms = reactive<Terms>({ terms: [] })
const columnOptionMap = new Map()
const emit = defineEmits<Emit>()
const expandChange = () => {
expand.value = !expand.value
@ -132,10 +145,12 @@ const searchParams = reactive({
const handleItems = () => {
searchItems.value = []
columnOptionMap.clear()
props.columns!.forEach((item, index) => {
if (item.search && Object.keys(item.search).length) {
columnOptionMap.set(item.dataIndex, item.search)
searchItems.value.push({
...omit(item.search, ['rename']),
...item.search,
sortIndex: item.search.first ? 0 : index + 1,
title: item.title,
column: item.dataIndex,
@ -144,6 +159,64 @@ const handleItems = () => {
})
}
const itemValueChange = (value: SearchItemData, index: number) => {
if (index < 4) { //
set(terms.terms, [0, 'terms', index - 1], value)
} else { //
set(terms.terms, [1, 'terms', index - 4], value)
}
}
const addUrlParams = () => {
urlParams.q = JSON.stringify(terms)
urlParams.target = props.target
}
/**
* 处理为外部使用
*/
const handleParamsFormat = () => {
// termsvalueitem
const cloneParams = cloneDeep(terms)
return {
terms: cloneParams.terms.map(item => {
if (item.terms) {
item.terms = item.terms.filter(iItem => iItem && iItem.value).map(iItem => {
// handleValuerename
const _item = columnOptionMap.get(iItem.column)
if (_item.rename) {
iItem.column = _item.rename
}
if (_item.handleValue && isFunction(_item.handleValue)) {
iItem.value = _item.handleValue(iItem.value, iItem)
}
return iItem
})
}
return item
})
}
}
/**
* 提交
*/
const searchSubmit = () => {
emit('search', handleParamsFormat())
addUrlParams()
}
/**
* 重置查询
*/
const reset = () => {
terms.terms = []
expand.value = false
urlParams.q = null
urlParams.target = null
}
watch(width, (value) => {
if (value < 1000) {
layout.value = 'vertical'
@ -154,6 +227,30 @@ watch(width, (value) => {
}
})
/**
* 处理URL中的查询参数
* @param _params
*/
const handleUrlParams = (_params: UrlParam) => {
// URLtargetprops
if (_params.target === props.target && _params.q) {
try {
terms.terms = JSON.parse(_params.q)?.terms || []
console.log(terms)
if (terms.terms.length === 2) {
expand.value = true
}
emit('search', handleParamsFormat())
} catch (e) {
console.warn(`Search组件中handleUrlParams处理JSON时异常${e}`)
}
}
}
nextTick(() => {
handleUrlParams(urlParams)
})
handleItems()
</script>

View File

@ -1,4 +1,4 @@
export interface SearchProps {
export interface SearchBaseProps {
rename?: string
type?: 'select' | 'number' | 'string' | 'treeSelect' | 'date' | 'time'
format?: string
@ -7,15 +7,41 @@ export interface SearchProps {
defaultTermType?: string // 默认 eq
title?: ColumnType.title
sortIndex?: number
handleValue?: (value: SearchItemData) => any
}
export interface SearchItemProps {
rename?: SearchBaseProps['rename']
title: string
column: ColumnType.dataIndex
}
export interface SearchItemData {
column: ColumnType.dataIndex
rename?: string
value: any
termType: string
type?: string
title: string
}
export interface SearchItemProps extends SearchProps, SearchItemData {}
export interface TermsItem {
terms: SearchItemData[]
}
export interface Terms {
terms: TermsItem[]
}
export interface SortItem {
name: string
order?: 'desc' | 'asc'
value?: any
}
export interface Params {
sorts: SortItem[]
terms: Terms['terms']
}
export interface SearchProps extends SearchBaseProps, SearchItemProps {
}

View File

@ -1,7 +1,7 @@
import { SearchItemProps } from 'components/Search/types'
import { SearchProps } from 'components/Search/types'
import { ColumnType } from 'ant-design-vue/es/table'
export interface JColumnsProps extends ColumnType{
scopedSlots?: boolean;
search: SearchItemProps
search: SearchProps
}

View File

@ -2,13 +2,22 @@
<div class='search'>
<Search
:columns='columns'
target='device'
@search='search'
/>
<Search
type='simple'
:columns='columns'
target='product'
@search='search'
/>
<Search type='simple' :columns='columns' />
</div>
</template>
<script setup name='demoSearch'>
import { category } from '../../api/device/product'
const columns = [
{
title: '名称',
@ -31,7 +40,7 @@ const columns = [
value: 'test3'
},
],
handValue: (v) => {
handleValue: (v) => {
return '123'
}
}
@ -77,9 +86,13 @@ const columns = [
search: {
first: true,
type: 'treeSelect',
// options: async () => {
// return await
// }
options: async () => {
return new Promise((res) => {
category().then(resp => {
res(resp.result)
})
})
}
}
},
{
@ -90,6 +103,9 @@ const columns = [
scopedSlots: true,
}
]
const search = (params) => {
console.log(params)
}
</script>
<style scoped>