index.jsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. import { h, ref, defineComponent, computed, reactive, provide } from 'vue'
  2. import { ElTable, ElTableColumn, ElRadio, ElTooltip } from 'element-plus'
  3. import { isNotEmpty, isEmpty, isArray, getUsingConfig, setFieldValue } from '@cip/utils/util'
  4. import CipTableHandler from '../cip-table-handler'
  5. import { useCipConfig } from '../hooks/use-cip-config'
  6. import { cipTableKey } from '../hooks/use-table'
  7. import { tableProps } from './table-props'
  8. import ColumnInput from './column-input'
  9. import { EmptyStatus } from './icons-vue'
  10. import { dateColumnWidthMap, handleColumnWidthMap } from './config'
  11. import './index.less'
  12. import { analyseData, getPropertyKeyByPath, calculateCurrentWidth } from './util'
  13. export default defineComponent({
  14. name: 'CipTable',
  15. inheritAttrs: false,
  16. props: tableProps,
  17. emits: ['sort', 'update:data', 'update:selectColumns'],
  18. setup (props, context) {
  19. const cipConfig = useCipConfig()
  20. const cipTableRef = ref()
  21. const _size = computed(() => {
  22. // 考虑历史因素使用default作为默认值, 当前主要项目使用medium, 在修改表格size注意定宽项的折叠
  23. return getUsingConfig(props.size, cipConfig.table.size, 'default')
  24. })
  25. const calculateCurrentWidthFn = computed(() => {
  26. if (props.size) return (width) => width
  27. const { sizeStandard = 'default', size = 'default' } = cipConfig.table || {}
  28. return (width) => calculateCurrentWidth(size, sizeStandard, width)
  29. })
  30. const cipTable = reactive({
  31. size: _size
  32. })
  33. provide(cipTableKey, cipTable)
  34. context.expose({
  35. cipTableRef
  36. })
  37. // table 数据更新 v-model:data
  38. const updateData = (val, index) => {
  39. // 数据索引
  40. const dataIndexed = analyseData(props.data, props.treeProps)
  41. const path = dataIndexed[index]
  42. // 当存在tree是index与实际的不符合需要使用key进行唯一定位 或者遍历计数
  43. const propertyKey = getPropertyKeyByPath(path, props.treeProps)
  44. const data = props.data
  45. setFieldValue(data, propertyKey, val)
  46. // data[index] = val
  47. context.emit('update:data', data)
  48. }
  49. // 触发table的排序事件
  50. const onSortChange = ({ prop, order }) => {
  51. context.emit('sort', { prop, order })
  52. }
  53. // 触发列的选中改变事件
  54. const onSelectionChange = (val) => {
  55. context.emit('update:selectColumns', val)
  56. }
  57. // 原始的width 转换系数
  58. const transformWidth = (widthStr) => {
  59. if (typeof widthStr === 'number') return Math.ceil(calculateCurrentWidthFn.value(widthStr))
  60. if (widthStr.indexOf('px') > -1) return `${Math.ceil(calculateCurrentWidthFn.value(Number(widthStr.replace(/px$/, ''))))}px`
  61. return widthStr
  62. }
  63. // 渲染table的单个数据列 注意此处为Column
  64. const renderTableColumn = ({ key, config } = {}) => {
  65. const { children, type, formatter, ...tableColumnConfig } = config
  66. // date 类型 强行修改宽度
  67. if (!tableColumnConfig.width) {
  68. if (config.type === 'date' && config.viewType === 'datetime') {
  69. tableColumnConfig.width = dateColumnWidthMap[_size.value]
  70. }
  71. } else {
  72. tableColumnConfig.width = transformWidth(tableColumnConfig.width)
  73. }
  74. const headerSlots = ({ column, $index }) => {
  75. const result = [h('span', {}, [config.label])]
  76. // 存在说明
  77. if (config.description) {
  78. const descriptionComp = (
  79. <ElTooltip effect={config.descriptionEffect || 'light'} placement={'top'}>
  80. {{
  81. content: () => config.description,
  82. default: () => <i class={'el-icon-question'} style={'margin-left:2px'}/>
  83. }}
  84. </ElTooltip>
  85. )
  86. result.push(descriptionComp)
  87. }
  88. // 必填标记
  89. if (config.required === true && config.writable === true) {
  90. const requiredAsterisk = (<span class={['cip-danger-color']} style={{ marginRight: '4px' }}>*</span>)
  91. result.unshift(requiredAsterisk)
  92. }
  93. return result
  94. }
  95. let dataIndexed
  96. if (props.rowKey) {
  97. dataIndexed = analyseData(props.data, props.treeProps)
  98. }
  99. return h(ElTableColumn, {
  100. prop: key,
  101. align: config.type === 'number' ? 'right' : '', // 针对数字类型进行居右优化
  102. style: 'display: flex;',
  103. ...tableColumnConfig
  104. }, {
  105. header: headerSlots,
  106. default: ({ row, $index, column }) => {
  107. // if ($index === -1) return // 如果写上这个代码 children 将失效
  108. if (isArray(config.children) && config.children.length > 0) {
  109. return renderTableColumns(config.children)
  110. } else {
  111. if ($index < 0) return null
  112. // 同时开放key值插槽及key+'Slot' 的插槽
  113. // default/append/expand/_handler为table其他用途的插槽不可用于渲染column
  114. // 如果存在字段名为上述字段的,需要加Slot后缀,例: defaultSlot\appendSlot
  115. if (!['default', 'append', 'prepend', 'expand', '_handler', '$handler'].includes(key) && context.slots[key]) {
  116. return context.slots[key]({ row, $index, column })
  117. }
  118. // 特殊字段插槽 ['default', 'append', 'expand', '_handler', '$handler']
  119. if (context.slots[`${key}Slot`]) {
  120. return context.slots[`${key}Slot`]({ row, $index, column })
  121. }
  122. let propertyKey = $index
  123. if (dataIndexed) {
  124. const path = dataIndexed[$index]
  125. if (path) {
  126. propertyKey = path?.length > 1 ? getPropertyKeyByPath(path, props.treeProps) : path[0]
  127. }
  128. }
  129. return h(ColumnInput, {
  130. config,
  131. fieldKey: props.fieldKey,
  132. index: $index,
  133. model: row,
  134. key,
  135. tableRuleKey: props.ruleKey,
  136. propertyKey: propertyKey,
  137. columnKey: key,
  138. tableDependOnValues: props.dependOnValues,
  139. tableData: props.data,
  140. updateData
  141. })
  142. }
  143. }
  144. })
  145. }
  146. // 渲染table的所有数据列 注意此处为Columns
  147. const renderTableColumns = (columns = []) => {
  148. if (!isArray(columns)) {
  149. throw new Error('function renderTableColumns param columns must be array')
  150. }
  151. return columns.filter(column => !column.config.hideItem).map(column => renderTableColumn(column))
  152. }
  153. // 渲染table所有列 操作、选中、序号等
  154. const TableColumns = () => {
  155. // table字段渲染
  156. const slots = renderTableColumns(props.columns)
  157. // 序号渲染
  158. if (isNotEmpty(props.offset) && props.offset > -1 && !props.hideIndex) {
  159. const indexColumn = h(ElTableColumn, {
  160. label: '序号',
  161. fixed: props.indexFixed ? 'left' : '',
  162. width: transformWidth(isEmpty(props.rowKey) ? 55 : 75)
  163. },
  164. {
  165. default: ({ $index }) => `${$index + 1 + props.offset}`
  166. })
  167. slots.unshift(indexColumn)
  168. }
  169. // 复选框
  170. if (props.selectType === 'checkbox') {
  171. const option = { type: 'selection', width: transformWidth(45), fixed: 'left' }
  172. if (isNotEmpty(props.selectable)) { // 选择
  173. option.selectable = (row, index) => props.selectable(row || {}, index)
  174. }
  175. const selectionColumn = h(ElTableColumn, option)
  176. slots.unshift(selectionColumn)
  177. }
  178. // 单选框
  179. if (props.selectType === 'radio') {
  180. const selectionColumn = h(ElTableColumn, { width: transformWidth(45), fixed: 'left' }, {
  181. default: ({ row }) => h(ElRadio, {
  182. label: row[props.selectLabel] ?? row.id,
  183. modelValue: props.selectRadio,
  184. disabled: props.selectable && !props.selectable(row)
  185. }, { default: () => '' })
  186. })
  187. slots.unshift(selectionColumn)
  188. }
  189. // 展开
  190. if (context.slots.expand) {
  191. const expendColumn = h(ElTableColumn, { type: 'expand', width: transformWidth(32), fixed: 'left' }, {
  192. default: ({ row, index }) => context.slots.expand({ row, index })
  193. })
  194. slots.unshift(expendColumn)
  195. }
  196. // jsx编译时有时候会去除_handler插槽
  197. // 注意_handler即将废弃请使用$handler代替
  198. if (props.withTableHandle && (context.slots._handler || context.slots.$handler)) {
  199. const handlerSlot = context.slots._handler || context.slots.$handler
  200. const handlerColumn = h(ElTableColumn, {
  201. label: '操作',
  202. fixed: 'right',
  203. width: props.handlerWidth ? transformWidth(props.handlerWidth) : handleColumnWidthMap[_size.value]
  204. }, {
  205. default: ({ row, $index }) => h(CipTableHandler, { limit: props.handlerLimit, row }, {
  206. default: () => handlerSlot({ row, $index })
  207. })
  208. })
  209. slots.unshift(handlerColumn)
  210. // 内部组件必须使用cip-table-button
  211. }
  212. // el-table组件提供的默认插槽
  213. if (context.slots.default) {
  214. slots.push(context.slots.default())
  215. }
  216. // el-table组件提供的prepend插槽
  217. if (context.slots.prepend) {
  218. const prependSlots = context.slots.prepend()
  219. if (isArray(prependSlots)) {
  220. slots.unshift(...prependSlots)
  221. } else {
  222. slots.unshift(prependSlots)
  223. }
  224. }
  225. // el-table组件提供的append插槽
  226. if (context.slots.append) {
  227. const appendSlots = context.slots.append()
  228. if (isArray(appendSlots)) {
  229. slots.push(...appendSlots)
  230. } else {
  231. slots.push(appendSlots)
  232. }
  233. }
  234. // 给所有column加一个父亲
  235. if (props.tableHeaderLabel) return h(ElTableColumn, { label: props.tableHeaderLabel, align: 'center' }, { default: () => slots })
  236. return slots
  237. }
  238. // 数据为空时显示
  239. const EmptyBlock = () => {
  240. return <div class='cip-table__empty'>
  241. <EmptyStatus class='cip-table__empty__svg'/>
  242. <div class="cip-table__empty__text">暂无数据</div>
  243. </div>
  244. }
  245. // 渲染table
  246. return () => <ElTable
  247. ref={cipTableRef}
  248. size={_size.value}
  249. {...context.attrs}
  250. class={'cip-table'}
  251. data={props.data}
  252. rowKey={props.rowKey}
  253. treeProps={props.treeProps}
  254. defaultExpendAll={props.defaultExpendAll}
  255. onSortChange={onSortChange}
  256. onSelectionChange={onSelectionChange}
  257. >
  258. {{
  259. default: () => <TableColumns/>,
  260. empty: () => <EmptyBlock/>
  261. }}
  262. </ElTable>
  263. }
  264. })