index.js 10.0 KB

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