feat: add excel copy toggle beside the list refresh icon

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
wuzilongandCursor committed 2026-10-19 19:46:01 +08:00
1 parent 52d94b94dd
commit bf0099c2ad
1 file changed
+169 -78
@@ -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'
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' })
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
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 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 handleReset = () => {
emit('resetColumns')
}
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
}
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
}
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;
}
&:hover,
&:focus-visible {
color: var(--mint-primary-dark);
.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);
}
}
.list-toolbar-end-btn.is-excel-active {
color: var(--mint-primary-dark);
background: var(--mint-primary-bg);
}
&:active {
border: none !important;
box-shadow: none;
.list-excel-icon {
width: 16px;
height: 16px;
display: block;
}
}
</style>