This commit is contained in:
owenchen committed 2026-08-27 15:58:44 +08:00
1 parent 4b92ee5fab
commit a2a03f5475
35 files changed
+373 -201

No files matched your search

+276 -135
View File
@@ -1,64 +1,130 @@
<template>
<div ref="searchRef" style="margin-left: 4px;padding-top: 4px; margin-right: 12px">
<searchCriteria @submit="onSubmit" @reset="onReset" @handle-change-bind-value="handleChangeBindValue">
<div
ref="searchRef"
style="margin-left: 4px; padding-top: 4px; margin-right: 12px"
>
<searchCriteria
@submit="onSubmit"
@reset="onReset"
@handle-change-bind-value="handleChangeBindValue"
>
<template #control>
<slot name="control"></slot>
</template>
</searchCriteria>
<hr />
</div>
<div v-if="!isModal && !isSM" class="setting">
<div ref="settingRef" v-if="!isModal && !isSM" class="setting">
<a-row :wrap="false">
<a-col :lg="4"></a-col>
<a-col style="display: flex; justify-content: flex-end" :lg="20" :sm="24">
<a-input flex="none" readonly value="视图" class="site-input-split" style="
<a-input
flex="none"
readonly
value="视图"
class="site-input-split"
style="
width: 80px;
border-radius: 0;
border-top-left-radius: 5pt 5pt;
border-bottom-left-radius: 5pt 5pt;
background-color: rgb(250, 250, 250);
pointer-events: none;
" />
<a-select style="min-width: 200px" v-model:value="viewOption.Value" @select="onSelectColumn">
<a-select-option v-for="o in viewOption.SelectOptions" :key="o.DisplayName" :value="o.BindValue">{{
o.DisplayName }}</a-select-option>
"
/>
<a-select
style="min-width: 200px"
v-model:value="viewOption.Value"
@select="onSelectColumn"
>
<a-select-option
v-for="o in viewOption.SelectOptions"
:key="o.DisplayName"
:value="o.BindValue"
>{{ o.DisplayName }}</a-select-option
>
</a-select>
<a-button type="dashed" :icon="h(SettingTwoTone)" @click.prevent="onViewResult"></a-button>
<a-button type="dashed" :icon="h(PrinterTwoTone)" @click.prevent="onPrint"></a-button>
<a-button
type="dashed"
:icon="h(SettingTwoTone)"
@click.prevent="onViewResult"
></a-button>
<a-button
type="dashed"
:icon="h(PrinterTwoTone)"
@click.prevent="onPrint"
></a-button>
</a-col>
</a-row>
<columnModal :show-modal="showColumnModal" @on-change-columns="onChangeColumns" @handle-close="onHandleClose"
@on-change-view-option="onChangeViewOption" />
<columnModal
:show-modal="showColumnModal"
@on-change-columns="onChangeColumns"
@handle-close="onHandleClose"
@on-change-view-option="onChangeViewOption"
/>
</div>
<a-table style="margin-left: 2px;margin-right: 6px" :row-key="(index: any) => { return index }" :columns="columns" :data-source="datas"
:row-selection="rowSelection" :pagination="pagination" @change="handleTableChange"
:scroll="{ x: scrollx, y: resultH }" :custom-row="customRow" size="small" bordered>
<a-table
style="margin-left: 2px; margin-right: 6px"
:row-key="
(index: any) => {
return index;
}
"
:columns="columns"
:data-source="datas"
:row-selection="rowSelection"
:pagination="pagination"
@change="handleTableChange"
:scroll="{ x: scrollx, y: resultH }"
:custom-row="customRow"
size="small"
bordered
>
headerCell
<template #headerCell="datas">
<slot name="headerCell" v-bind="datas"> </slot>
</template>
<template #bodyCell="datas">
<template v-if="datas.column.dataIndex === 'index'">
<span>{{ ((pagination.current as number - 1) * pagination.pageSize) + datas.index + 1 }}</span>
<span>{{
((pagination.current as number) - 1) * pagination.pageSize +
datas.index +
1
}}</span>
</template>
<slot name="bodyCell" v-bind="datas"> </slot>
</template>
</a-table>
<slot name="footer" style="padding-left: 4px;"></slot>
<slot name="footer" style="padding-left: 4px"></slot>
</template>
<script setup lang="ts">
import { reactive, useSlots, watch, inject, computed, onMounted, defineComponent, h, ref, unref, provide } from "vue";
import { usePagination, type PaginationQueryResult } from 'vue-request'
import type { TablePaginationConfig, TableProps } from 'ant-design-vue'
import {
reactive,
useSlots,
watch,
inject,
computed,
onMounted,
defineComponent,
h,
ref,
unref,
provide,
onUnmounted,
nextTick,
} from "vue";
import { usePagination, type PaginationQueryResult } from "vue-request";
import type { TablePaginationConfig, TableProps } from "ant-design-vue";
import type { ATable } from "ant-design-vue";
import searchCriteria from "@/components/basic/SearchCriteria.vue";
import { SearchFilter } from "@/data/auto/searchFilter"
import { SearchFilter } from "@/data/auto/searchFilter";
import { SearchCriteriaResultData } from "@/data/auto/searchCriteriaResultData";
import { BindOption } from "@/data/custom/bindOption";
import { SelectOption } from "@/data/auto/selectOption";
import { SettingTwoTone, PrinterTwoTone } from '@ant-design/icons-vue';
import { SettingTwoTone, PrinterTwoTone } from "@ant-design/icons-vue";
import columnModal from "./ColumnModal.vue";
import { useWindowSize } from '@vueuse/core'
import { bsInfo } from "@/utils/browser"
import { useWindowSize } from "@vueuse/core";
import { bsInfo } from "@/utils/browser";
import { on } from "events";
import { enquiryMethodKey } from "@/injections";
@@ -86,215 +152,292 @@ const props = defineProps({
},
rowSelectionType: {
type: String,
default: 'radio',
default: "radio",
},
});
const isModal = ref(props['isModal']);
const paginationJ = ref(props['pagination']);
const rowSelectionType = ref(props['rowSelectionType']);
const tableY = ref(props['scrolly']);
const isModal = ref(props["isModal"]);
const paginationJ = ref(props["pagination"]);
const rowSelectionType = ref(props["rowSelectionType"]);
const tableY = ref(props["scrolly"]);
const enquiry = inject(enquiryMethodKey);
const datas = enquiry?.getDatas()!;
const viewOption = ref(
enquiry ? enquiry.getViewOption() : {
Value: "default",
SelectOptions: [{ BindValue: "default", DisplayName: "默认" }],
Columns: [{ BindValue: "default", BindColumn: [] }],
}
enquiry
? enquiry.getViewOption()
: {
Value: "default",
SelectOptions: [{ BindValue: "default", DisplayName: "默认" }],
Columns: [{ BindValue: "default", BindColumn: [] }],
},
);
//================inject================
const isSM = ref<boolean>(false);
const { width, height } = useWindowSize()
const { width, height } = useWindowSize();
if (bsInfo.size.sm) {
isSM.value = true;
}
else {
} else {
isSM.value = false;
}
const resultH = ref('');
const resultH = ref("");
const showColumnModal = ref<boolean>(false);
const selected = ref<any[]>([])
const selected = ref<any[]>([]);
const emit = defineEmits([
"onReset",
"onSubmit",
"handleChangeBindValue",
"usePagination",
"onRowDblclick",
"onclick",
"onSelectChange",
"onViewResult",
"onPrint",
"updateColumns",
]);
const emit = defineEmits(['onReset', 'onSubmit', 'handleChangeBindValue', 'usePagination', 'onRowDblclick', 'onclick', 'onSelectChange', 'onViewResult', 'onPrint', 'updateColumns']);
const onViewResult = (() => {
const onViewResult = () => {
showColumnModal.value = true;
emit('onViewResult');
});
emit("onViewResult");
};
const onHandleClose = (() => {
const onHandleClose = () => {
showColumnModal.value = false;
};
const onPrint = () => {
emit("onPrint", viewOption.value);
};
const searchRef = ref<HTMLElement | null>();
const settingRef = ref<HTMLElement | null>();
const tableRef = ref<InstanceType<typeof ATable> | null>(null); // 表格组件ref
// 增加监听窗口宽高变化,窗口缩放自动重算
watch([width, height, isModal, tableY], () => {
void computResultTableHeight();
});
const onPrint = (() => {
emit('onPrint', viewOption.value);
});
const searchRef = ref<HTMLElement | null>()
onMounted(() => {
computResultTableHeight()
})
watch(isModal, () => {
computResultTableHeight();
});
// 仅弹窗模式保留固定偏移(弹窗无法通过DOM测量)
const HEIGHT_OFFSETS = {
modal: 560,
normalDesktop: 330,
normalMobileFallback: 420,
minTableHeight: 120, // 表格最小高度,防止calc结果负数
};
let resizeObserver: ResizeObserver | null = null;
/** 获取真实表头高度,包含headerCell渲染后的高度 */
const getTableHeaderHeight = (): number => {
const defaultHeight = 44;
if (!tableRef.value?.$el) return defaultHeight;
const root = tableRef.value.$el as HTMLElement;
const headerDom = root.querySelector(
".ant-table-header",
) as HTMLElement | null;
if (!headerDom) return defaultHeight;
if (!headerDom.offsetParent) return defaultHeight;
return headerDom.offsetHeight ?? defaultHeight;
};
const computResultTableHeight = () => {
// 安全获取搜索区域高度,默认0
const viewportH = window.innerHeight; // JS直接取视口像素,替代css 100vh
const searchHeight = searchRef.value?.offsetHeight ?? 0;
const base = `calc(100vh - ${searchHeight}px`;
let settingHeight = 0;
let HeaderAndfooterHeight = 242;
if (!isModal.value && !isSM.value && settingRef.value) {
settingHeight = settingRef.value.offsetHeight;
}
const headerH = getTableHeaderHeight();
// console.log({
// viewportH,
// searchHeight,
// settingHeight,
// HeaderAndfooterHeight,
// headerH,
// });
let finalY: number;
// 合并重复的 isModal 分支
if (isModal.value) {
resultH.value = `${base} - ${HEIGHT_OFFSETS.modal}px)`;
return;
}
// 桌面端非弹窗
if (width.value > 640) {
resultH.value = `${base} - ${HEIGHT_OFFSETS.normalDesktop}px)`;
return;
}
// 移动端非弹窗
if (tableY.value > 0) {
resultH.value = `${base} - ${tableY.value}px)`;
// 弹窗依旧保留固定偏移(弹窗内部window.innerHeight仍是浏览器窗口,这是局限)
finalY = viewportH - searchHeight - HEIGHT_OFFSETS.modal - headerH;
} else {
resultH.value = `${base} - ${HEIGHT_OFFSETS.normalMobileFallback}px)`;
if (tableY.value > 0) {
finalY =
viewportH -
searchHeight -
tableY.value -
HeaderAndfooterHeight -
headerH;
} else {
finalY =
viewportH -
searchHeight -
settingHeight -
HeaderAndfooterHeight -
headerH;
}
}
// 最小高度兜底,不能小于minTableHeight
finalY = Math.max(finalY, HEIGHT_OFFSETS.minTableHeight);
resultH.value = `${finalY}px`; // 直接输出px,不再calc
};
// 重新绑定observer,处理v‑if动态创建的setting DOM
const rebindObserver = () => {
if (!resizeObserver) return;
// 先全部取消
resizeObserver.disconnect();
if (searchRef.value) resizeObserver.observe(searchRef.value);
if (settingRef.value) resizeObserver.observe(settingRef.value);
computResultTableHeight();
};
onMounted(() => {
resizeObserver = new ResizeObserver(() => {
computResultTableHeight();
});
rebindObserver();
});
onUnmounted(() => {
if (resizeObserver) {
resizeObserver.disconnect();
resizeObserver = null;
}
});
// ✅监听v‑if条件变化,条件一变,重新绑定观测器
watch([isModal, isSM], () => {
rebindObserver();
});
watch([width, height, tableY], () => {
computResultTableHeight();
});
defineOptions({
name: "search",
});
const columns = ref(viewOption.value.Columns[0].BindColumn?.filter((x: any) => x.hidden != 'Y'));
const columns = ref(
viewOption.value.Columns[0].BindColumn?.filter((x: any) => x.hidden != "Y"),
);
//接收拖动变化
const onChangeColumns = ((cls: Column[]) => {
columns.value = cls?.filter(x => x.hidden != 'Y');
});
const onChangeColumns = (cls: Column[]) => {
columns.value = cls?.filter((x) => x.hidden != "Y");
};
const onChangeViewOption = ((vop: ViewOption) => {
const onChangeViewOption = (vop: ViewOption) => {
viewOption.value = vop;
});
};
const onSelectColumn = (() => {
columns.value = viewOption.value.Columns.filter((x: any) => x.BindValue == viewOption.value.Value)[0].BindColumn?.filter((x: any) => x.hidden != 'Y') as Column[];
});
const onSelectColumn = () => {
columns.value = viewOption.value.Columns.filter(
(x: any) => x.BindValue == viewOption.value.Value,
)[0].BindColumn?.filter((x: any) => x.hidden != "Y") as Column[];
};
const onSubmit = (async (filters: SearchFilter[]) => {
emit('onSubmit', filters);
});
const onSubmit = async (filters: SearchFilter[]) => {
emit("onSubmit", filters);
};
const handleChangeBindValue = ((bindValue: string, value: string) => {
emit('handleChangeBindValue', bindValue, value)
});
const handleChangeBindValue = (bindValue: string, value: string) => {
emit("handleChangeBindValue", bindValue, value);
};
const onReset = (async () => {
const onReset = async () => {
datas.value = [];
selected.value = [];
emit('onReset');
});
emit("onReset");
};
const customRow = (record: any) => {
return {
onDblclick: (event: any) => {
if (selected.value.includes(record)) {
selected.value = selected.value.filter(item => item !== record);
}
else {
selected.value = selected.value.filter((item) => item !== record);
} else {
selected.value.push(record);
}
emit('onRowDblclick', event, record);
emit('onSelectChange', selected.value);
emit("onRowDblclick", event, record);
emit("onSelectChange", selected.value);
},
onclick: (event: any) => {
if (unref(rowSelectionType) == 'radio') {
if (unref(rowSelectionType) == "radio") {
selected.value = [record];
}
else {
} else {
if (selected.value.includes(record)) {
selected.value = selected.value.filter(item => item !== record);
}
else {
selected.value = selected.value.filter((item) => item !== record);
} else {
selected.value.push(record);
}
}
emit('onclick', event, [record]);
emit('onSelectChange', selected.value);
}
}
}
emit("onclick", event, [record]);
emit("onSelectChange", selected.value);
},
};
};
const onSelectChange = (changableRowKeys: any[]) => {
selected.value = changableRowKeys;
emit('onSelectChange', selected.value);
emit("onSelectChange", selected.value);
};
const rowSelection = computed(() => {
return {
columnWidth: '4%',
columnWidth: "4%",
type: rowSelectionType.value,
selectedRowKeys: unref(selected),
onChange: onSelectChange,
}
};
});
const pagination = computed(() => ({
total: paginationJ.value.Total,
current: paginationJ.value.Page,
pageSize: paginationJ.value.Limit
}))
pageSize: paginationJ.value.Limit,
}));
const handleTableChange: TableProps['onChange'] = (
const handleTableChange: TableProps["onChange"] = (
pag: TablePaginationConfig,
filters: any,
sorter: any
sorter: any,
) => {
run({
pageSize: pag.pageSize,
page: pag.current,
sortField: sorter.field,
sortOrder: sorter.order,
...filters
})
}
...filters,
});
};
const queryData = async (params: PagingParams): Promise<any> => {
try {
emit('usePagination', params);
} catch (err) {
}
emit("usePagination", params);
} catch (err) {}
return datas.value;
}
};
const { run, current, pageSize } = usePagination(queryData, {
pagination: {
currentKey: 'page',
pageSizeKey: 'pageSize',
totalKey: 'totalCount'
}
})
currentKey: "page",
pageSizeKey: "pageSize",
totalKey: "totalCount",
},
});
</script>
<style lang="less">
@@ -344,6 +487,4 @@ const { run, current, pageSize } = usePagination(queryData, {
bottom: 8px;
border-top: 1px solid silver;
}
</style>