update: 优化Search组件
This commit is contained in:
parent
2315a05224
commit
8c7295b074
|
@ -25,7 +25,7 @@
|
||||||
"event-source-polyfill": "^1.0.31",
|
"event-source-polyfill": "^1.0.31",
|
||||||
"global": "^4.4.0",
|
"global": "^4.4.0",
|
||||||
"jetlinks-store": "^0.0.3",
|
"jetlinks-store": "^0.0.3",
|
||||||
"jetlinks-ui-components": "^1.0.4",
|
"jetlinks-ui-components": "^1.0.5",
|
||||||
"js-cookie": "^3.0.1",
|
"js-cookie": "^3.0.1",
|
||||||
"less": "^4.1.3",
|
"less": "^4.1.3",
|
||||||
"less-loader": "^11.1.0",
|
"less-loader": "^11.1.0",
|
||||||
|
|
|
@ -1,125 +0,0 @@
|
||||||
<template>
|
|
||||||
<a-dropdown-button
|
|
||||||
type='primary'
|
|
||||||
@click='click'
|
|
||||||
placement='bottomLeft'
|
|
||||||
:visible='historyVisible'
|
|
||||||
@visibleChange='visibleChange'
|
|
||||||
>
|
|
||||||
搜索
|
|
||||||
<template #overlay>
|
|
||||||
<a-menu>
|
|
||||||
<template v-if='!showEmpty'>
|
|
||||||
<a-menu-item v-for='item in historyList' :key='item.id'>
|
|
||||||
<div class='history-item'>
|
|
||||||
<span @click.stop='itemClick(item.content)'>{{ item.name }}</span>
|
|
||||||
<a-popconfirm
|
|
||||||
title='确认删除吗?'
|
|
||||||
placement='top'
|
|
||||||
@confirm.stop='deleteHistory(item.id)'
|
|
||||||
:okButtonProps='{
|
|
||||||
loading: deleteLoading
|
|
||||||
}'
|
|
||||||
>
|
|
||||||
<span class='delete'>
|
|
||||||
<DeleteOutlined />
|
|
||||||
</span>
|
|
||||||
</a-popconfirm>
|
|
||||||
</div>
|
|
||||||
</a-menu-item>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<div class='history-empty'>
|
|
||||||
<a-empty />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</a-menu>
|
|
||||||
</template>
|
|
||||||
<template #icon>
|
|
||||||
<SearchOutlined />
|
|
||||||
</template>
|
|
||||||
</a-dropdown-button>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang='ts' name='SearchHistory'>
|
|
||||||
import { SearchOutlined, DeleteOutlined } from '@ant-design/icons-vue'
|
|
||||||
import { deleteSearchHistory, getSearchHistory } from '@/api/comm'
|
|
||||||
import type { SearchHistoryList } from 'components/Search/types'
|
|
||||||
|
|
||||||
type Emit = {
|
|
||||||
(event: 'click'): void
|
|
||||||
(event: 'itemClick', data: string): void
|
|
||||||
}
|
|
||||||
const emit = defineEmits<Emit>()
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
target: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const historyList = ref<SearchHistoryList[]>([])
|
|
||||||
const historyVisible = ref(false)
|
|
||||||
const deleteLoading = ref(false)
|
|
||||||
const showEmpty = ref(false)
|
|
||||||
|
|
||||||
const visibleChange = async (visible: boolean) => {
|
|
||||||
historyVisible.value = visible
|
|
||||||
if (visible) {
|
|
||||||
const resp = await getSearchHistory(props.target)
|
|
||||||
if (resp.success && resp.result.length) {
|
|
||||||
historyList.value = resp.result.filter(item => item.content)
|
|
||||||
showEmpty.value = false
|
|
||||||
} else {
|
|
||||||
showEmpty.value = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const click = () => {
|
|
||||||
emit('click')
|
|
||||||
}
|
|
||||||
|
|
||||||
const itemClick = (content: string) => {
|
|
||||||
historyVisible.value = false
|
|
||||||
emit('itemClick', content)
|
|
||||||
}
|
|
||||||
|
|
||||||
const deleteHistory = async (id: string) => {
|
|
||||||
deleteLoading.value = true
|
|
||||||
const resp = await deleteSearchHistory(props.target, id)
|
|
||||||
deleteLoading.value = false
|
|
||||||
if (resp.success) {
|
|
||||||
historyVisible.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang='less'>
|
|
||||||
.history-empty {
|
|
||||||
width: 200px;
|
|
||||||
background-color: #fff;
|
|
||||||
box-shadow: @box-shadow-base;
|
|
||||||
border-radius: 2px;
|
|
||||||
overflow-y: auto;
|
|
||||||
overflow-x: hidden;
|
|
||||||
max-height: 200px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.history-item {
|
|
||||||
width: 200px;
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
> span {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.delete {
|
|
||||||
padding: 0 6px;
|
|
||||||
flex: 0 0 28px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,349 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class='JSearch-item'>
|
|
||||||
<div class='JSearch-item--type' v-if='expand'>
|
|
||||||
<a-select
|
|
||||||
v-if='index !== 1 && index !== 4'
|
|
||||||
:options='typeOptions'
|
|
||||||
v-model:value='termsModel.type'
|
|
||||||
style='width: 100%;'
|
|
||||||
@change='valueChange'
|
|
||||||
/>
|
|
||||||
<span v-else>
|
|
||||||
{{
|
|
||||||
index === 1 ? '第一组' : '第二组'
|
|
||||||
}}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<a-select
|
|
||||||
class='JSearch-item--column'
|
|
||||||
:options='columnOptions'
|
|
||||||
v-model:value='termsModel.column'
|
|
||||||
@change='columnChange'
|
|
||||||
/>
|
|
||||||
<a-select
|
|
||||||
class='JSearch-item--termType'
|
|
||||||
: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='valueChange'
|
|
||||||
/>
|
|
||||||
<a-select
|
|
||||||
v-else-if='component === componentType.select'
|
|
||||||
showSearch
|
|
||||||
:loading='optionLoading'
|
|
||||||
v-model:value='termsModel.value'
|
|
||||||
:options='options'
|
|
||||||
style='width: 100%'
|
|
||||||
: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='valueChange'
|
|
||||||
/>
|
|
||||||
<a-input-password
|
|
||||||
v-else-if='component === componentType.password'
|
|
||||||
v-model:value='termsModel.value'
|
|
||||||
style='width: 100%'
|
|
||||||
@change='valueChange'
|
|
||||||
/>
|
|
||||||
<a-switch
|
|
||||||
v-else-if='component === componentType.switch'
|
|
||||||
v-model:checked='termsModel.value'
|
|
||||||
style='width: 100%'
|
|
||||||
@change='valueChange'
|
|
||||||
/>
|
|
||||||
<a-radio-group
|
|
||||||
v-else-if='component === componentType.radio'
|
|
||||||
v-model:value='termsModel.value'
|
|
||||||
style='width: 100%'
|
|
||||||
@change='valueChange'
|
|
||||||
/>
|
|
||||||
<a-checkbox-group
|
|
||||||
v-else-if='component === componentType.checkbox'
|
|
||||||
v-model:value='termsModel.value'
|
|
||||||
:options='options'
|
|
||||||
style='width: 100%'
|
|
||||||
@change='valueChange'
|
|
||||||
/>
|
|
||||||
<a-time-picker
|
|
||||||
v-else-if='component === componentType.time'
|
|
||||||
valueFormat='HH:mm:ss'
|
|
||||||
v-model:value='termsModel.value'
|
|
||||||
style='width: 100%'
|
|
||||||
@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='valueChange'
|
|
||||||
/>
|
|
||||||
<a-tree-select
|
|
||||||
v-else-if='component === componentType.treeSelect'
|
|
||||||
showSearch
|
|
||||||
v-model:value='termsModel.value'
|
|
||||||
:tree-data='options'
|
|
||||||
style='width: 100%'
|
|
||||||
:fieldNames='{ label: "name", value: "id" }'
|
|
||||||
@change='valueChange'
|
|
||||||
:filterTreeNode='(v, option) => filterSelectNode(v, option)'
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang='ts' name='SearchItem'>
|
|
||||||
import { componentType } from 'components/Form'
|
|
||||||
import { typeOptions, termType } from './util'
|
|
||||||
import { PropType } from 'vue'
|
|
||||||
import type { SearchItemData, SearchProps, Terms } from './types'
|
|
||||||
import { cloneDeep, get, isArray, isFunction } from 'lodash-es'
|
|
||||||
import { filterTreeSelectNode, filterSelectNode } from '@/utils/comm'
|
|
||||||
import { useUrlSearchParams } from '@vueuse/core'
|
|
||||||
|
|
||||||
type ItemType = SearchProps['type']
|
|
||||||
|
|
||||||
interface Emit {
|
|
||||||
(e: 'change', data: SearchItemData): void
|
|
||||||
}
|
|
||||||
type UrlParam = {
|
|
||||||
q: string | null
|
|
||||||
target: string | null
|
|
||||||
}
|
|
||||||
|
|
||||||
const urlParams = useUrlSearchParams<UrlParam>('hash')
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
columns: {
|
|
||||||
type: Array as PropType<SearchProps[]>,
|
|
||||||
default: () => [],
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
index: {
|
|
||||||
type: Number,
|
|
||||||
default: 1
|
|
||||||
},
|
|
||||||
expand: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
termsItem: {
|
|
||||||
type: Object as PropType<Terms>,
|
|
||||||
default: {}
|
|
||||||
},
|
|
||||||
reset: {
|
|
||||||
type: Number,
|
|
||||||
default: 1
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const emit = defineEmits<Emit>()
|
|
||||||
|
|
||||||
const termsModel = reactive<SearchItemData>({
|
|
||||||
type: 'or',
|
|
||||||
value: '',
|
|
||||||
termType: 'like',
|
|
||||||
column: ''
|
|
||||||
})
|
|
||||||
|
|
||||||
const component = ref(componentType.input)
|
|
||||||
|
|
||||||
const options = ref<any[]>([])
|
|
||||||
|
|
||||||
const columnOptions = ref<({ label: string, value: string})[]>([])
|
|
||||||
const columnOptionMap = new Map()
|
|
||||||
|
|
||||||
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':
|
|
||||||
case 'number':
|
|
||||||
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':
|
|
||||||
component.value = componentType.select
|
|
||||||
break;
|
|
||||||
case 'treeSelect':
|
|
||||||
component.value = componentType.treeSelect
|
|
||||||
break;
|
|
||||||
case 'date':
|
|
||||||
component.value = componentType.date
|
|
||||||
break;
|
|
||||||
case 'time':
|
|
||||||
component.value = componentType.time
|
|
||||||
break;
|
|
||||||
case 'number':
|
|
||||||
component.value = componentType.inputNumber
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
component.value = componentType.input
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleItemOptions = (option?: any[] | Function) => {
|
|
||||||
options.value = []
|
|
||||||
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)
|
|
||||||
optionLoading.value = false
|
|
||||||
// 设置value为undefined
|
|
||||||
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
|
|
||||||
|
|
||||||
columnOptions.value = props.columns.map(item => { // 对columns进行Map处理以及值处理
|
|
||||||
columnOptionMap.set(item.column, item)
|
|
||||||
return {
|
|
||||||
label: item.title,
|
|
||||||
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 termTypeChange = () => {
|
|
||||||
valueChange()
|
|
||||||
}
|
|
||||||
|
|
||||||
const valueChange = () => {
|
|
||||||
|
|
||||||
emit('change', {
|
|
||||||
type: termsModel.type,
|
|
||||||
value: termsModel.value,
|
|
||||||
termType: termsModel.termType,
|
|
||||||
column: termsModel.column,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleQuery = (_params: UrlParam) => {
|
|
||||||
if (_params.q) {
|
|
||||||
const path = props.index < 4 ? [0, 'terms', props.index - 1] : [1, 'terms', props.index - 4]
|
|
||||||
const itemData: SearchItemData = get(props.termsItem.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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleItem()
|
|
||||||
|
|
||||||
nextTick(() => {
|
|
||||||
handleQuery(urlParams)
|
|
||||||
})
|
|
||||||
|
|
||||||
watch(() => props.reset, () => {
|
|
||||||
handleItem()
|
|
||||||
})
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang='less'>
|
|
||||||
.JSearch-item {
|
|
||||||
display: flex;
|
|
||||||
gap: 16px;
|
|
||||||
|
|
||||||
.JSearch-item--type {
|
|
||||||
min-width: 80px;
|
|
||||||
> span {
|
|
||||||
line-height: 34px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.JSearch-item--column {
|
|
||||||
min-width: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.JSearch-item--termType {
|
|
||||||
min-width: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.JSearch-item--value {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,100 +0,0 @@
|
||||||
<template>
|
|
||||||
<a-popover
|
|
||||||
title='搜索名称'
|
|
||||||
trigger='click'
|
|
||||||
v-model:visible='visible'
|
|
||||||
@visibleChange='visibleChange'
|
|
||||||
>
|
|
||||||
<template #content>
|
|
||||||
<div style='width: 240px'>
|
|
||||||
<a-form ref='formRef' :model='modelRef'>
|
|
||||||
<a-form-item
|
|
||||||
name='name'
|
|
||||||
:rules='[
|
|
||||||
{ required: true, message: "请输入名称"}
|
|
||||||
]'
|
|
||||||
>
|
|
||||||
<a-textarea
|
|
||||||
v-model:value='modelRef.name'
|
|
||||||
:rows='3'
|
|
||||||
:maxlength='200'
|
|
||||||
/>
|
|
||||||
</a-form-item>
|
|
||||||
</a-form>
|
|
||||||
<a-button
|
|
||||||
:loading='saveHistoryLoading'
|
|
||||||
type='primary'
|
|
||||||
class='save-btn'
|
|
||||||
@click='saveHistory'
|
|
||||||
>
|
|
||||||
保存
|
|
||||||
</a-button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<a-button>
|
|
||||||
<template #icon>
|
|
||||||
<SaveOutlined />
|
|
||||||
</template>
|
|
||||||
保存
|
|
||||||
</a-button>
|
|
||||||
</a-popover>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang='ts' name='SaveHistory'>
|
|
||||||
import type { Terms } from './types'
|
|
||||||
import { PropType } from 'vue'
|
|
||||||
import { saveSearchHistory } from '@/api/comm'
|
|
||||||
import { SaveOutlined } from '@ant-design/icons-vue';
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
terms: {
|
|
||||||
type: Object as PropType<Terms>,
|
|
||||||
default: () => ({})
|
|
||||||
},
|
|
||||||
target: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const searchName = ref('')
|
|
||||||
|
|
||||||
const saveHistoryLoading = ref(false)
|
|
||||||
|
|
||||||
const visible = ref(false)
|
|
||||||
|
|
||||||
const formRef = ref()
|
|
||||||
|
|
||||||
const modelRef = reactive({
|
|
||||||
name: undefined
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 保存当前查询条件
|
|
||||||
*/
|
|
||||||
const saveHistory = async () => {
|
|
||||||
// 获取当前查询条件并转化为字符串
|
|
||||||
const formData = await formRef.value.validate()
|
|
||||||
if (formData) {
|
|
||||||
formData.content = JSON.stringify(props.terms)
|
|
||||||
saveHistoryLoading.value = true
|
|
||||||
const resp = await saveSearchHistory(formData, props.target)
|
|
||||||
saveHistoryLoading.value = false
|
|
||||||
if (resp.success) {
|
|
||||||
visible.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const visibleChange = (e: boolean) => {
|
|
||||||
visible.value = e
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang='less'>
|
|
||||||
.save-btn {
|
|
||||||
width: 100%
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,83 +1,21 @@
|
||||||
<template>
|
<template>
|
||||||
<div :class="['JSearch-warp', props.class]" ref='searchRef'>
|
<j-advanced-search
|
||||||
<!-- 高级模式 -->
|
:target='target'
|
||||||
<div v-if='props.type === "advanced"' :class='["JSearch-content senior", expand ? "senior-expand" : "", screenSize ? "big" : "small"]'>
|
:type='type'
|
||||||
<div :class='["JSearch-items", expand ? "items-expand" : "", layout]'>
|
:request='saveSearchHistory'
|
||||||
<div class='left'>
|
:historyRequest='getSearchHistory'
|
||||||
<SearchItem :expand='expand' :index='1' :columns='searchItems' @change='(v) => itemValueChange(v, 1)' :termsItem='terms' :reset='resetNumber'/>
|
:columns='columns'
|
||||||
<SearchItem v-if='expand' :expand='expand' :index='2' :columns='searchItems' @change='(v) => itemValueChange(v, 2)' :termsItem='terms' :reset='resetNumber'/>
|
@search='searchSubmit'
|
||||||
<SearchItem v-if='expand' :expand='expand' :index='3' :columns='searchItems' @change='(v) => itemValueChange(v, 3)' :termsItem='terms' :reset='resetNumber'/>
|
/>
|
||||||
</div>
|
|
||||||
<div class='center' v-if='expand'>
|
|
||||||
<a-select
|
|
||||||
v-model:value='termType'
|
|
||||||
class='center-select'
|
|
||||||
:options='typeOptions'
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class='right' v-if='expand'>
|
|
||||||
<SearchItem :expand='expand' :index='4' :columns='searchItems' @change='(v) => itemValueChange(v, 4)' :termsItem='terms' :reset='resetNumber'/>
|
|
||||||
<SearchItem :expand='expand' :index='5' :columns='searchItems' @change='(v) => itemValueChange(v, 5)' :termsItem='terms' :reset='resetNumber'/>
|
|
||||||
<SearchItem :expand='expand' :index='6' :columns='searchItems' @change='(v) => itemValueChange(v, 6)' :termsItem='terms' :reset='resetNumber'/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div :class='["JSearch-footer", expand ? "expand" : ""]'>
|
|
||||||
<div class='JSearch-footer--btns'>
|
|
||||||
<History :target='target' @click='searchSubmit' @itemClick='historyItemClick' />
|
|
||||||
<SaveHistory :terms='terms' :target='target'/>
|
|
||||||
<a-button @click='reset'>
|
|
||||||
<template #icon><RedoOutlined /></template>
|
|
||||||
重置
|
|
||||||
</a-button>
|
|
||||||
</div>
|
|
||||||
<a-button type='link' class='more-btn' @click='expandChange'>
|
|
||||||
更多筛选
|
|
||||||
<DownOutlined :class='["more-icon",expand ? "more-up" : "more-down"]' />
|
|
||||||
</a-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- 简单模式 -->
|
|
||||||
<div v-else class='JSearch-content simple big'>
|
|
||||||
<div class='JSearch-items'>
|
|
||||||
<div class='left'>
|
|
||||||
<SearchItem :expand='false' :index='1' :columns='searchItems' @change='(v) => itemValueChange(v, 1)' :termsItem='terms' :reset='resetNumber'/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class='JSearch-footer'>
|
|
||||||
<div class='JSearch-footer--btns'>
|
|
||||||
<a-button type="primary" @click='searchSubmit'>
|
|
||||||
<template #icon><SearchOutlined /></template>
|
|
||||||
搜索
|
|
||||||
</a-button>
|
|
||||||
<a-button @click='reset'>
|
|
||||||
<template #icon><RedoOutlined /></template>
|
|
||||||
重置
|
|
||||||
</a-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang='ts' name='Search'>
|
<script setup lang='ts' name='ProSearch'>
|
||||||
import SearchItem from './Item.vue'
|
|
||||||
import { typeOptions } from './util'
|
|
||||||
import { useElementSize, useUrlSearchParams } from '@vueuse/core'
|
|
||||||
import { cloneDeep, isFunction, isString, set } from 'lodash-es'
|
|
||||||
import { SearchOutlined, DownOutlined, RedoOutlined } from '@ant-design/icons-vue';
|
|
||||||
import { PropType } from 'vue'
|
import { PropType } from 'vue'
|
||||||
import { JColumnsProps } from 'components/Table/types'
|
import { JColumnsProps } from 'components/Table/types'
|
||||||
import SaveHistory from './SaveHistory.vue'
|
import { saveSearchHistory, getSearchHistory } from '@/api/comm'
|
||||||
import History from './History.vue'
|
|
||||||
import type { SearchItemData, SearchProps, Terms } from './types'
|
|
||||||
|
|
||||||
type UrlParam = {
|
|
||||||
q: string | null
|
|
||||||
target: string | null
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Emit {
|
interface Emit {
|
||||||
(e: 'search', data: Terms): void
|
(e: 'search', data: any): void
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
@ -98,299 +36,20 @@ const props = defineProps({
|
||||||
class: {
|
class: {
|
||||||
type: String,
|
type: String,
|
||||||
default: ''
|
default: ''
|
||||||
},
|
}
|
||||||
// defaultTerms: {
|
|
||||||
// type: Object,
|
|
||||||
// default: () => ({})
|
|
||||||
// }
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const searchRef = ref(null)
|
|
||||||
const { width } = useElementSize(searchRef)
|
|
||||||
|
|
||||||
const urlParams = useUrlSearchParams<UrlParam>('hash')
|
|
||||||
|
|
||||||
// 是否展开更多筛选
|
|
||||||
const expand = ref(false)
|
|
||||||
|
|
||||||
// 第一组,第二组的关系
|
|
||||||
const termType = ref('and')
|
|
||||||
// 搜索历史记录
|
|
||||||
const historyList = ref([])
|
|
||||||
|
|
||||||
// 组件方向
|
|
||||||
const layout = ref('horizontal')
|
|
||||||
// 当前组件宽度 true 大于1000
|
|
||||||
const screenSize = ref(true)
|
|
||||||
const resetNumber = ref(1)
|
|
||||||
|
|
||||||
const searchItems = ref<SearchProps[]>([])
|
|
||||||
|
|
||||||
// 当前查询条件
|
|
||||||
const terms = reactive<Terms>({ terms: [] })
|
|
||||||
|
|
||||||
const columnOptionMap = new Map()
|
|
||||||
|
|
||||||
const emit = defineEmits<Emit>()
|
const emit = defineEmits<Emit>()
|
||||||
|
|
||||||
const expandChange = () => {
|
|
||||||
expand.value = !expand.value
|
|
||||||
}
|
|
||||||
|
|
||||||
const searchParams = reactive({
|
|
||||||
data: {}
|
|
||||||
})
|
|
||||||
|
|
||||||
const handleItems = () => {
|
|
||||||
searchItems.value = []
|
|
||||||
columnOptionMap.clear()
|
|
||||||
const cloneColumns = cloneDeep(props.columns)
|
|
||||||
cloneColumns!.forEach((item, index) => {
|
|
||||||
if (item.search && Object.keys(item.search).length) {
|
|
||||||
columnOptionMap.set(item.dataIndex, item.search)
|
|
||||||
searchItems.value.push({
|
|
||||||
...item.search,
|
|
||||||
sortIndex: item.search.first ? 0 : index + 1,
|
|
||||||
title: item.title,
|
|
||||||
column: item.dataIndex,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理termType为like,nlike的值
|
|
||||||
* @param v
|
|
||||||
*/
|
|
||||||
const handleLikeValue = (v: string) => {
|
|
||||||
if (isString(v)) {
|
|
||||||
return v.split('').reduce((pre: string, next: string) => {
|
|
||||||
let _next = next
|
|
||||||
if (next === '\\') {
|
|
||||||
_next = '\\\\'
|
|
||||||
} else if (next === '%') {
|
|
||||||
_next = '\\%'
|
|
||||||
}
|
|
||||||
return pre + _next
|
|
||||||
}, '')
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理为外部使用
|
|
||||||
*/
|
|
||||||
const handleParamsFormat = () => {
|
|
||||||
// 过滤掉terms中value无效的item
|
|
||||||
const cloneParams = cloneDeep(terms)
|
|
||||||
return {
|
|
||||||
terms: cloneParams.terms.map(item => {
|
|
||||||
if (item.terms) {
|
|
||||||
item.terms = item.terms.filter(iItem => iItem && iItem.value)
|
|
||||||
.map(iItem => {
|
|
||||||
// 处理handleValue和rename
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (['like','nlike'].includes(iItem.termType) && !!iItem.value) {
|
|
||||||
iItem.value = `%${handleLikeValue(iItem.value)}%`
|
|
||||||
}
|
|
||||||
return iItem
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return item
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 提交
|
* 提交
|
||||||
*/
|
*/
|
||||||
const searchSubmit = () => {
|
const searchSubmit = (data: any) => {
|
||||||
emit('search', handleParamsFormat())
|
emit('search', data)
|
||||||
console.log('searchSubmit')
|
|
||||||
if (props.type === 'advanced') {
|
|
||||||
addUrlParams()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 重置查询
|
|
||||||
*/
|
|
||||||
const reset = () => {
|
|
||||||
terms.terms = []
|
|
||||||
expand.value = false
|
|
||||||
if (props.type === 'advanced') {
|
|
||||||
urlParams.q = null
|
|
||||||
urlParams.target = null
|
|
||||||
}
|
|
||||||
resetNumber.value += 1
|
|
||||||
emit('search', { terms: []})
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(width, (value) => {
|
|
||||||
if (value < 1000) {
|
|
||||||
layout.value = 'vertical'
|
|
||||||
screenSize.value = false
|
|
||||||
} else {
|
|
||||||
layout.value = 'horizontal'
|
|
||||||
screenSize.value = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const historyItemClick = (content: string) => {
|
|
||||||
try {
|
|
||||||
terms.terms = JSON.parse(content)?.terms || []
|
|
||||||
if (terms.terms.length === 2) {
|
|
||||||
expand.value = true
|
|
||||||
}
|
|
||||||
addUrlParams()
|
|
||||||
} catch (e) {
|
|
||||||
console.warn(`Search组件中handleUrlParams处理JSON时异常:【${e}】`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理URL中的查询参数
|
|
||||||
* @param _params
|
|
||||||
*/
|
|
||||||
const handleUrlParams = (_params: UrlParam) => {
|
|
||||||
// URL中的target和props的一致,则还原查询参数
|
|
||||||
if (_params.target === props.target && _params.q) {
|
|
||||||
try {
|
|
||||||
terms.terms = JSON.parse(_params.q)?.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>
|
</script>
|
||||||
|
|
||||||
<style scoped lang='less'>
|
<style scoped lang='less'>
|
||||||
.JSearch-warp {
|
|
||||||
padding: 24px;
|
|
||||||
background-color: #fff;
|
|
||||||
margin-bottom: 24px;
|
|
||||||
|
|
||||||
.JSearch-content {
|
|
||||||
display: flex;
|
|
||||||
gap: 12px;
|
|
||||||
|
|
||||||
.JSearch-items,& .JSearch-footer {
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.JSearch-items {
|
|
||||||
display: flex;
|
|
||||||
gap: 16px;
|
|
||||||
|
|
||||||
.left, & .right {
|
|
||||||
display: flex;
|
|
||||||
gap: 12px;
|
|
||||||
flex-direction: column;
|
|
||||||
width: 0;
|
|
||||||
flex-grow: 1;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.center {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
min-width: 80px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.vertical {
|
|
||||||
flex-direction: column;
|
|
||||||
.left,& .right,& .center {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.center {
|
|
||||||
flex-direction: row;
|
|
||||||
}
|
|
||||||
.center-select {
|
|
||||||
width: 120px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.JSearch-footer {
|
|
||||||
display: flex;
|
|
||||||
gap: 64px;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&.expand {
|
|
||||||
margin-top: 12px;
|
|
||||||
width: 100%;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
.more-btn {
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.JSearch-footer--btns {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.senior-expand {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.more-up {
|
|
||||||
transform: rotate(-180deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.big {
|
|
||||||
gap: 64px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.small {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.simple {
|
|
||||||
.JSearch-items {
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
.JSearch-footer {
|
|
||||||
flex-grow: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
</style>
|
|
@ -5,7 +5,7 @@ import JTable from './Table/index'
|
||||||
import TitleComponent from "./TitleComponent/index.vue";
|
import TitleComponent from "./TitleComponent/index.vue";
|
||||||
import Form from './Form';
|
import Form from './Form';
|
||||||
import CardBox from './CardBox/index.vue';
|
import CardBox from './CardBox/index.vue';
|
||||||
// import Search from './Search'
|
import Search from './Search'
|
||||||
import NormalUpload from './NormalUpload/index.vue'
|
import NormalUpload from './NormalUpload/index.vue'
|
||||||
import FileFormat from './FileFormat/index.vue'
|
import FileFormat from './FileFormat/index.vue'
|
||||||
import JProUpload from './JUpload/index.vue'
|
import JProUpload from './JUpload/index.vue'
|
||||||
|
@ -25,7 +25,7 @@ export default {
|
||||||
.component('TitleComponent', TitleComponent)
|
.component('TitleComponent', TitleComponent)
|
||||||
.component('Form', Form)
|
.component('Form', Form)
|
||||||
.component('CardBox', CardBox)
|
.component('CardBox', CardBox)
|
||||||
// .component('Search', Search)
|
.component('ProSearch', Search)
|
||||||
.component('NormalUpload', NormalUpload)
|
.component('NormalUpload', NormalUpload)
|
||||||
.component('FileFormat', FileFormat)
|
.component('FileFormat', FileFormat)
|
||||||
.component('JProUpload', JProUpload)
|
.component('JProUpload', JProUpload)
|
||||||
|
|
|
@ -8,7 +8,7 @@ export const AccountMenu = {
|
||||||
meta: {
|
meta: {
|
||||||
title: '个人中心',
|
title: '个人中心',
|
||||||
icon: '',
|
icon: '',
|
||||||
hideInMenu: false
|
hideInMenu: true
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -37,6 +37,10 @@ type MenuStateType = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
siderMenus: MenuItem[]
|
siderMenus: MenuItem[]
|
||||||
|
params: {
|
||||||
|
key: string
|
||||||
|
params: Record<string, any>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -44,6 +48,10 @@ export const useMenuStore = defineStore({
|
||||||
id: 'menu',
|
id: 'menu',
|
||||||
state: (): MenuStateType => ({
|
state: (): MenuStateType => ({
|
||||||
menus: {},
|
menus: {},
|
||||||
|
params: {
|
||||||
|
key: '',
|
||||||
|
params: {}
|
||||||
|
},
|
||||||
siderMenus: []
|
siderMenus: []
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
:format='myFormat'
|
:format='myFormat'
|
||||||
:valueFormat='myFormat'
|
:valueFormat='myFormat'
|
||||||
:getPopupContainer='getPopupContainer'
|
:getPopupContainer='getPopupContainer'
|
||||||
|
popupClassName='manual-time-picker-popup'
|
||||||
@change='change'
|
@change='change'
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -62,7 +63,7 @@ const change = (e: string) => {
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang='less' scoped>
|
<style lang='less'>
|
||||||
.dropdown-time-picker {
|
.dropdown-time-picker {
|
||||||
>div{
|
>div{
|
||||||
position: relative !important;
|
position: relative !important;
|
||||||
|
@ -80,6 +81,9 @@ const change = (e: string) => {
|
||||||
.ant-picker-panel {
|
.ant-picker-panel {
|
||||||
width: 100%
|
width: 100%
|
||||||
}
|
}
|
||||||
|
.ant-picker-footer {
|
||||||
|
border-bottom: 0px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-picker-panel-container {
|
.ant-picker-panel-container {
|
||||||
|
|
|
@ -20,6 +20,10 @@ export const getComponent = (type: string): string => {
|
||||||
return 'date'
|
return 'date'
|
||||||
case 'tree':
|
case 'tree':
|
||||||
return 'tree'
|
return 'tree'
|
||||||
|
case 'file':
|
||||||
|
return 'file'
|
||||||
|
case 'geoPoint':
|
||||||
|
return 'geoPoint'
|
||||||
default:
|
default:
|
||||||
return 'input'
|
return 'input'
|
||||||
}
|
}
|
||||||
|
@ -33,7 +37,7 @@ export const getOption = (data: any[], value?: string | number | boolean, key: s
|
||||||
if (item[key] === value) {
|
if (item[key] === value) {
|
||||||
option = data[i]
|
option = data[i]
|
||||||
break
|
break
|
||||||
} else if (item.children && item.children.length){
|
} else if (item.children && item.children.length) {
|
||||||
option = getOption(item.children, value, key)
|
option = getOption(item.children, value, key)
|
||||||
if (option) {
|
if (option) {
|
||||||
break
|
break
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<page-container>
|
<page-container>
|
||||||
<j-advanced-search :columns="columns" target="scene" @search="handleSearch" />
|
<pro-search :columns="columns" target="scene" @search="handleSearch" />
|
||||||
<JProTable
|
<JProTable
|
||||||
ref="sceneRef"
|
ref="sceneRef"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
|
|
|
@ -3693,10 +3693,10 @@ jetlinks-store@^0.0.3:
|
||||||
resolved "https://registry.npmjs.org/jetlinks-store/-/jetlinks-store-0.0.3.tgz"
|
resolved "https://registry.npmjs.org/jetlinks-store/-/jetlinks-store-0.0.3.tgz"
|
||||||
integrity sha512-AZf/soh1hmmwjBZ00fr1emuMEydeReaI6IBTGByQYhTmK1Zd5pQAxC7WLek2snRAn/HHDgJfVz2hjditKThl6Q==
|
integrity sha512-AZf/soh1hmmwjBZ00fr1emuMEydeReaI6IBTGByQYhTmK1Zd5pQAxC7WLek2snRAn/HHDgJfVz2hjditKThl6Q==
|
||||||
|
|
||||||
jetlinks-ui-components@^1.0.4:
|
jetlinks-ui-components@^1.0.5:
|
||||||
version "1.0.4"
|
version "1.0.5"
|
||||||
resolved "https://registry.jetlinks.cn/jetlinks-ui-components/-/jetlinks-ui-components-1.0.4.tgz#41d52892f0f4d38adc6df02a87290a3042eb5645"
|
resolved "http://47.108.170.157:9013/jetlinks-ui-components/-/jetlinks-ui-components-1.0.5.tgz#de995a654e2613c988fac2e800b156285265c9be"
|
||||||
integrity sha512-aX+XiGigzxZnrG52xqipxd+WuFwBeZ6+dvLkcvOfLLBqSu8sgfvr/8NJ5hFgv5Eo2QFnUJq3Qf4HXLw9Ogv/yw==
|
integrity sha512-rZtqFmxhU9nplFqqp45bHJAoiH4zzLM8juMGQV6eCCkzquXLmDqATiAK2D/fquAcLMIY0fPgw/Dzc9odQDKmVg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@vueuse/core" "^9.12.0"
|
"@vueuse/core" "^9.12.0"
|
||||||
ant-design-vue "^3.2.15"
|
ant-design-vue "^3.2.15"
|
||||||
|
|
Loading…
Reference in New Issue