index.vue 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841
  1. <script lang="ts" setup generic="T">
  2. import type { TableInstance, TableProps } from 'element-plus'
  3. import {
  4. Comment,
  5. Fragment,
  6. Text,
  7. cloneVNode,
  8. computed,
  9. nextTick,
  10. provide,
  11. ref,
  12. useAttrs,
  13. useSlots,
  14. watch
  15. } from 'vue'
  16. import { useRoute } from 'vue-router'
  17. import { TableContextKey } from './token'
  18. import type { ColumnAlign, ColumnFixed, ColumnSettingItem } from './token'
  19. import type { DefaultRow } from 'element-plus/es/components/table/src/table/defaults'
  20. import type { VNode } from 'vue'
  21. interface ColumnMeta {
  22. key: string
  23. label: string
  24. visible?: boolean
  25. action: boolean
  26. configurable: boolean
  27. fixed: ColumnFixed
  28. children: ColumnMeta[]
  29. }
  30. interface Props
  31. extends /* @vue-ignore */ Partial<
  32. Omit<TableProps<T extends DefaultRow ? T : DefaultRow>, 'data'>
  33. > {
  34. data: T[]
  35. loading: boolean
  36. customClass?: boolean
  37. showBorder?: boolean
  38. hoverHighlight?: boolean
  39. align?: ColumnAlign
  40. columnMaxWidth?: number
  41. settingsCache?: boolean
  42. settingsCacheKey?: string
  43. }
  44. const props = defineProps<Props>()
  45. const attrs = useAttrs()
  46. const slots = useSlots()
  47. const route = useRoute()
  48. const tableRef = ref<TableInstance>()
  49. const columnSettings = ref<ColumnSettingItem[]>([])
  50. const columnDefaultSettings = ref<ColumnSettingItem[]>([])
  51. const slotColumnSignature = ref('')
  52. const columnSettingsStorageKey = ref('')
  53. const isSyncingColumnSettings = ref(false)
  54. const columnSettingsStoragePrefix = 'zm-table:column-settings:'
  55. const defaultOptions: Partial<Props> = {
  56. size: 'default',
  57. stripe: true,
  58. border: true,
  59. highlightCurrentRow: true,
  60. showOverflowTooltip: true,
  61. scrollbarAlwaysOn: true,
  62. showBorder: false,
  63. hoverHighlight: true,
  64. customClass: false,
  65. tooltipOptions: {
  66. popperClass: 'max-w-120'
  67. }
  68. }
  69. const bindProps = computed(() => {
  70. const {
  71. data,
  72. customClass: _customClass,
  73. showBorder: _showBorder,
  74. hoverHighlight: _hoverHighlight,
  75. align: _align,
  76. columnMaxWidth,
  77. settingsCache: _settingsCache,
  78. settingsCacheKey: _settingsCacheKey,
  79. ...otherProps
  80. } = props
  81. return {
  82. ...defaultOptions,
  83. ...attrs,
  84. ...otherProps,
  85. data: data || []
  86. }
  87. })
  88. const safeData = computed(() => props.data || [])
  89. const safeLoading = computed(() => props.loading)
  90. const safeColumnAlign = computed<ColumnAlign>(() => {
  91. return props.align === 'left' || props.align === 'right' || props.align === 'center'
  92. ? props.align
  93. : 'center'
  94. })
  95. const safeColumnMaxWidth = computed(() => {
  96. const maxWidth = Number(props.columnMaxWidth)
  97. return Number.isFinite(maxWidth) && maxWidth > 0 ? maxWidth : 360
  98. })
  99. const isColumnSettingsCacheEnabled = computed(() => props.settingsCache !== false)
  100. const forwardedSlots = computed(() => {
  101. const { default: _default, ...restSlots } = slots
  102. return restSlots
  103. })
  104. const getPropValue = (props: Record<string, any> | null | undefined, ...keys: string[]) => {
  105. if (!props) return undefined
  106. for (const key of keys) {
  107. if (props[key] !== undefined) return props[key]
  108. }
  109. return undefined
  110. }
  111. const toBoolean = (value: unknown) => value === true || value === ''
  112. const normalizeFixed = (fixed: unknown): ColumnFixed => {
  113. if (fixed === true || fixed === 'left') return 'left'
  114. if (fixed === 'right') return 'right'
  115. return false
  116. }
  117. const isColumnNode = (node: VNode) => {
  118. if (!node.type && typeof node.type !== 'object') return false
  119. const nodeType = node.type as { __name?: string }
  120. return nodeType.__name === 'ZmTableColumn'
  121. }
  122. const canResolveColumnChildren = (node: VNode) => {
  123. const nodeProps = node.props
  124. return toBoolean(getPropValue(nodeProps, 'isParent', 'is-parent'))
  125. }
  126. const getColumnKey = (node: VNode, index: number, parentKey?: string) => {
  127. const nodeProps = node.props
  128. const key = getPropValue(nodeProps, 'columnKey', 'column-key')
  129. const prop = getPropValue(nodeProps, 'prop')
  130. const type = getPropValue(nodeProps, 'type')
  131. const label = getPropValue(nodeProps, 'label')
  132. const fallbackKey = parentKey ? `${parentKey}.column-${index}` : `column-${index}`
  133. return String(key ?? prop ?? type ?? label ?? fallbackKey)
  134. }
  135. const getColumnLabel = (node: VNode, index: number, parentKey?: string) => {
  136. const label = getPropValue(node.props, 'label')
  137. return String(label ?? getColumnKey(node, index, parentKey))
  138. }
  139. const getColumnChildren = (node: VNode) => {
  140. if (!canResolveColumnChildren(node)) return []
  141. const children = node.children
  142. if (children && typeof children === 'object' && 'default' in children) {
  143. const defaultSlot = (children as { default?: unknown }).default
  144. if (typeof defaultSlot !== 'function') return []
  145. try {
  146. const slotNodes = defaultSlot()
  147. const normalizedNodes = Array.isArray(slotNodes) ? slotNodes : slotNodes ? [slotNodes] : []
  148. return flattenSlotNodes(normalizedNodes as VNode[]).filter(isColumnNode)
  149. } catch {
  150. return []
  151. }
  152. }
  153. return []
  154. }
  155. const getColumnMeta = (node: VNode, index: number, parentKey?: string): ColumnMeta => {
  156. const key = getColumnKey(node, index, parentKey)
  157. const action = toBoolean(getPropValue(node.props, 'action'))
  158. const hideInColumnSettings = toBoolean(
  159. getPropValue(node.props, 'hideInColumnSettings', 'hide-in-column-settings')
  160. )
  161. return {
  162. key,
  163. label: getColumnLabel(node, index, parentKey),
  164. visible: getPropValue(node.props, 'visible'),
  165. action,
  166. configurable: !hideInColumnSettings,
  167. fixed: normalizeFixed(getPropValue(node.props, 'fixed')),
  168. children: getColumnChildren(node)
  169. .map((child, childIndex) => getColumnMeta(child, childIndex, key))
  170. .filter((item) => item.configurable)
  171. }
  172. }
  173. const flattenSlotNodes = (nodes: Array<VNode | null | undefined>): VNode[] => {
  174. return nodes.flatMap((node) => {
  175. if (!node) return []
  176. if (node.type === Fragment && Array.isArray(node.children)) {
  177. return flattenSlotNodes(node.children as VNode[])
  178. }
  179. if (node.type === Comment || node.type === Text) return []
  180. return [node]
  181. })
  182. }
  183. const syncColumnSettings = (nodes: VNode[]) => {
  184. const metas = nodes
  185. .map((node, index) => getColumnMeta(node, index))
  186. .filter((item) => item.configurable)
  187. const signature = JSON.stringify(metas)
  188. if (signature === slotColumnSignature.value) return
  189. slotColumnSignature.value = signature
  190. nextTick(() => {
  191. const defaults = metas.map(createDefaultColumnSetting)
  192. const storageKey = resolveColumnSettingsStorageKey(defaults)
  193. const cachedSettings = loadColumnSettings(storageKey)
  194. columnDefaultSettings.value = defaults
  195. columnSettingsStorageKey.value = storageKey
  196. setColumnSettings(mergeColumnSettings(defaults, cachedSettings || columnSettings.value))
  197. })
  198. }
  199. const createDefaultColumnSetting = (meta: ColumnMeta): ColumnSettingItem => ({
  200. key: meta.key,
  201. label: meta.label,
  202. visible: true,
  203. fixed: meta.fixed,
  204. children: meta.children.length ? meta.children.map(createDefaultColumnSetting) : undefined
  205. })
  206. const mergeColumnSettings = (
  207. defaults: ColumnSettingItem[],
  208. current: ColumnSettingItem[]
  209. ): ColumnSettingItem[] => {
  210. const defaultMap = new Map(defaults.map((item) => [item.key, item]))
  211. const currentMap = new Map(current.map((item) => [item.key, item]))
  212. const existingItems = current
  213. .filter((item) => defaultMap.has(item.key))
  214. .map((item) => {
  215. const defaultItem = defaultMap.get(item.key)!
  216. return {
  217. ...defaultItem,
  218. visible: item.visible,
  219. fixed: item.fixed,
  220. children: defaultItem.children
  221. ? mergeColumnSettings(defaultItem.children, item.children || [])
  222. : undefined
  223. }
  224. })
  225. const addedItems = defaults.filter((item) => !currentMap.has(item.key))
  226. return [...existingItems, ...addedItems]
  227. }
  228. const mapColumnSettings = (
  229. items: ColumnSettingItem[],
  230. mapper: (item: ColumnSettingItem) => ColumnSettingItem
  231. ): ColumnSettingItem[] => {
  232. return items.map((item) => {
  233. const mappedItem = mapper(item)
  234. return {
  235. ...mappedItem,
  236. children: mappedItem.children ? mapColumnSettings(mappedItem.children, mapper) : undefined
  237. }
  238. })
  239. }
  240. const updateColumnVisible = (key: string, visible: boolean) => {
  241. columnSettings.value = mapColumnSettings(columnSettings.value, (item) =>
  242. item.key === key ? { ...item, visible } : item
  243. )
  244. }
  245. const updateColumnFixed = (key: string, fixed: ColumnFixed) => {
  246. columnSettings.value = mapColumnSettings(columnSettings.value, (item) =>
  247. item.key === key ? { ...item, fixed } : item
  248. )
  249. }
  250. const sortSettingsByKeys = (items: ColumnSettingItem[], keys: string[]) => {
  251. const orderMap = new Map(keys.map((key, index) => [key, index]))
  252. return [...items].sort((a, b) => {
  253. const orderA = orderMap.get(a.key) ?? Number.MAX_SAFE_INTEGER
  254. const orderB = orderMap.get(b.key) ?? Number.MAX_SAFE_INTEGER
  255. return orderA - orderB
  256. })
  257. }
  258. const updateColumnOrder = (keys: string[], parentKey?: string) => {
  259. if (!parentKey) {
  260. columnSettings.value = sortSettingsByKeys(columnSettings.value, keys)
  261. return
  262. }
  263. columnSettings.value = mapColumnSettings(columnSettings.value, (item) => {
  264. if (item.key !== parentKey) return item
  265. return {
  266. ...item,
  267. children: item.children ? sortSettingsByKeys(item.children, keys) : item.children
  268. }
  269. })
  270. }
  271. const cloneColumnSettings = (items: ColumnSettingItem[]): ColumnSettingItem[] => {
  272. return items.map((item) => ({
  273. ...item,
  274. children: item.children ? cloneColumnSettings(item.children) : undefined
  275. }))
  276. }
  277. const resetColumnSettings = () => {
  278. setColumnSettings(cloneColumnSettings(columnDefaultSettings.value))
  279. }
  280. const flattenColumnSettingKeys = (items: ColumnSettingItem[]): string[] => {
  281. return items.flatMap((item) => [
  282. item.key,
  283. ...(item.children ? flattenColumnSettingKeys(item.children) : [])
  284. ])
  285. }
  286. const encodeStorageKeyPart = (value: string) => {
  287. try {
  288. return encodeURIComponent(value)
  289. } catch {
  290. return value
  291. }
  292. }
  293. const resolveColumnSettingsStorageKey = (defaults: ColumnSettingItem[]) => {
  294. if (!isColumnSettingsCacheEnabled.value) return ''
  295. const userKey = props.settingsCacheKey?.trim()
  296. if (userKey) return `${columnSettingsStoragePrefix}${encodeStorageKeyPart(userKey)}`
  297. const routeKey = String(route.name || route.path || 'global')
  298. const columnKey = flattenColumnSettingKeys(defaults).sort().join('|')
  299. return `${columnSettingsStoragePrefix}${encodeStorageKeyPart(`${routeKey}:${columnKey}`)}`
  300. }
  301. const isColumnSettingItem = (item: unknown): item is ColumnSettingItem => {
  302. if (!item || typeof item !== 'object') return false
  303. const setting = item as ColumnSettingItem
  304. const validFixed =
  305. setting.fixed === false || setting.fixed === 'left' || setting.fixed === 'right'
  306. return typeof setting.key === 'string' && typeof setting.visible === 'boolean' && validFixed
  307. }
  308. const normalizeCachedColumnSettings = (items: unknown): ColumnSettingItem[] => {
  309. if (!Array.isArray(items)) return []
  310. return items.filter(isColumnSettingItem).map((item) => ({
  311. key: item.key,
  312. label: typeof item.label === 'string' ? item.label : item.key,
  313. visible: item.visible,
  314. fixed: item.fixed,
  315. children: item.children ? normalizeCachedColumnSettings(item.children) : undefined
  316. }))
  317. }
  318. const loadColumnSettings = (storageKey: string) => {
  319. if (!storageKey || typeof window === 'undefined') return null
  320. try {
  321. const rawValue = window.localStorage.getItem(storageKey)
  322. if (!rawValue) return null
  323. const cachedValue = JSON.parse(rawValue)
  324. const settings = normalizeCachedColumnSettings(cachedValue?.settings)
  325. return settings.length ? settings : null
  326. } catch {
  327. return null
  328. }
  329. }
  330. const saveColumnSettings = (storageKey: string, settings: ColumnSettingItem[]) => {
  331. if (!storageKey || typeof window === 'undefined') return
  332. try {
  333. window.localStorage.setItem(
  334. storageKey,
  335. JSON.stringify({
  336. version: 1,
  337. settings
  338. })
  339. )
  340. } catch {
  341. // localStorage 可能被浏览器策略禁用,失败时不影响表格正常使用。
  342. }
  343. }
  344. const setColumnSettings = (settings: ColumnSettingItem[]) => {
  345. isSyncingColumnSettings.value = true
  346. columnSettings.value = settings
  347. nextTick(() => {
  348. isSyncingColumnSettings.value = false
  349. saveColumnSettings(columnSettingsStorageKey.value, columnSettings.value)
  350. })
  351. }
  352. watch(
  353. columnSettings,
  354. (settings) => {
  355. if (isSyncingColumnSettings.value) return
  356. saveColumnSettings(columnSettingsStorageKey.value, settings)
  357. },
  358. { deep: true }
  359. )
  360. watch(
  361. [() => props.settingsCache, () => props.settingsCacheKey, () => route.name, () => route.path],
  362. () => {
  363. slotColumnSignature.value = ''
  364. }
  365. )
  366. const applyColumnSetting = (node: VNode, setting: ColumnSettingItem) => {
  367. const clonedNode = cloneVNode(
  368. node,
  369. {
  370. columnKey: setting.key,
  371. fixed: setting.fixed || undefined,
  372. key: `${setting.key}-${setting.visible}-${setting.fixed || 'none'}`
  373. },
  374. true
  375. )
  376. const childNodes = getColumnChildren(node)
  377. if (!childNodes.length || !setting.children?.length) return clonedNode
  378. const originalChildren =
  379. clonedNode.children && typeof clonedNode.children === 'object' ? clonedNode.children : {}
  380. return {
  381. ...clonedNode,
  382. children: {
  383. ...(originalChildren as Record<string, unknown>),
  384. default: () => renderColumnNodes(childNodes, setting.children || [], setting.key)
  385. }
  386. } as VNode
  387. }
  388. const getSettingVisible = (meta: ColumnMeta, setting?: ColumnSettingItem) => {
  389. return meta.visible ?? setting?.visible ?? true
  390. }
  391. const isColumnVisible = (meta: ColumnMeta, setting?: ColumnSettingItem) => {
  392. if (!getSettingVisible(meta, setting)) return false
  393. if (!meta.children.length) return true
  394. const childrenSettings = setting?.children || []
  395. const childSettingMap = new Map(childrenSettings.map((item) => [item.key, item]))
  396. return meta.children.some((child) => isColumnVisible(child, childSettingMap.get(child.key)))
  397. }
  398. const renderColumnNodes = (nodes: VNode[], settings: ColumnSettingItem[], parentKey?: string) => {
  399. const settingMap = new Map(settings.map((item) => [item.key, item]))
  400. const orderMap = new Map(settings.map((item, index) => [item.key, index]))
  401. const sortedConfigurableNodes = nodes
  402. .map((node, index) => ({ node, meta: getColumnMeta(node, index, parentKey) }))
  403. .filter(({ meta }) => {
  404. const setting = settingMap.get(meta.key)
  405. return meta.configurable && setting && isColumnVisible(meta, setting)
  406. })
  407. .sort((a, b) => {
  408. const orderA = orderMap.get(a.meta.key) ?? Number.MAX_SAFE_INTEGER
  409. const orderB = orderMap.get(b.meta.key) ?? Number.MAX_SAFE_INTEGER
  410. return orderA - orderB
  411. })
  412. return nodes.flatMap((node, index) => {
  413. const meta = getColumnMeta(node, index, parentKey)
  414. const setting = settingMap.get(meta.key)
  415. if (!isColumnVisible(meta, setting)) return []
  416. if (!meta.configurable) return [node]
  417. const nextColumn = sortedConfigurableNodes.shift()
  418. if (!nextColumn) return []
  419. const nextColumnSetting = settingMap.get(nextColumn.meta.key)
  420. return nextColumnSetting
  421. ? [applyColumnSetting(nextColumn.node, nextColumnSetting)]
  422. : [nextColumn.node]
  423. })
  424. }
  425. const renderDefaultSlot = () => {
  426. const nodes = flattenSlotNodes(slots.default?.() || [])
  427. syncColumnSettings(nodes)
  428. return renderColumnNodes(nodes, columnSettings.value)
  429. }
  430. const TableDefaultSlot = () => renderDefaultSlot()
  431. provide(TableContextKey, {
  432. data: safeData,
  433. loading: safeLoading,
  434. columnAlign: safeColumnAlign,
  435. columnMaxWidth: safeColumnMaxWidth,
  436. columnSettings,
  437. updateColumnVisible,
  438. updateColumnFixed,
  439. updateColumnOrder,
  440. resetColumnSettings
  441. })
  442. defineExpose({
  443. elTableRef: tableRef
  444. })
  445. </script>
  446. <template>
  447. <el-table
  448. ref="tableRef"
  449. v-loading="loading"
  450. :class="{
  451. 'zm-table': !customClass,
  452. 'show-border': showBorder,
  453. 'is-hover-highlight-disabled': hoverHighlight === false
  454. }"
  455. v-bind="bindProps"
  456. :data="data">
  457. <template v-for="(_, name) in forwardedSlots" #[name]="slotData">
  458. <slot :name="name" v-bind="slotData || {}"></slot>
  459. </template>
  460. <TableDefaultSlot />
  461. </el-table>
  462. </template>
  463. <style lang="scss">
  464. .zm-table {
  465. --zm-table-font-family: inherit;
  466. --zm-table-font-size: 12px;
  467. --zm-table-text-color: #40546d;
  468. --zm-table-strong-text-color: #24364d;
  469. --zm-table-row-font-weight: 500;
  470. --zm-table-bg: var(--el-bg-color);
  471. --zm-table-border-color: #e7edf4;
  472. --zm-table-radius: 10px;
  473. --zm-table-header-bg: var(--el-fill-color-extra-light, #f7f9fc);
  474. --zm-table-header-text-color: var(--el-text-color-secondary, #6b7f99);
  475. --zm-table-header-border-color: var(--zm-table-border-color);
  476. --zm-table-header-cell-height: 36px;
  477. --zm-table-header-group-cell-height: 42px;
  478. --zm-table-header-font-size: var(--zm-table-font-size);
  479. --zm-table-header-font-weight: 600;
  480. --zm-table-header-line-height: 16px;
  481. --zm-table-header-icon-btn-size: 18px;
  482. --zm-table-header-icon-btn-color: #8aa0b8;
  483. --zm-table-header-icon-btn-radius: 4px;
  484. --zm-table-header-icon-btn-hover-color: var(--el-color-primary);
  485. --zm-table-header-icon-btn-hover-bg: var(--el-color-primary-light-9);
  486. --zm-table-header-icon-btn-active-color: var(--zm-table-header-icon-btn-hover-color);
  487. --zm-table-header-icon-btn-active-bg: var(--zm-table-header-icon-btn-hover-bg);
  488. --zm-table-header-icon-size: 16px;
  489. --zm-table-row-border-color: #edf2f7;
  490. --zm-table-stripe-bg: #fcfdff;
  491. --zm-table-hover-bg: #f5f9ff;
  492. --zm-table-current-bg: #eef6ff;
  493. --zm-table-summary-bg: #f7f9fc;
  494. --zm-table-summary-text-color: var(--zm-table-strong-text-color);
  495. --zm-table-summary-font-weight: 600;
  496. --zm-table-summary-border-color: var(--zm-table-border-color);
  497. --zm-table-cell-height: 38px;
  498. --zm-table-cell-padding-x: 8px;
  499. --zm-table-cell-first-padding-left: 0px;
  500. --zm-table-cell-last-padding-right: 0px;
  501. --zm-table-cell-line-height: 18px;
  502. --zm-table-empty-min-height: 148px;
  503. --zm-table-empty-bg: var(--zm-table-bg);
  504. --zm-table-empty-text-font-size: var(--zm-table-font-size);
  505. --zm-table-empty-text-color: var(--el-text-color-secondary);
  506. --zm-table-scrollbar-size: 7px;
  507. --zm-table-scrollbar-thumb-bg: #b8c5d6;
  508. width: 100%;
  509. overflow: hidden;
  510. font-family: var(--zm-table-font-family);
  511. font-size: var(--zm-table-font-size);
  512. color: var(--zm-table-text-color);
  513. background: var(--zm-table-bg);
  514. border: 1px solid var(--zm-table-border-color);
  515. border-radius: var(--zm-table-radius);
  516. box-shadow: none;
  517. &::before,
  518. &::after {
  519. display: none;
  520. }
  521. .el-table__inner-wrapper {
  522. &::before,
  523. &::after {
  524. display: none;
  525. }
  526. }
  527. .el-table__border-left-patch {
  528. display: none;
  529. }
  530. .el-table__inner-wrapper,
  531. .el-table__header-wrapper,
  532. .el-table__body-wrapper,
  533. .el-scrollbar__wrap {
  534. background: transparent;
  535. }
  536. .el-table__inner-wrapper {
  537. border-radius: var(--zm-table-radius);
  538. }
  539. .el-table__cell {
  540. height: var(--zm-table-cell-height);
  541. padding: 0;
  542. color: var(--zm-table-text-color);
  543. background: var(--zm-table-bg);
  544. border-right: 1px solid var(--zm-table-row-border-color) !important;
  545. border-bottom: 1px solid var(--zm-table-row-border-color) !important;
  546. transition:
  547. background-color 0.16s ease,
  548. color 0.16s ease;
  549. &:last-child {
  550. border-right: none !important;
  551. }
  552. }
  553. .cell {
  554. padding-right: var(--zm-table-cell-padding-x);
  555. padding-left: var(--zm-table-cell-padding-x);
  556. line-height: var(--zm-table-cell-line-height);
  557. }
  558. .el-table__header {
  559. color: var(--zm-table-header-text-color);
  560. .el-table__cell {
  561. height: var(--zm-table-header-cell-height);
  562. font-size: var(--zm-table-header-font-size);
  563. font-weight: var(--zm-table-header-font-weight);
  564. color: var(--zm-table-header-text-color);
  565. background: var(--zm-table-header-bg) !important;
  566. border-right: 1px solid var(--zm-table-header-border-color) !important;
  567. border-bottom: 1px solid var(--zm-table-header-border-color) !important;
  568. .cell {
  569. display: flex;
  570. min-height: 100%;
  571. align-items: center;
  572. justify-content: center;
  573. padding-top: 0;
  574. padding-bottom: 0;
  575. }
  576. &:last-child {
  577. .cell {
  578. border-right: none;
  579. }
  580. }
  581. }
  582. tr:first-child {
  583. .el-table__cell {
  584. &:first-child {
  585. border-top-left-radius: var(--zm-table-radius);
  586. }
  587. &:last-child {
  588. border-top-right-radius: var(--zm-table-radius);
  589. }
  590. }
  591. }
  592. tr:not(:last-child) {
  593. .el-table__cell {
  594. height: var(--zm-table-header-group-cell-height);
  595. border-bottom-color: var(--zm-table-header-border-color) !important;
  596. }
  597. }
  598. }
  599. .el-table__body {
  600. tr.el-table__row--striped {
  601. .el-table__cell {
  602. background: var(--zm-table-stripe-bg);
  603. }
  604. }
  605. tr.current-row {
  606. .el-table__cell {
  607. // color: var(--el-color-primary);
  608. background: var(--zm-table-current-bg) !important;
  609. }
  610. }
  611. }
  612. &:not(.is-hover-highlight-disabled) {
  613. .el-table__body {
  614. tr:hover,
  615. tr.hover-row {
  616. .el-table__cell {
  617. background: var(--zm-table-hover-bg) !important;
  618. }
  619. }
  620. }
  621. }
  622. .el-table__row {
  623. .el-table__cell {
  624. font-weight: var(--zm-table-row-font-weight);
  625. color: var(--zm-table-strong-text-color);
  626. &:first-child {
  627. // .cell {
  628. // padding-left: var(--zm-table-cell-first-padding-left);
  629. // }
  630. }
  631. &:last-child {
  632. // .cell {
  633. // padding-right: var(--zm-table-cell-last-padding-right);
  634. // }
  635. }
  636. }
  637. }
  638. .el-table__empty-block {
  639. width: 100% !important;
  640. min-width: 100%;
  641. min-height: var(--zm-table-empty-min-height);
  642. background: var(--zm-table-empty-bg);
  643. }
  644. .el-table__empty-text {
  645. font-size: var(--zm-table-empty-text-font-size);
  646. color: var(--zm-table-empty-text-color);
  647. }
  648. .el-table__footer-wrapper {
  649. background: var(--zm-table-summary-bg);
  650. border-top: 1px solid var(--zm-table-summary-border-color);
  651. }
  652. .el-table__footer {
  653. color: var(--zm-table-summary-text-color);
  654. .el-table__cell {
  655. height: var(--zm-table-cell-height);
  656. font-weight: var(--zm-table-summary-font-weight);
  657. color: var(--zm-table-summary-text-color);
  658. background: var(--zm-table-summary-bg) !important;
  659. border-right: 1px solid var(--zm-table-row-border-color) !important;
  660. border-bottom: none !important;
  661. .cell {
  662. display: flex;
  663. min-height: 100%;
  664. align-items: center;
  665. justify-content: center;
  666. }
  667. &:last-child {
  668. border-right: none !important;
  669. }
  670. }
  671. tr:last-child {
  672. .el-table__cell {
  673. &:first-child {
  674. border-bottom-left-radius: var(--zm-table-radius);
  675. }
  676. &:last-child {
  677. border-bottom-right-radius: var(--zm-table-radius);
  678. }
  679. }
  680. }
  681. .el-table__cell.el-table-fixed-column--left.is-last-column,
  682. .el-table__cell.el-table-fixed-column--right.is-first-column {
  683. box-shadow: none;
  684. }
  685. }
  686. .el-table__cell.el-table-fixed-column--left,
  687. .el-table__cell.el-table-fixed-column--right {
  688. background: inherit;
  689. }
  690. .el-table__cell.el-table-fixed-column--left.is-last-column {
  691. box-shadow: 6px 0 12px -10px rgb(15 23 42 / 22%);
  692. }
  693. .el-table__cell.el-table-fixed-column--right.is-first-column {
  694. box-shadow: -6px 0 12px -10px rgb(15 23 42 / 22%);
  695. }
  696. .el-table__fixed-right-patch {
  697. background: var(--zm-table-header-bg);
  698. border-bottom: 1px solid var(--zm-table-header-border-color);
  699. }
  700. .el-scrollbar__bar {
  701. &.is-horizontal {
  702. height: var(--zm-table-scrollbar-size);
  703. }
  704. &.is-vertical {
  705. width: var(--zm-table-scrollbar-size);
  706. }
  707. }
  708. .el-scrollbar__thumb {
  709. background: var(--zm-table-scrollbar-thumb-bg);
  710. border-radius: 999px;
  711. opacity: 0.55;
  712. &:hover {
  713. opacity: 0.85;
  714. }
  715. }
  716. }
  717. .zm-table:not(.show-border) {
  718. .el-table__header {
  719. .el-table__cell {
  720. border-right-color: var(--zm-table-header-border-color) !important;
  721. .cell {
  722. border-right: none;
  723. }
  724. &:last-child {
  725. .cell {
  726. border-right: none;
  727. }
  728. }
  729. }
  730. }
  731. }
  732. </style>