index.jsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  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 (config.tableRender) return config.tableRender({ row, $index, column, h })
  116. if (!['default', 'append', 'prepend', 'expand', '_handler', '$handler'].includes(key) && context.slots[key]) {
  117. return context.slots[key]({ row, $index, column })
  118. }
  119. // 特殊字段插槽 ['default', 'append', 'expand', '_handler', '$handler']
  120. if (context.slots[`${key}Slot`]) {
  121. return context.slots[`${key}Slot`]({ row, $index, column })
  122. }
  123. let propertyKey = $index
  124. if (dataIndexed) {
  125. const path = dataIndexed[$index]
  126. if (path) {
  127. propertyKey = path?.length > 1 ? getPropertyKeyByPath(path, props.treeProps) : path[0]
  128. }
  129. }
  130. return h(ColumnInput, {
  131. config,
  132. fieldKey: props.fieldKey,
  133. index: $index,
  134. model: row,
  135. key,
  136. tableRuleKey: props.ruleKey,
  137. propertyKey: propertyKey,
  138. columnKey: key,
  139. tableDependOnValues: props.dependOnValues,
  140. tableData: props.data,
  141. updateData
  142. })
  143. }
  144. }
  145. })
  146. }
  147. // 渲染table的所有数据列 注意此处为Columns
  148. const renderTableColumns = (columns = []) => {
  149. if (!isArray(columns)) {
  150. throw new Error('function renderTableColumns param columns must be array')
  151. }
  152. return columns.filter(column => !column.config.hideItem).map(column => renderTableColumn(column))
  153. }
  154. // 渲染table所有列 操作、选中、序号等
  155. const TableColumns = () => {
  156. // table字段渲染
  157. const slots = renderTableColumns(props.columns)
  158. // 序号渲染
  159. if (isNotEmpty(props.offset) && props.offset > -1 && !props.hideIndex) {
  160. const indexColumn = h(ElTableColumn, {
  161. label: '序号',
  162. fixed: props.indexFixed ? 'left' : '',
  163. width: transformWidth(isEmpty(props.rowKey) ? 55 : 75)
  164. },
  165. {
  166. default: ({ $index }) => `${$index + 1 + props.offset}`
  167. })
  168. slots.unshift(indexColumn)
  169. }
  170. // 复选框
  171. if (props.selectType === 'checkbox') {
  172. const option = { type: 'selection', width: transformWidth(45), fixed: 'left' }
  173. if (isNotEmpty(props.selectable)) { // 选择
  174. option.selectable = (row, index) => props.selectable(row || {}, index)
  175. }
  176. const selectionColumn = h(ElTableColumn, option)
  177. slots.unshift(selectionColumn)
  178. }
  179. // 单选框
  180. if (props.selectType === 'radio') {
  181. const selectionColumn = h(ElTableColumn, { width: transformWidth(45), fixed: 'left' }, {
  182. default: ({ row }) => h(ElRadio, {
  183. label: row[props.selectLabel] ?? row.id,
  184. modelValue: props.selectRadio,
  185. disabled: props.selectable && !props.selectable(row)
  186. }, { default: () => '' })
  187. })
  188. slots.unshift(selectionColumn)
  189. }
  190. // 展开
  191. if (context.slots.expand || props.expandRender) { // 支持函数
  192. const renderFn = props.expandRender || context.slots.expand
  193. const expendColumn = h(ElTableColumn, { type: 'expand', width: transformWidth(32), fixed: 'left' }, {
  194. default: ({ row, index }) => renderFn({ row, index })
  195. })
  196. slots.unshift(expendColumn)
  197. }
  198. // jsx编译时有时候会去除_handler插槽
  199. // 注意_handler即将废弃请使用$handler代替
  200. if (props.withTableHandle && (context.slots._handler || context.slots.$handler)) {
  201. const handlerSlot = context.slots._handler || context.slots.$handler
  202. const handlerColumn = h(ElTableColumn, {
  203. label: '操作',
  204. fixed: 'right',
  205. width: props.handlerWidth ? transformWidth(props.handlerWidth) : handleColumnWidthMap[_size.value]
  206. }, {
  207. default: ({ row, $index }) => h(CipTableHandler, { limit: props.handlerLimit, row }, {
  208. default: () => handlerSlot({ row, $index })
  209. })
  210. })
  211. slots.push(handlerColumn)
  212. // 内部组件必须使用cip-table-button
  213. }
  214. // el-table组件提供的默认插槽
  215. if (context.slots.default) {
  216. slots.push(context.slots.default())
  217. }
  218. // el-table组件提供的prepend插槽
  219. if (context.slots.prepend) {
  220. const prependSlots = context.slots.prepend()
  221. if (isArray(prependSlots)) {
  222. slots.unshift(...prependSlots)
  223. } else {
  224. slots.unshift(prependSlots)
  225. }
  226. }
  227. // el-table组件提供的append插槽
  228. if (context.slots.append) {
  229. const appendSlots = context.slots.append()
  230. if (isArray(appendSlots)) {
  231. slots.push(...appendSlots)
  232. } else {
  233. slots.push(appendSlots)
  234. }
  235. }
  236. // 给所有column加一个父亲
  237. if (props.tableHeaderLabel) return h(ElTableColumn, { label: props.tableHeaderLabel, align: 'center' }, { default: () => slots })
  238. return slots
  239. }
  240. // 数据为空时显示
  241. const EmptyBlock = () => {
  242. return <div class='cip-table__empty'>
  243. <EmptyStatus class='cip-table__empty__svg'/>
  244. <div class="cip-table__empty__text">暂无数据</div>
  245. </div>
  246. }
  247. // 渲染table
  248. return () => <ElTable
  249. ref={cipTableRef}
  250. size={_size.value}
  251. {...context.attrs}
  252. class={'cip-table'}
  253. data={props.data}
  254. rowKey={props.rowKey}
  255. treeProps={props.treeProps}
  256. defaultExpendAll={props.defaultExpendAll}
  257. onSortChange={onSortChange}
  258. onSelectionChange={onSelectionChange}
  259. >
  260. {{
  261. default: () => <TableColumns/>,
  262. empty: () => <EmptyBlock/>
  263. }}
  264. </ElTable>
  265. }
  266. })