update: 优化Search组件

This commit is contained in:
xieyonghong 2023-01-13 14:29:16 +08:00
parent 5f6aa6ce65
commit 2a2a8121c3
11 changed files with 4268 additions and 3891 deletions

View File

@ -15,6 +15,7 @@
"dependencies": {
"@vitejs/plugin-vue-jsx": "^3.0.0",
"@vuemap/vue-amap": "^1.1.20",
"@vueuse/core": "^9.10.0",
"ant-design-vue": "^3.2.15",
"axios": "^1.2.1",
"echarts": "^5.4.1",

View File

@ -5,4 +5,4 @@ export const getDeviceCount_api = () => server.get(`/device/instance/_count`);
// 产品数量
export const getProductCount_api = (data) => server.post(`/device-product/_count`, data);
// 查询产品列表
export const getProductList_api = (data) => server.get(`/device/product/_query/no-paging?paging=false`, data);
export const getProductList_api = (data) => server.get(`/device/product/_query/no-paging?paging=false`, data);

View File

@ -1,6 +1,6 @@
<template>
<div class='JSearch-item'>
<div class='JSearch-item--type'>
<div class='JSearch-item--type' v-if='expand'>
<a-select
v-if='index !== 1 && index !== 4'
:options='typeOptions'
@ -27,45 +27,55 @@
<a-input
v-if='component === componentType.input'
v-model:value='termsModel.value'
@change='(v) => valueChange(v)'
/>
<a-select
v-else-if='component === componentType.select'
v-model:value='termsModel.value'
:options='options'
@change='(v) => valueChange(v)'
/>
<a-inputnumber
v-else-if='component === componentType.inputNumber'
v-model:value='termsModel.value'
@change='(v) => valueChange(v)'
/>
<a-input-password
v-else-if='component === componentType.password'
v-model:value='termsModel.value'
@change='(v) => valueChange(v)'
/>
<a-switch
v-else-if='component === componentType.switch'
v-model:checked='termsModel.value'
@change='(v) => valueChange(v)'
/>
<a-radio-group
v-else-if='component === componentType.radio'
v-model:value='termsModel.value'
@change='(v) => valueChange(v)'
/>
<a-checkbox-group
v-else-if='component === componentType.checkbox'
v-model:value='termsModel.value'
:options='options'
@change='(v) => valueChange(v)'
/>
<a-time-picker
v-else-if='component === componentType.time'
v-model:value='termsModel.value'
@change='(v) => valueChange(v)'
/>
<a-date-picker
v-else-if='component === componentType.date'
v-model:value='termsModel.value'
@change='(v) => valueChange(v)'
/>
<a-tree-select
v-else-if='component === componentType.tree'
v-model:value='termsModel.value'
:tree-data='options'
@change='(v) => valueChange(v)'
/>
</div>
</div>
@ -73,27 +83,99 @@
<script setup lang='ts' name='SearchItem'>
import { componentType } from 'components/Form'
import { typeOptions } from './util'
import { typeOptions, termType } from './util'
import { PropType } from 'vue'
import type { SearchItemProps, SearchItemData } from './types'
import { cloneDeep } from 'lodash-es'
type ItemDataProps = Omit<SearchItemData, 'title'>
interface Emit {
(e: 'change', data: ItemDataProps): void
}
const props = defineProps({
component: {
type: String,
default: componentType.input
columns: {
type: Array as PropType<SearchItemProps[]>,
default: () => [],
required: true
},
index: {
type: Number,
default: 1
},
expand: {
type: Boolean,
default: false
}
})
const termsModel = reactive({})
const emit = defineEmits<Emit>()
const termsModel = reactive<ItemDataProps>({
type: 'or',
value: '',
termType: 'eq',
column: ''
})
const component = ref(componentType.input)
const options = ref([])
const columnOptions = reactive([])
const columnOptions = ref<({ label: string, value: string})[]>([])
const columnOptionMap = new Map()
const termTypeOptions = reactive([])
const termTypeOptions = reactive(termType)
const getTermType = (type: string) => {
switch (type) {
case 'select':
case 'treeSelect':
return 'eq'
case 'date':
case 'time':
return 'gt'
default:
return 'like'
}
}
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 as string)
columnOptions.value = props.columns.map(item => {
columnOptionMap.set(item.column, item)
return {
label: item.title,
value: item.column
}
})
}
const valueChange = (value: any) => {
emit('change', {
type: termsModel.type,
value: termsModel.value,
termType: termsModel.termType,
column: termsModel.column,
})
}
handleItem()
</script>
@ -103,7 +185,7 @@ const termTypeOptions = reactive([])
gap: 16px;
.JSearch-item--type {
min-width: 120px;
min-width: 80px;
> span {
line-height: 34px;
font-weight: bold;
@ -111,11 +193,11 @@ const termTypeOptions = reactive([])
}
.JSearch-item--column {
min-width: 120px;
min-width: 100px;
}
.JSearch-item--termType {
min-width: 120px;
min-width: 100px;
}
.JSearch-item--value {

View File

@ -1,19 +1,74 @@
<template>
<div class='JSearch-content'>
<div class='left'>
<SearchItem :index='1' />
<SearchItem :index='2' />
<SearchItem :index='3' />
<div class='JSearch-warp' ref='searchRef'>
<!-- 高级模式 -->
<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' />
</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' />
<SearchItem :expand='expand' :index='5' :columns='searchItems' />
<SearchItem :expand='expand' :index='6' :columns='searchItems' />
</div>
</div>
<div :class='["JSearch-footer", expand ? "expand" : ""]'>
<div class='JSearch-footer--btns'>
<a-dropdown-button type="primary">
搜索
<template #overlay>
<a-menu v-if='!!historyList.length'>
<a-menu-item>
</a-menu-item>
</a-menu>
<a-empty v-else />
</template>
<template #icon><SearchOutlined /></template>
</a-dropdown-button>
<a-button>
<template #icon><PoweroffOutlined /></template>
保存
</a-button>
<a-button>
<template #icon><PoweroffOutlined /></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 class='center'>
<a-select
:options='typeOptions'
/>
</div>
<div class='right'>
<SearchItem :index='4' />
<SearchItem :index='5' />
<SearchItem :index='6' />
<!-- 简单模式 -->
<div v-else class='JSearch-content simple big'>
<div class='JSearch-items'>
<div class='left'>
<SearchItem :expand='false' :index='1' />
</div>
</div>
<div class='JSearch-footer'>
<div class='JSearch-footer--btns'>
<a-button type="primary">
<template #icon><SearchOutlined /></template>
搜索
</a-button>
<a-button>
<template #icon><PoweroffOutlined /></template>
重置
</a-button>
</div>
</div>
</div>
</div>
</template>
@ -21,6 +76,12 @@
<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 { SearchOutlined, DownOutlined } from '@ant-design/icons-vue';
import type { SearchItemProps } from './types'
import { PropType } from 'vue'
import { JColumnsProps } from 'components/Table/types'
const props = defineProps({
defaultParams: {
@ -28,14 +89,14 @@ const props = defineProps({
default: () => ({})
},
columns: {
type: Array,
default: () => []
type: Array as PropType<JColumnsProps[]>,
default: () => [],
required: true
},
type: {
type: String,
default: 'advanced'
},
key: {
type: String,
default: '',
@ -43,30 +104,153 @@ const props = defineProps({
}
})
const searchRef = ref(null)
const { width } = useElementSize(searchRef)
//
const expand = ref(false)
//
const termType = ref('or')
//
const historyList = ref([])
//
const layout = ref('horizontal')
// true 1000
const screenSize = ref(true)
const searchItems = ref<SearchItemProps[]>([])
const expandChange = () => {
expand.value = !expand.value
}
const searchParams = reactive({
data: {}
})
const handleItems = () => {
searchItems.value = []
props.columns!.forEach((item, index) => {
if (item.search && Object.keys(item.search).length) {
searchItems.value.push({
...omit(item.search, ['rename']),
sortIndex: item.search.first ? 0 : index + 1,
title: item.title,
column: item.dataIndex,
})
}
})
}
watch(width, (value) => {
if (value < 1000) {
layout.value = 'vertical'
screenSize.value = false
} else {
layout.value = 'horizontal'
screenSize.value = true
}
})
handleItems()
</script>
<style scoped lang='less'>
.JSearch-content {
display: flex;
gap: 16px;
.left, & .right {
.JSearch-warp {
padding: 24px;
background-color: #fff;
.JSearch-content {
display: flex;
gap: 16px;
flex-direction: column;
width: 0;
flex-grow: 1;
min-width: 0;
}
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;
.center {
display: flex;
flex-direction: column;
justify-content: center;
flex-basis: 120px;
.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: 4;
}
.JSearch-footer {
flex-grow: 3;
}
}
}
}
</style>

21
src/components/Search/types.d.ts vendored Normal file
View File

@ -0,0 +1,21 @@
export interface SearchProps {
rename?: string
type?: 'select' | 'number' | 'string' | 'treeSelect' | 'date' | 'time'
format?: string
options?: any[] | Function
first?: boolean
defaultTermType?: string // 默认 eq
title?: ColumnType.title
sortIndex?: number
}
export interface SearchItemData {
column: ColumnType.dataIndex
rename?: string
value: any
termType: string
type?: string
title: string
}
export interface SearchItemProps extends SearchProps, SearchItemData {}

View File

@ -1,4 +1,17 @@
export const typeOptions = [
{ label: '或者', value: 'or' },
{ label: '并且', value: 'and' },
]
]
export const termType = [
{ label: '=', value: 'eq' },
{ label: '!=', value: 'not' },
{ label: '包含', value: 'like' },
{ label: '不包含', value: 'nlike' },
{ label: '>', value: 'gt' },
{ label: '>=', value: 'gte' },
{ label: '<', value: 'lt' },
{ label: '<=', value: 'lte' },
{ label: '属于', value: 'in' },
{ label: '不属于', value: 'nin' },
];

7
src/components/Table/types.d.ts vendored Normal file
View File

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

View File

@ -15,7 +15,7 @@ const filterPath = [
router.beforeEach((to, from, next) => {
const token = LocalStore.get(TOKEN_KEY)
// TODO 切换路由取消请求
if (token || filterPath.includes(to.path)) {
next()
} else {

View File

@ -1,3 +1,5 @@
import { TOKEN_KEY } from '@/utils/variable'
/**
*
* @param path {String}
@ -29,4 +31,8 @@ export const LocalStore = {
removeAll() {
localStorage.clear()
}
}
export const getToken = () => {
return LocalStore.get(TOKEN_KEY)
}

View File

@ -1,11 +1,56 @@
<template>
<div class='search'>
<Search />
<Search
:columns='columns'
/>
<Search type='simple' />
</div>
</template>
<script>
<script setup name='demoSearch'>
const columns = [
{
title: '名称',
dataIndex: 'name',
key: 'name',
search: {
rename: 'deviceId',
type: 'select',
handValue: (v) => {
return '123'
}
}
},
{
title: 'ID',
dataIndex: 'id',
key: 'id',
scopedSlots: true,
search: {
type: 'string',
}
},
{
title: '分类',
dataIndex: 'classifiedName',
key: 'classifiedName',
search: {
first: true,
type: 'treeSelect',
// options: async () => {
// return await
// }
}
},
{
title: '操作',
key: 'action',
fixed: 'right',
width: 250,
scopedSlots: true,
}
]
</script>
<style scoped>

7704
yarn.lock

File diff suppressed because it is too large Load Diff