From 38b161e4d52362081bfe78fb5b51fbf384db7ce2 Mon Sep 17 00:00:00 2001 From: schangxiang@126.com <schangxiang@126.com> Date: 周二, 06 5月 2025 07:22:21 +0800 Subject: [PATCH] 222 --- HIAWms/web/src/components/Table/index.vue | 680 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 680 insertions(+), 0 deletions(-) diff --git a/HIAWms/web/src/components/Table/index.vue b/HIAWms/web/src/components/Table/index.vue new file mode 100644 index 0000000..45435a0 --- /dev/null +++ b/HIAWms/web/src/components/Table/index.vue @@ -0,0 +1,680 @@ +<template> + <div + ref="informationTableRef" + :i="Language.triggerRenderData.i" + :class="{ + 'information-table': true, + 'information-table-drag': props.showDarg, + 'information-table-border': headBorder, + [dragClass ? dragClass : '']: dragClass, + }" + > + <div + :style="{ + height: props.isFooter ? 'calc(100% - 42px)' : '100%', + }" + > + <vxe-table + v-bind="$attrs" + show-header-overflow + show-overflow + width="100%" + header-row-class-name="information-table-base-header" + ref="tableRef" + min-height="45px" + :row-class-name="rowClassName" + :row-style="rowStyle" + :cell-style="cellStyle" + :empty-text="globalT(emptyText)" + :size="props.size || 'small'" + :border="border || true" + :height="currentHeight" + :max-height="currentMaxHeight" + :row-config="{ + isCurrent: true, + isHover: true, + keyField: props.id || 'id', + ...props.rowConfig, + }" + :data="dataSource" + :scroll-y="{ enabled: !!isVScroll, gt: 0 }" + :scroll-x="{ enabled: !!isVScroll, gt: 0 }" + :sort-config="{ + remote: isSort, + }" + @checkbox-all="selectChangeEvent" + @checkbox-change="selectChangeEvent" + @sort-change="onSortChange" + @current-change="clickRowChange" + > + <!-- 鑷畾涔夊簭鍙凤紝鎷栨嫿锛岄�夋嫨 --> + <CustomVxeColumn + v-bind="props" + v-model:dataSource="dataSource" + v-model:contextMenuConfig="contextMenuConfig" + :id="props.id || 'id'" + ref="vxeColumnRef" + @check="onCheck" + @change="onRadioChange" + :LanguageScopeKey="props.LanguageScopeKey" + > + <template #[name]="data" v-for="name in Object.keys(slots)"> + <slot :name="name" v-bind="data"></slot> + </template> + </CustomVxeColumn> + </vxe-table> + </div> + + <div @click="onClickFooter" v-if="isFooter" class="information-table-foot"> + <!-- <img src="@/assets/images/+.png" style="width: 12px" /> --> + <Icon icon="+" :width="12"></Icon> + </div> + <paginAtion + v-bind="props" + :params="params" + :totalCount="totalCount" + :pageSize="props.MaxResultCount || props.pageSize || 50" + @change="onChange" + @currentChange="onCurrentChange" + v-model:pageNum="pageNum" + :tableRef="tableRef" + /> + + <context-menu + v-if="contextMenu?.length > 0" + v-model:show="contextMenuConfig.show" + :options="contextMenuConfig.options" + > + <template v-for="(item, index) in contextMenu" :key="index"> + <context-menu-item + :label="item.label" + @click="onHandleMenuItem(item)" + :disabled="!!contextDisabled(item)" + :style="{ + filter: contextDisabled(item) ? 'opacity(0.4)' : 'none', + }" + > + <div + :style="{ + cursor: !contextDisabled(item) ? 'pointer' : 'not-allowed', + }" + class="table-context-menu-item-c" + > + <div style="width: 16px; margin-right: 7px"> + <Icon :height="16" class="icon-box" :icon="item.icon" /> + </div> + <div class="label-c">{{ item.label }}</div> + </div> + </context-menu-item> + </template> + </context-menu> + </div> +</template> +<script lang="ts" setup> +// @ts-nocheck +import { + computed, + nextTick, + ref, + useSlots, + onMounted, + onUnmounted, + reactive, + watch, + provide, + defineProps, + inject, + useAttrs, +} from 'vue' +import Icon from '../Icon/Icon' +import { ContextMenu, ContextMenuItem } from '@/components/vue3-context-menu' +import Sortable from 'sortablejs' +import isBoolean from 'lodash/isBoolean' +import isNil from 'lodash/isNil' +import { getListData, adjustSort } from './api' +import type { + ParamsItem, + TablePropsItemType, + contextMenuItemType, +} from './index.d' +import CustomVxeColumn from './components/custom-vxe-column.vue' +import paginAtion from './components/pagination.vue' +import { debounce, isFunction } from 'lodash' +import VxeTable from 'vxe-table/es/table/index' +import { getScopeT, Language, _t as globalT } from '@/libs/Language/Language' +const props = defineProps<TablePropsItemType>() +const LanguageScopeKey = + props.LanguageScopeKey || inject('LanguageScopeKey', '') +const _t = LanguageScopeKey ? getScopeT(LanguageScopeKey) : globalT +const attrs = useAttrs() +const emit = defineEmits([ + 'drag', + 'check', + 'sort', + 'page', + 'rowClick', + 'update:dataSource', + 'clickFooter', + 'update', + 'load', + 'beforeLoad', + 'reload', + 'update:total', + 'change', +]) + +const waitEventFns = ref<any>([]) + +const baseParams = reactive<any>({ + SkipCount: 0, + MaxResultCount: props.pageSize || 50, +}) + +const vxeColumnRef = ref() + +const rowClassName = (...arg: any) => { + const name = 'information-table-base-row' + + const className = props.rowClassName + if (className) { + if (isFunction(className)) { + return `${name} ${className(...arg) || ''}` + } + return `${name} ${className || ''}` + } else { + return `${name} able` + } +} + +const params = computed<any>(() => { + const p = props.params || {} + + Object.assign(baseParams, p) + + for (let key in baseParams) { + if (baseParams[key] === '') { + delete baseParams[key] + } + } + return baseParams +}) + +const emptyText = computed(() => { + return props.emptyText || '鏆傛棤鏁版嵁' +}) +const contextMenuConfig = ref<contextMenuItemType>({ + show: false, + current: null, + options: { + zIndex: 2000, + minWidth: 132, + x: 0, + y: 0, + }, +}) +const informationTableRef = ref(null) +const pageNum = ref(1) +const tableRef = ref() +const slots = useSlots() +const total = ref(props.total || 0) +let dropR: any = null + +provide('tableRef', tableRef) + +const totalCount = computed({ + get() { + if (!isNil(props.total)) { + return props.total + } else { + return total.value + } + }, + set(v) { + if (!isNil(props.total)) { + emit('update:total', v) + } else { + total.value = v + } + }, +}) + +const contextDisabled: any = computed(() => { + return (item: any) => { + if (typeof item.disabled === 'function') { + return item.disabled(contextMenuConfig.value.current) + } + if (item.disabled !== undefined) { + if (!isNil(item.disabled?.value)) { + return item.disabled?.value + } else { + return item.disabled + } + } + return false + } +}) + +const dragClass = computed(() => { + if (isBoolean(props.showDarg)) return false + return props.showDarg +}) + +const dataSource = computed({ + get() { + return props.dataSource + }, + set(v) { + if (v) { + emit('update:dataSource', v) + } + }, +}) + +const currentHeight = computed(() => { + if (props.height === 'auto') { + return '' + } + return props.maxHeight ? '' : '100%' +}) + +const currentMaxHeight = computed(() => { + if (props.height === 'auto') { + return '' + } + if (props.isFooter) { + if (props.maxHeight) { + const h = parseInt(props.maxHeight) - 42 + return h > 0 ? h : '100%' + } + return '100%' + } else { + return props.maxHeight ? props.maxHeight : '100%' + } +}) +/** + * 鑿滃崟 + * @param item + */ +const onHandleMenuItem = (item: any) => { + item.fn && item.fn(contextMenuConfig.value.current, pageNum.value) +} + +/** + * 鐐瑰嚮搴曢儴娣诲姞 + */ +const onClickFooter = () => { + emit('clickFooter') +} + +const onChange = () => { + onCurrentChange(Number(pageNum.value)) +} +/** + * 鍗曢�夐�変腑 + * @param row + */ +const onRadioChange = (row: any) => { + emit('change', row) +} + +const onCheck = ( + records: Record<string, any>[], + selectionMap: Record<string, boolean> +) => { + emit('check', records, selectionMap) +} + +const selectChangeEvent = (records: any[]) => { + // console.log(records, 'records') +} + +/** + * 璁剧疆閫変腑 + * @param keys + * @param checked + */ +const setSelectRow = (keys: any[], checked = true) => { + const $table = tableRef.value + if ($table) { + const fn = () => { + const rows: any[] = [] + dataSource.value.forEach((item: any) => { + if (keys.includes(item[props?.id || 'id'])) { + rows.push(item) + } + }) + vxeColumnRef.value.setCheckboxRow(rows, checked) + emit('check', rows) + } + waitEventFns.value.push(fn) + } +} +/** + * rows閫変腑 + * @param rows + * @param checked + */ +const setSelectRowByObj = (rows: any[], checked = true) => { + if (vxeColumnRef) { + const fn = () => { + vxeColumnRef.value.setCheckboxRow(rows, checked) + } + waitEventFns.value.push(fn) + } +} +/** + * 璁剧疆閫変腑 + * @param id + */ +const setRadioRowKey = (id) => { + vxeColumnRef.value.setRadioRowKey(id) +} +/** + * 璁剧疆閫変腑 + * @param row + */ +const setRadioRow = (row) => { + vxeColumnRef.value.setRadioRow(row) +} + +/** + * 璁剧疆鍏ㄩ�夐�変腑鐘舵�� + * @param checked 閫変腑鐘舵�� + */ +const setAllCheckboxRow = (checked = false) => { + const $table = tableRef.value + if ($table) { + waitEventFns.value.push(() => + vxeColumnRef.value.selectChangeAllEvent(checked) + ) + } +} + +/** + * 璁剧疆鍗曡楂樹寒 + * @param key + */ +const setCurrentRow = (key: string) => { + const $table = tableRef.value + if ($table) { + const fn = () => { + const row = dataSource.value.find((item: any) => { + return key === item[props?.id || 'id'] + }) + if (row) { + tableRef.value.setCurrentRow(row) + } + } + waitEventFns.value.push(fn) + } +} + +/** + * 娓呴櫎閫変腑 + */ +const clearSelectEvent = () => { + const $vxeColumnRef = vxeColumnRef.value + if ($vxeColumnRef) { + $vxeColumnRef.clearSelection() + } +} + +const onSortChange = (row: any) => { + const column = props.columns.find((item) => item.field === row.field) + if (column) { + if (row.order === 'asc') { + params.value[column.sortKey] = true + } else if (row.order === 'desc') { + params.value[column.sortKey] = false + } else { + delete params.value[column.sortKey] + } + getTableList() + } + + emit('sort', row) +} + +/** + * 璁剧疆鍗曢�� + * @param row + */ +const setRow = (row: any) => { + const $table = tableRef.value + if ($table) { + const fn = () => { + tableRef.value.setCurrentRow(row) + } + waitEventFns.value.push(fn) + } +} + +const clickRowChange = (tableData: Record<string, any>[]) => { + const $table = tableRef.value + if ($table) { + emit('rowClick', tableData) + } +} + +const onCurrentChange = async (current: number) => { + pageNum.value = current + + // @ts-ignore + params.value.SkipCount = (current - 1) * params.value.MaxResultCount + if (props.url) { + await getTableList() + } + emit('page', current) +} + +const sortableInit = () => { + const moveDom = tableRef.value?.$el?.querySelector( + `.body--wrapper>.vxe-table--body tbody` + ) as HTMLElement + if (moveDom && Sortable) { + dropR = new Sortable(moveDom, { + handle: '.drag-move', + chosenClass: 'sortable-chosen', + swapThreshold: 1, + draggable: '.able', + animation: 150, + onEnd: (sortableEvent: any) => { + const newIndex = sortableEvent.newIndex as number + const oldIndex = sortableEvent.oldIndex as number + const data = [...dataSource.value] + + const currRow = data.splice(oldIndex, 1)[0] + data.splice(newIndex, 0, currRow) + dataSource.value = [] + nextTick(async () => { + dataSource.value = data + if (props.sortUrlTpl) { + const sortData = { + id: currRow.id, + sort: + newIndex + 1 + (pageNum.value - 1) * baseParams.MaxResultCount, + } + await adjustSort(sortData, props.sortUrlTpl) + } + emit('drag', newIndex, oldIndex, currRow) + }) + }, + }) + } +} + +const getTableList = () => { + return new Promise(async (r: any) => { + const res = await getListData(params.value, props.url || '') + if (props.dataTransformer && isFunction(props.dataTransformer)) { + dataSource.value = + props.dataTransformer(res?.items || res) || res?.items || res + } else { + dataSource.value = res?.items || res + } + totalCount.value = res?.totalCount || 0 + r() + emit('reload') + }) +} + +const getList = (formData?: Record<string, any>) => { + return new Promise((resolve) => { + if (formData) { + Object.assign(params.value, formData) + } + nextTick(async () => { + await getTableList() + clearAll() + resolve(dataSource.value) + }) + }) +} + +/** + * 鑾峰彇鍒楄〃鎻愪氦鍙傛暟 + */ +const getParams = () => { + return params.value +} + +/** + * 鑾峰彇鍒嗛〉鍙傛暟 + */ +const getPaginationParams = () => { + return { + pageNum: pageNum.value, + pageSize: params.value.MaxResultCount, + totalCount: totalCount.value, + } +} +/** + * 闃熷垪澶勭悊浜嬩欢鍑芥暟 + */ +const handleEventFns = () => { + if (!waitEventFns.value.length || !dataSource.value.length) return + nextTick(() => { + let fn: () => void = waitEventFns.value.pop() + while (fn) { + fn() + fn = waitEventFns.value.pop() + } + }) +} +/** + * 璺宠浆 + */ +const scrollToRowLine = () => { + const t = setTimeout(() => { + tableRef.value?.scrollToRow(dataSource.value[dataSource.value.length - 1]) + clearTimeout(t) + }) +} +/** + * 璺宠浆 + */ +const skipRow = (row: any) => { + const t = setTimeout(() => { + tableRef.value?.scrollToRow(row) + tableRef.value.setCurrentRow(row) + clearTimeout(t) + }) +} + +/** + * 婊氬姩骞跺畾浣嶅埌閭d竴琛岋紝濡傛湁鍒嗛〉锛岄渶瑕佽烦杞埌鍒嗛〉 + */ +const scrollToRow = async ({ row, skip }: any) => { + if (skip) { + if (totalCount.value > dataSource.value.length) { + pageNum.value = Math.ceil(totalCount.value / params.value.MaxResultCount) + params.value.SkipCount = (pageNum.value - 1) * params.value.MaxResultCount + } + await getTableList() + skipRow(dataSource.value[dataSource.value.length - 1]) + } else { + skipRow(row) + } +} + +const clearAll = () => { + tableRef.value?.clearAll() + vxeColumnRef.value?.clearSelection() +} +/** + * 娓呴櫎缂撳瓨 + * 鎵嬪姩娓呴櫎琛ㄦ牸鎵�鏈夋潯浠讹紝杩樺師鍒板垵濮嬬姸鎬� + */ +const resetTable = () => { + tableRef.value?.clearAll() +} + +Language.useChange((lang: typeof Language) => { + if (props.url) { + getList() + } +}) +onUnmounted(() => { + dropR?.destroy() +}) + +onMounted(async () => { + emit('beforeLoad') + if (props.url) { + await getTableList() + } + + if (props.selections?.length && dataSource.value.length) { + setSelectRow(props.selections) + } + sortableInit() + // 琛ㄦ牸鏁版嵁鍔犺浇瀹屾垚鐨勫洖璋� + emit('load') +}) + +watch(dataSource, () => { + if (Array.isArray(dataSource.value)) { + tableRef.value?.reloadData(dataSource.value) + if (props.autoFirstClickRow) { + if (dataSource.value.length) { + const row = dataSource.value[0] + setCurrentRow(row[props?.id || 'id']) + emit('rowClick', { row }) + } + } + nextTick(handleEventFns) + } +}) + +watch(() => waitEventFns.value.length, debounce(handleEventFns, 100)) + +const exposeMap = { + resetTable, + clearAll, + setCurrentRow, + setSelectRow, + setAllCheckboxRow, + setRow, + clearSelectEvent, + getList, + getTableList, + getParams, + getPaginationParams, + setSelectRowByObj, + scrollToRow, + scrollToRowLine, + handleEventFns, + setRadioRowKey, + setRadioRow, +} + +defineExpose(exposeMap) + +emit('update', exposeMap) +</script> +<style lang="scss"> +@import url('./index.scss'); +</style> +<style lang="scss" scoped> +@import url('./index.module.scss'); +</style> -- Gitblit v1.9.3