ow
This commit is contained in:
1 parent
4b92ee5fab
commit
a2a03f5475
35 files changed
+373
-201
No files matched your search
@@ -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>
|
||||
Reference in new issue
Block a user