123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332 |
- <template>
- <div class="xc-table_v2"
- ref="tableRef">
- <div class="header"
- ref="headerRef">
- <template v-for="(item,index) in props.columns">
- <div class="th"
- @click="headerClick(item)"
- :style="fixedStyle(item,index)">
- <template v-if="item.type">
- <component :is="typeEnum[item.type].header()"/>
- </template>
- {{ item.name }}
- </div>
- </template>
- </div>
- <div v-bind="containerProps"
- class="xc-body_v2"
- :style="{height : props.height + 'px'}">
- <div v-bind="wrapperProps"
- ref="dataRef">
- <div v-for="item in list"
- :key="props.rowKey === null ? item.index : props.rowKey"
- @contextmenu.prevent.stop="contextmenu(item.data,item.index,$event)"
- :style="rowStyle(item.data, item.index)"
- @dblclick.stop="dbRowClick(item.data, $event, item.index)"
- @click="rowClick(item.data, $event, item.index)"
- class="row">
- <template v-for="(he,heindex) in props.columns">
- <div :title="item.data[he.code]"
- class="cell"
- :style="fixedStyle(he,heindex)">
- <template v-if="he.type">
- <component :is="typeEnum[he.type].body(item)"/>
- </template>
- <template v-else-if="he.cellRenderer">
- <component :is="he.cellRenderer({data: item.data,index:item.index,cellData: item.data[he.code]})"/>
- </template>
- <template v-else>
- {{ item.data[he.code] }}
- </template>
- </div>
- </template>
- </div>
- </div>
- </div>
- </div>
- </template>
- <script setup name='XcTableV2' lang="tsx">
- import {useVirtualList} from "@vueuse/core";
- import {computed, nextTick, onMounted, ref, Ref} from 'vue'
- import {ElCheckbox} from "element-plus";
- const props = defineProps({
- data: Array,
- columns: Array,
- height: {
- type: Number,
- default: 300
- },
- rowStyle: {
- type: Function,
- default: null
- },
- rowKey: {
- type: String,
- default: null
- }
- })
- const tempData = computed(() => {
- return props.data
- })
- const emits = defineEmits(['rowClick', 'dbRowClick', 'contextmenu'])
- const {list, containerProps, wrapperProps, scrollTo} = useVirtualList(
- tempData,
- {
- itemHeight: 24,
- },
- )
- const headerClick = (item) => {
- if (item.headerFunc) {
- item.headerFunc()
- }
- }
- const rowStyle = (data, index) => {
- if (props.rowStyle === null) return
- return props.rowStyle(data, index)
- }
- let timer = null
- const rowClick = (item, event, index) => {
- if (timer) {
- clearTimeout(timer)
- }
- timer = setTimeout(() => {
- emits('rowClick', item, index, event)
- }, 300)
- }
- const dbRowClick = (item, event, index) => {
- if (timer) {
- clearTimeout(timer)
- }
- emits('dbRowClick', item, index, event)
- }
- const contextmenu = (data, index, event) => {
- emits('contextmenu', data, index, event)
- }
- const tableRef: Ref<HTMLElement | null> = ref(null)
- const dataRef: Ref<HTMLElement | null> = ref(null)
- const headerRef: Ref<HTMLElement | null> = ref(null)
- const checkAll: Ref<boolean> = ref(false)
- const isIndeterminate = computed(() => {
- if (selectedData.value.length > 0) {
- return true
- } else if (selectedData.value.length === selectedData.value.length) {
- return false
- }
- })
- const selectedData: Ref<Array<any>> = ref([])
- const typeEnum = {
- "selected": {
- header: () => {
- return (<ElCheckbox modelValue={checkAll.value}
- onclick={(e) => {
- clickSelectedAll(e)
- }}
- indeterminate={isIndeterminate.value}/>)
- },
- body: ({data}) => {
- return (<ElCheckbox
- modelValue={data.tempCheckbox}
- onclick={(e) => clickSelected(data, e)}/>)
- }
- },
- "index": {
- header: () => (<span>排序</span>),
- body: ({index}) => (
- <span>{index + 1}</span>
- )
- }
- }
- const clickSelected = (data, e: Event) => {
- if (e !== null) {
- e.preventDefault()
- e.stopImmediatePropagation();
- }
- data.tempCheckbox = !data.tempCheckbox
- if (data.tempCheckbox) {
- selectedData.value.push(data)
- } else {
- let index = selectedData.value.findIndex((item) => {
- return item === data
- })
- selectedData.value.splice(index, 1)
- }
- }
- const clickSelectedAll = (e: Event) => {
- e.preventDefault()
- e.stopPropagation()
- checkAll.value = !checkAll.value
- if (checkAll.value) {
- selectedData.value = []
- tempData.value.forEach(item => {
- item.tempCheckbox = true
- })
- } else {
- selectedData.value = tempData.value
- selectedData.value = []
- tempData.value.forEach(item => {
- item.tempCheckbox = false
- })
- }
- }
- const selectedRow = (data) => {
- clickSelected(data, null)
- }
- const getSelectedData = () => {
- return selectedData.value
- }
- const scrollToByData = (data) => {
- let temp = JSON.stringify(data)
- let index = -1
- for (let i = 0, len = tempData.value.length; i < len; i++) {
- let item = JSON.stringify(tempData.value[i])
- if (item === temp) {
- index = i
- break;
- }
- }
- if (index > 0) {
- scrollTo(index)
- }
- }
- const scrollToByKey = (key) => {
- if (props.rowKey === null) {
- throw new Error('请先选择一个 key')
- }
- let index = -1
- for (let i = 0, len = tempData.value.length; i < len; i++) {
- if (tempData.value[i][props.rowKey] == key) {
- index = i
- break;
- }
- }
- if (index > 0) {
- scrollTo(index - 2)
- }
- return index
- }
- const fixedStyle = (item, index) => {
- let str = {width: item.width + 'px'}
- let px = 0
- if (index > 0) {
- for (let i = 0; i < index; i++) {
- let he = props.columns[i]
- if (he.fixed && he.fixed === item.fixed) {
- px += he.width
- }
- }
- }
- if (item.fixed) {
- str['position'] = 'sticky!important';
- str[item.fixed] = px + 'px'
- str['z-index'] = 2
- str['background'] = '#f5f5f5'
- }
- return str
- }
- const clearSelected = () => {
- if (selectedData.value.length > 0) {
- selectedData.value.forEach(item => {
- item.tempCheckbox = false
- })
- selectedData.value = []
- }
- }
- onMounted(async () => {
- await nextTick()
- containerProps.ref.value.addEventListener('scroll', () => {
- headerRef.value.scrollLeft = containerProps.ref.value.scrollLeft
- })
- })
- defineExpose({
- selectedRow,
- getSelectedData,
- scrollToByData,
- scrollToByKey,
- scrollTo,
- clearSelected
- })
- </script>
- <style lang="scss">
- .xc-table_v2 {
- overflow-x: hidden;
- color: black;
- .header {
- height: max-content;
- display: flex;
- align-items: center;
- border-bottom: 1px solid;
- overflow-x: hidden;
- .th {
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- flex-grow: 0;
- flex-shrink: 0
- }
- }
- .xc-body_v2 {
- &::-webkit-scrollbar {
- height: 6px;
- }
- .row {
- height: 22px;
- display: flex;
- align-items: center;
- }
- .cell {
- overflow: hidden;
- border-bottom: 1px solid #fff;
- border-top: 1px solid #fff;
- display: flex;
- align-items: center;
- text-overflow: ellipsis;
- white-space: nowrap;
- height: 100%;
- flex-grow: 0;
- flex-shrink: 0;
- }
- }
- }
- </style>
|