|
|
@@ -1,6 +1,38 @@
|
|
|
<!-- 列表工具栏右侧:刷新 + 列显隐(勾选持久化由 usePersistedListColumns 负责) -->
|
|
|
<template>
|
|
|
- <div class="list-toolbar-end">
|
|
|
+ <div ref="rootEl" class="list-toolbar-end">
|
|
|
+ <el-tooltip
|
|
|
+ v-if="showExcelCopy"
|
|
|
+ :content="excelActive ? '退出 Excel 复制' : 'Excel 复制'"
|
|
|
+ placement="top"
|
|
|
+ >
|
|
|
+ <el-button
|
|
|
+ class="list-toolbar-end-btn"
|
|
|
+ :class="{ 'is-excel-active': excelActive }"
|
|
|
+ :aria-pressed="excelActive"
|
|
|
+ aria-label="Excel 复制"
|
|
|
+ @click="toggleExcelCopy"
|
|
|
+ >
|
|
|
+ <svg class="list-excel-icon" viewBox="0 0 16 16" aria-hidden="true">
|
|
|
+ <rect
|
|
|
+ x="1.5"
|
|
|
+ y="2"
|
|
|
+ width="13"
|
|
|
+ height="12"
|
|
|
+ rx="1.5"
|
|
|
+ fill="none"
|
|
|
+ stroke="currentColor"
|
|
|
+ stroke-width="1.4"
|
|
|
+ />
|
|
|
+ <path
|
|
|
+ d="M1.5 6.2h13M1.5 10h13M6.2 2v12M10.4 2v12"
|
|
|
+ stroke="currentColor"
|
|
|
+ stroke-width="1.1"
|
|
|
+ />
|
|
|
+ </svg>
|
|
|
+ </el-button>
|
|
|
+ </el-tooltip>
|
|
|
+
|
|
|
<el-tooltip content="刷新列表" placement="top">
|
|
|
<el-button
|
|
|
class="list-toolbar-end-btn"
|
|
|
@@ -61,96 +93,155 @@
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
-import { computed } from 'vue'
|
|
|
-import { Refresh, Setting } from '@element-plus/icons-vue'
|
|
|
-import type { ListColumnDef } from '@/composables/usePersistedListColumns'
|
|
|
-
|
|
|
-defineOptions({ name: 'ListToolbarEndActions' })
|
|
|
-
|
|
|
-const props = withDefaults(
|
|
|
- defineProps<{
|
|
|
- loading?: boolean
|
|
|
- columnDefs?: ListColumnDef[]
|
|
|
- visibleColumns?: string[]
|
|
|
- checkAll?: boolean
|
|
|
- isIndeterminate?: boolean
|
|
|
- showColumns?: boolean
|
|
|
- }>(),
|
|
|
- {
|
|
|
- loading: false,
|
|
|
- columnDefs: () => [],
|
|
|
- visibleColumns: () => [],
|
|
|
- checkAll: false,
|
|
|
- isIndeterminate: false,
|
|
|
- showColumns: undefined
|
|
|
+ import { computed, nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
|
|
|
+ import { Refresh, Setting } from '@element-plus/icons-vue'
|
|
|
+ import type { ListColumnDef } from '@/composables/usePersistedListColumns'
|
|
|
+
|
|
|
+ defineOptions({ name: 'ListToolbarEndActions' })
|
|
|
+
|
|
|
+ const props = withDefaults(
|
|
|
+ defineProps<{
|
|
|
+ loading?: boolean
|
|
|
+ columnDefs?: ListColumnDef[]
|
|
|
+ visibleColumns?: string[]
|
|
|
+ checkAll?: boolean
|
|
|
+ isIndeterminate?: boolean
|
|
|
+ showColumns?: boolean
|
|
|
+ showExcelCopy?: boolean
|
|
|
+ }>(),
|
|
|
+ {
|
|
|
+ loading: false,
|
|
|
+ columnDefs: () => [],
|
|
|
+ visibleColumns: () => [],
|
|
|
+ checkAll: false,
|
|
|
+ isIndeterminate: false,
|
|
|
+ showColumns: undefined,
|
|
|
+ showExcelCopy: true
|
|
|
+ }
|
|
|
+ )
|
|
|
+
|
|
|
+ const emit = defineEmits<{
|
|
|
+ refresh: []
|
|
|
+ 'update:visibleColumns': [value: string[]]
|
|
|
+ 'update:checkAll': [value: boolean]
|
|
|
+ resetColumns: []
|
|
|
+ }>()
|
|
|
+
|
|
|
+ const showColumns = computed(() => {
|
|
|
+ if (props.showColumns !== undefined) return props.showColumns
|
|
|
+ return props.columnDefs.length > 0
|
|
|
+ })
|
|
|
+
|
|
|
+ const checkAllProxy = computed({
|
|
|
+ get: () => props.checkAll,
|
|
|
+ set: (value: boolean) => emit('update:checkAll', value)
|
|
|
+ })
|
|
|
+
|
|
|
+ const handleColumnToggle = (prop: string, visible: boolean) => {
|
|
|
+ const next = new Set(props.visibleColumns)
|
|
|
+ if (visible) {
|
|
|
+ next.add(prop)
|
|
|
+ } else {
|
|
|
+ next.delete(prop)
|
|
|
+ }
|
|
|
+ const ordered = props.columnDefs
|
|
|
+ .filter((col) => col.disabled || next.has(col.prop))
|
|
|
+ .map((col) => col.prop)
|
|
|
+ emit('update:visibleColumns', ordered)
|
|
|
+ }
|
|
|
+
|
|
|
+ const handleReset = () => {
|
|
|
+ emit('resetColumns')
|
|
|
+ }
|
|
|
+
|
|
|
+ const excelActive = ref(false)
|
|
|
+ const rootEl = ref<HTMLElement | null>(null)
|
|
|
+ let panelEl: HTMLElement | null = null
|
|
|
+
|
|
|
+ function controlledTable(): HTMLElement | null {
|
|
|
+ const panel = rootEl.value?.closest('.panel')
|
|
|
+ const table = panel?.querySelector('[data-list-excel-copy="on"]')
|
|
|
+ return table instanceof HTMLElement ? table : null
|
|
|
}
|
|
|
-)
|
|
|
-
|
|
|
-const emit = defineEmits<{
|
|
|
- refresh: []
|
|
|
- 'update:visibleColumns': [value: string[]]
|
|
|
- 'update:checkAll': [value: boolean]
|
|
|
- resetColumns: []
|
|
|
-}>()
|
|
|
-
|
|
|
-const showColumns = computed(() => {
|
|
|
- if (props.showColumns !== undefined) return props.showColumns
|
|
|
- return props.columnDefs.length > 0
|
|
|
-})
|
|
|
-
|
|
|
-const checkAllProxy = computed({
|
|
|
- get: () => props.checkAll,
|
|
|
- set: (value: boolean) => emit('update:checkAll', value)
|
|
|
-})
|
|
|
-
|
|
|
-const handleColumnToggle = (prop: string, visible: boolean) => {
|
|
|
- const next = new Set(props.visibleColumns)
|
|
|
- if (visible) {
|
|
|
- next.add(prop)
|
|
|
- } else {
|
|
|
- next.delete(prop)
|
|
|
+
|
|
|
+ function toggleExcelCopy() {
|
|
|
+ controlledTable()?.dispatchEvent(new CustomEvent('sx-list-excel-toggle'))
|
|
|
+ }
|
|
|
+
|
|
|
+ function onExcelState(event: Event) {
|
|
|
+ const custom = event as CustomEvent<{ active?: boolean }>
|
|
|
+ if (event.target !== controlledTable()) return
|
|
|
+ excelActive.value = !!custom.detail?.active
|
|
|
}
|
|
|
- const ordered = props.columnDefs
|
|
|
- .filter((col) => col.disabled || next.has(col.prop))
|
|
|
- .map((col) => col.prop)
|
|
|
- emit('update:visibleColumns', ordered)
|
|
|
-}
|
|
|
-
|
|
|
-const handleReset = () => {
|
|
|
- emit('resetColumns')
|
|
|
-}
|
|
|
+
|
|
|
+ function attachExcelStateListener() {
|
|
|
+ const panel = rootEl.value?.closest('.panel')
|
|
|
+ if (!(panel instanceof HTMLElement)) return false
|
|
|
+ panel.addEventListener('sx-list-excel-state', onExcelState, true)
|
|
|
+ panelEl = panel
|
|
|
+ return true
|
|
|
+ }
|
|
|
+
|
|
|
+ onMounted(() => {
|
|
|
+ if (!attachExcelStateListener()) {
|
|
|
+ nextTick(() => {
|
|
|
+ attachExcelStateListener()
|
|
|
+ })
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ onBeforeUnmount(() => {
|
|
|
+ panelEl?.removeEventListener('sx-list-excel-state', onExcelState, true)
|
|
|
+ panelEl = null
|
|
|
+ })
|
|
|
</script>
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
-.list-toolbar-end-btn.el-button {
|
|
|
- --el-button-border-color: transparent;
|
|
|
- --el-button-hover-border-color: transparent;
|
|
|
- --el-button-active-border-color: transparent;
|
|
|
- width: 36px;
|
|
|
- min-width: 36px;
|
|
|
- height: 36px;
|
|
|
- padding: 0;
|
|
|
- border-radius: 8px;
|
|
|
- color: var(--mint-text-muted);
|
|
|
- border: none !important;
|
|
|
- box-shadow: none;
|
|
|
- background: transparent;
|
|
|
-
|
|
|
- .el-icon {
|
|
|
- font-size: 16px;
|
|
|
+ .list-toolbar-end-btn.el-button {
|
|
|
+ --el-button-border-color: transparent;
|
|
|
+ --el-button-hover-border-color: transparent;
|
|
|
+ --el-button-active-border-color: transparent;
|
|
|
+ width: 36px;
|
|
|
+ min-width: 36px;
|
|
|
+ height: 36px;
|
|
|
+ padding: 0;
|
|
|
+ border-radius: 8px;
|
|
|
+ color: var(--mint-text-muted);
|
|
|
+ border: none !important;
|
|
|
+ box-shadow: none;
|
|
|
+ background: transparent;
|
|
|
+
|
|
|
+ .el-icon {
|
|
|
+ font-size: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ &:hover,
|
|
|
+ &:focus-visible {
|
|
|
+ color: var(--mint-primary-dark);
|
|
|
+ border: none !important;
|
|
|
+ box-shadow: none;
|
|
|
+ background: var(--mint-primary-bg);
|
|
|
+ }
|
|
|
+
|
|
|
+ &:active {
|
|
|
+ border: none !important;
|
|
|
+ box-shadow: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.is-excel-active {
|
|
|
+ color: var(--mint-primary-dark);
|
|
|
+ background: var(--mint-primary-bg);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- &:hover,
|
|
|
- &:focus-visible {
|
|
|
+ .list-toolbar-end-btn.is-excel-active {
|
|
|
color: var(--mint-primary-dark);
|
|
|
- border: none !important;
|
|
|
- box-shadow: none;
|
|
|
background: var(--mint-primary-bg);
|
|
|
}
|
|
|
|
|
|
- &:active {
|
|
|
- border: none !important;
|
|
|
- box-shadow: none;
|
|
|
+ .list-excel-icon {
|
|
|
+ width: 16px;
|
|
|
+ height: 16px;
|
|
|
+ display: block;
|
|
|
}
|
|
|
-}
|
|
|
</style>
|