| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311 |
- <template>
- <div class="custom-search-container">
- <div ref="listRef" class="render-condition-list">
- <div v-for="(item, index) in conditions" :key="index" class="condition-item-row">
- <Select
- v-model:value="item.key"
- show-search
- placeholder="请选择字段"
- class="condition-field-select"
- :options="filterTableColumn"
- :field-names="{ label: 'label', value: 'prop' }"
- @change="() => (item.value = undefined)"
- />
- <Select
- v-model:value="item.condition"
- placeholder="请选择条件"
- class="condition-op-select"
- :options="conditionSymbols"
- />
- <template v-if="!notShowValueConditions.includes(item.condition)">
- <DatePicker
- v-if="showDatePicker(item.key)"
- v-model:value="item.value"
- show-time
- format="YYYY-MM-DD HH:mm:ss"
- value-format="YYYY-MM-DD HH:mm:ss"
- placeholder="选择日期"
- class="condition-value-input"
- />
- <Input v-else v-model:value="item.value" placeholder="请输入" allow-clear class="condition-value-input" />
- </template>
- <div v-else class="condition-value-placeholder"></div>
- <CloseOutlined class="remove-btn" @click="removeCondition(index)" />
- </div>
- </div>
- <div class="control-button-box">
- <Dropdown :trigger="['click']" placement="bottomLeft">
- <Button type="link" class="add-condition-btn">
- <PlusOutlined />
- 添加条件
- </Button>
- <template #overlay>
- <Menu class="condition-field-dropdown" @click="({ key }) => addCondition(key)">
- <MenuItem v-for="col in filterTableColumn" :key="col.prop">
- {{ col.label }}
- </MenuItem>
- </Menu>
- </template>
- </Dropdown>
- <div class="control-right">
- <Select v-model:value="conditionRun" class="condition-run-select" :options="conditionRunOptions" />
- <Button type="primary" size="small" @click="handleQuery"> 查询 </Button>
- <Button size="small" @click="handleReset"> 重置 </Button>
- </div>
- </div>
- </div>
- </template>
- <script lang="ts" setup name="CustomSearch">
- import { ref, computed, nextTick } from 'vue';
- import { Button, Select, Input, DatePicker, Dropdown, Menu, MenuItem } from 'ant-design-vue';
- import { PlusOutlined, CloseOutlined } from '@ant-design/icons-vue';
- import { useMessage } from '@jeesite/core/hooks/web/useMessage';
- interface TableCol {
- title?: string;
- label?: string;
- prop?: string;
- dataIndex?: string | readonly (string | number)[];
- [key: string]: any;
- }
- interface Condition {
- key: string;
- condition: string;
- value: string | undefined;
- }
- const props = defineProps<{
- tableColumn: TableCol[];
- }>();
- const emit = defineEmits<{
- (e: 'search', val: { conditionRun: string; conditions: Condition[] } | null): void;
- }>();
- const { createMessage } = useMessage();
- const conditionSymbols = [
- { label: '等于', value: '=' },
- { label: '不等于', value: '!=' },
- { label: '大于', value: '>' },
- { label: '大于等于', value: '>=' },
- { label: '小于', value: '<' },
- { label: '小于等于', value: '<=' },
- { label: '包含', value: 'LIKE' },
- { label: '不包含', value: 'NOT LIKE' },
- { label: '为空', value: 'IS NULL' },
- { label: '不为空', value: 'IS NOT NULL' },
- ];
- const conditionRunOptions = [
- { label: '满足所有条件', value: 'AND' },
- { label: '满足任一条件', value: 'OR' },
- ];
- const notShowValueConditions = ['IS NULL', 'IS NOT NULL', 'IS NUll', 'IS NOT NUll'];
- const listRef = ref<HTMLElement | null>(null);
- const conditions = ref<Condition[]>([]);
- const conditionRun = ref<string>('AND');
- const resolveColumnProp = (column: TableCol): string => {
- if (column.prop) return column.prop;
- if (Array.isArray(column.dataIndex)) return String(column.dataIndex[0] || '');
- return typeof column.dataIndex === 'string' ? column.dataIndex : '';
- };
- const filterTableColumn = computed(() => {
- const filterProps = ['file_name'];
- return props.tableColumn
- .filter((it) => !filterProps.includes(resolveColumnProp(it)))
- .map((it) => ({
- label: it.title ?? it.label ?? '',
- prop: resolveColumnProp(it),
- }))
- .filter((it) => it.prop && it.label);
- });
- const showDatePicker = (key: string) => {
- const col = filterTableColumn.value.find((it) => it.prop === key);
- const label = col?.label ?? '';
- const list = ['time', 'date', '时间', '日期'];
- const regex = new RegExp(`(${list.join('|')})`, 'gi');
- return regex.test(key) || (label && regex.test(label));
- };
- const addCondition = (prop: string | number) => {
- conditions.value.push({
- key: String(prop),
- condition: '=',
- value: undefined,
- });
- // 添加后自动滚动到最后一条
- nextTick(() => {
- if (listRef.value) {
- listRef.value.scrollTop = listRef.value.scrollHeight;
- }
- });
- };
- const removeCondition = (index: number) => {
- conditions.value.splice(index, 1);
- if (!conditions.value.length) {
- conditionRun.value = 'AND';
- emit('search', null);
- }
- };
- const handleQuery = () => {
- const hasEmpty = conditions.value.some(
- (it) =>
- !notShowValueConditions.includes(it.condition) &&
- (it.value === undefined || it.value === null || it.value === ''),
- );
- if (hasEmpty) {
- createMessage.warning('请填写查询条件的值');
- return;
- }
- if (!conditions.value.length) {
- emit('search', null);
- return;
- }
- emit('search', {
- conditionRun: conditionRun.value,
- conditions: conditions.value.map((it) => ({
- ...it,
- value: notShowValueConditions.includes(it.condition) ? undefined : it.value,
- condition: encodeURIComponent(it.condition),
- })),
- });
- };
- const resetConditions = (silent = false) => {
- conditions.value = [];
- conditionRun.value = 'AND';
- if (silent) return;
- emit('search', null);
- };
- const handleReset = () => {
- resetConditions(false);
- };
- const resetSearch = (options?: { silent?: boolean }) => {
- resetConditions(!!options?.silent);
- };
- defineExpose({ handleReset: resetSearch });
- </script>
- <style lang="less" scoped>
- .custom-search-container {
- width: 630px;
- box-sizing: border-box;
- padding: 4px 0;
- .render-condition-list {
- max-height: 360px;
- overflow-y: auto;
- margin-bottom: 10px;
- &::-webkit-scrollbar {
- width: 6px;
- }
- &::-webkit-scrollbar-thumb {
- background: rgba(148, 163, 184, 0.5);
- border-radius: 999px;
- }
- &::-webkit-scrollbar-track {
- background: transparent;
- }
- }
- .condition-item-row {
- display: flex;
- align-items: center;
- gap: 8px;
- margin-bottom: 8px;
- .condition-field-select {
- width: 160px;
- flex-shrink: 0;
- }
- .condition-op-select {
- width: 130px;
- flex-shrink: 0;
- }
- .condition-value-input {
- flex: 1;
- min-width: 0;
- }
- .condition-value-placeholder {
- flex: 1;
- }
- .remove-btn {
- cursor: pointer;
- font-size: 16px;
- color: #9ca3af;
- flex-shrink: 0;
- transition: color 0.15s;
- &:hover {
- color: #ef4444;
- }
- }
- }
- .control-button-box {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding-top: 4px;
- border-top: 1px solid #f0f0f0;
- .add-condition-btn {
- padding: 0;
- color: #2563eb;
- font-size: 13px;
- &:hover {
- color: #1d4ed8;
- }
- }
- .control-right {
- display: flex;
- align-items: center;
- gap: 8px;
- .condition-run-select {
- width: 140px;
- }
- }
- }
- }
- </style>
- <style lang="less">
- .condition-field-dropdown {
- max-height: 360px;
- overflow-y: auto;
- &::-webkit-scrollbar {
- width: 6px;
- }
- &::-webkit-scrollbar-thumb {
- background: rgba(148, 163, 184, 0.5);
- border-radius: 999px;
- }
- }
- </style>
|