mirror of
https://github.com/element-plus/element-plus.git
synced 2026-03-13 07:51:17 +08:00
feat(descriptions): add new feature for customized style (#2358)
* feat(descriptions): add new feature for customized style add props: width, min-width, align, label-align, class-name, label-class-name re #1976 #2353 * feat: update docs * feat: update docs * feat: update * feat: separate type
This commit is contained in:
@@ -37,6 +37,50 @@ describe('Descriptions.vue', () => {
|
||||
expect(wrapper.find('table').classes()).toContain('is-bordered')
|
||||
})
|
||||
|
||||
test('should render align props', () => {
|
||||
const wrapper = _mount(`
|
||||
<el-descriptions border>
|
||||
<el-descriptions-item v-for="item in 3" :label="item" align="right" label-align="center">{{ item }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
`)
|
||||
|
||||
expect(wrapper.find('.el-descriptions__label').classes()).toContain('is-center')
|
||||
expect(wrapper.find('.el-descriptions__content').classes()).toContain('is-right')
|
||||
})
|
||||
|
||||
test('should render width props', () => {
|
||||
const wrapper = _mount(`
|
||||
<el-descriptions border>
|
||||
<el-descriptions-item v-for="item in 3" :label="item" width="50px" min-width="60px">{{ item }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
`)
|
||||
|
||||
expect(wrapper.find('.el-descriptions__label').attributes('style')).toContain('width: 50px; min-width: 60px;')
|
||||
expect(wrapper.find('.el-descriptions__content').attributes('style')).toContain('width: 50px; min-width: 60px;')
|
||||
})
|
||||
|
||||
test('should render class props', () => {
|
||||
const wrapper = _mount(`
|
||||
<el-descriptions border>
|
||||
<el-descriptions-item v-for="item in 3" :label="item" class-name="class-name" label-class-name="label-class-name">{{ item }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
`)
|
||||
|
||||
expect(wrapper.find('.el-descriptions__label').classes()).toContain('label-class-name')
|
||||
expect(wrapper.find('.el-descriptions__content').classes()).toContain('class-name')
|
||||
})
|
||||
|
||||
test('should render width props', () => {
|
||||
const wrapper = _mount(`
|
||||
<el-descriptions border>
|
||||
<el-descriptions-item v-for="item in 3" :label="item" width="50px" min-width="60px">{{ item }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
`)
|
||||
|
||||
expect(wrapper.find('.el-descriptions__label').attributes('style')).toContain('width: 50px; min-width: 60px;')
|
||||
expect(wrapper.find('.el-descriptions__content').attributes('style')).toContain('width: 50px; min-width: 60px;')
|
||||
})
|
||||
|
||||
test('should render column props', async () => {
|
||||
const wrapper = _mount(`
|
||||
<el-descriptions :column="5" :border="border">
|
||||
@@ -110,7 +154,7 @@ describe('Descriptions.vue', () => {
|
||||
remarks: ['school', 'hospital'],
|
||||
}
|
||||
}, {
|
||||
onClick () {
|
||||
onClick() {
|
||||
this.remarks[0] = CHANGE_VALUE
|
||||
},
|
||||
})
|
||||
|
||||
@@ -2,4 +2,38 @@ import { defineComponent } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ElDescriptionsItem',
|
||||
props: {
|
||||
label: {
|
||||
type: String,
|
||||
default: '1',
|
||||
},
|
||||
span: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
width: {
|
||||
type: [String, Number],
|
||||
default: '',
|
||||
},
|
||||
minWidth: {
|
||||
type: [String, Number],
|
||||
default: '',
|
||||
},
|
||||
align: {
|
||||
type: String,
|
||||
default: 'left',
|
||||
},
|
||||
labelAlign: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
className: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
labelClassName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { computed, defineComponent, h, inject } from 'vue'
|
||||
import { elDescriptionsKey, IDescriptionsInject } from './descriptions.type'
|
||||
import { defineComponent, h, inject } from 'vue'
|
||||
import { addUnit } from '@element-plus/utils/util'
|
||||
import { getNormalizedProps } from '@element-plus/utils/vnode'
|
||||
import { elDescriptionsKey } from './descriptions.type'
|
||||
|
||||
import type { VNode } from 'vue'
|
||||
import type { IDescriptionsInject, IDescriptionsItemInject } from './descriptions.type'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ElDescriptionsCell',
|
||||
@@ -14,42 +19,53 @@ export default defineComponent({
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
setup() {
|
||||
const descriptions = inject(elDescriptionsKey, {} as IDescriptionsInject)
|
||||
|
||||
const label = computed(() => props.cell?.children?.label?.() || props.cell?.props?.label)
|
||||
const content = computed(() => props.cell?.children?.default?.())
|
||||
const span = computed(() => props.cell?.props?.span || 1)
|
||||
|
||||
return {
|
||||
descriptions,
|
||||
label: label,
|
||||
content: content,
|
||||
span: span,
|
||||
}
|
||||
},
|
||||
render() {
|
||||
const item = getNormalizedProps(this.cell as VNode) as IDescriptionsItemInject
|
||||
|
||||
const label = this.cell?.children?.label?.() || item.label
|
||||
const content = this.cell?.children?.default?.()
|
||||
const span = item.span
|
||||
const align = item.align ? `is-${item.align}` : ''
|
||||
const labelAlign = item.labelAlign ? `is-${item.labelAlign}` : '' || align
|
||||
const className = item.className
|
||||
const labelClassName = item.labelClassName
|
||||
const style = {
|
||||
width: addUnit(item.width),
|
||||
minWidth: addUnit(item.minWidth),
|
||||
}
|
||||
|
||||
switch (this.type) {
|
||||
case 'label':
|
||||
return h(this.tag, {
|
||||
class: ['el-descriptions__label', { 'is-bordered-label': this.descriptions.border }],
|
||||
colSpan: this.descriptions.direction === 'vertical' ? this.span : 1,
|
||||
}, this.label)
|
||||
style: style,
|
||||
class: ['el-descriptions__label', { 'is-bordered-label': this.descriptions.border }, labelAlign, labelClassName],
|
||||
colSpan: this.descriptions.direction === 'vertical' ? span : 1,
|
||||
}, label)
|
||||
case 'content':
|
||||
return h(this.tag, {
|
||||
class: 'el-descriptions__content',
|
||||
colSpan: this.descriptions.direction === 'vertical' ? this.span : this.span * 2 - 1,
|
||||
}, this.content)
|
||||
style: style,
|
||||
class: ['el-descriptions__content', align, className],
|
||||
colSpan: this.descriptions.direction === 'vertical' ? span : span * 2 - 1,
|
||||
}, content)
|
||||
default:
|
||||
return h('td', {
|
||||
colSpan: this.span,
|
||||
style: style,
|
||||
class: [align],
|
||||
colSpan: span,
|
||||
}, [
|
||||
h('span', {
|
||||
class: ['el-descriptions__label', { 'is-bordered-label': this.descriptions.border }],
|
||||
}, this.label),
|
||||
class: ['el-descriptions__label', labelClassName],
|
||||
}, label),
|
||||
h('span', {
|
||||
class: 'el-descriptions__content',
|
||||
}, this.content)])
|
||||
class: ['el-descriptions__content', className],
|
||||
}, content)])
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -30,7 +30,8 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, inject } from 'vue'
|
||||
import DescriptionsCell from './descriptions-cell'
|
||||
import { elDescriptionsKey, IDescriptionsInject } from './descriptions.type'
|
||||
import { elDescriptionsKey } from './descriptions.type'
|
||||
import type { IDescriptionsInject } from './descriptions.type'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ElDescriptionsRow',
|
||||
|
||||
@@ -11,7 +11,22 @@ interface IDescriptionsInject {
|
||||
|
||||
const elDescriptionsKey: InjectionKey<IDescriptionsInject> = 'elDescriptions' as any
|
||||
|
||||
interface IDescriptionsItemInject {
|
||||
label: string
|
||||
span: number
|
||||
width: string | number
|
||||
minWidth: string | number
|
||||
align: string
|
||||
labelAlign: string
|
||||
className: string
|
||||
labelClassName: string
|
||||
}
|
||||
|
||||
const elDescriptionsItemKey: InjectionKey<IDescriptionsItemInject> = 'elDescriptionsItem' as any
|
||||
|
||||
export {
|
||||
IDescriptionsInject,
|
||||
elDescriptionsKey,
|
||||
IDescriptionsItemInject,
|
||||
elDescriptionsItemKey,
|
||||
}
|
||||
|
||||
@@ -22,17 +22,17 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, PropType, provide } from 'vue'
|
||||
import { computed, defineComponent, provide } from 'vue'
|
||||
import { isValidComponentSize } from '@element-plus/utils/validators'
|
||||
import DescriptionsItem from '@element-plus/descriptions-item'
|
||||
import DescriptionsRow from './descriptions-row.vue'
|
||||
import { useGlobalConfig } from '@element-plus/utils/util'
|
||||
import { elDescriptionsKey } from './descriptions.type'
|
||||
|
||||
import type { PropType } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ElDescriptions',
|
||||
components: {
|
||||
[DescriptionsItem.name]: DescriptionsItem,
|
||||
[DescriptionsRow.name]: DescriptionsRow,
|
||||
},
|
||||
props: {
|
||||
|
||||
@@ -28,9 +28,22 @@
|
||||
width: 100%;
|
||||
|
||||
th, td {
|
||||
box-sizing: border-box;
|
||||
text-align: left;
|
||||
font-weight: normal;
|
||||
line-height: 1.5;
|
||||
|
||||
@include when(left) {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
@include when(center) {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@include when(right) {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import { Fragment, Text, Comment, createBlock, openBlock, createCommentVNode } from 'vue'
|
||||
import { Fragment, Text, Comment, createBlock, openBlock, createCommentVNode, isVNode, camelize } from 'vue'
|
||||
|
||||
import type { VNode, VNodeTypes, VNodeChild } from 'vue'
|
||||
import { hasOwn } from '@vue/shared'
|
||||
import { warn } from './error'
|
||||
|
||||
type Children = VNodeTypes[] | VNodeTypes
|
||||
|
||||
const TEMPLATE = 'template'
|
||||
|
||||
export const SCOPE = 'VNode'
|
||||
|
||||
export enum PatchFlags {
|
||||
TEXT = 1,
|
||||
CLASS = 2,
|
||||
@@ -87,3 +91,30 @@ export function renderBlock(
|
||||
) {
|
||||
return (openBlock(), createBlock(node, props, children, patchFlag, patchProps))
|
||||
}
|
||||
|
||||
/**
|
||||
* todo
|
||||
* get normalized props from VNode
|
||||
* @param node
|
||||
*/
|
||||
export const getNormalizedProps = (node: VNode) => {
|
||||
if (!isVNode(node)) {
|
||||
warn(SCOPE, 'value must be a VNode')
|
||||
return
|
||||
}
|
||||
const raw = node.props || {}
|
||||
const type = node.type?.props || {}
|
||||
const props = {}
|
||||
|
||||
Object.keys(type).forEach(key => {
|
||||
if (hasOwn(type[key], 'default')) {
|
||||
props[key] = type[key].default
|
||||
}
|
||||
})
|
||||
|
||||
Object.keys(raw).forEach(key => {
|
||||
props[camelize(key)] = raw[key]
|
||||
})
|
||||
|
||||
return props
|
||||
}
|
||||
|
||||
@@ -2,4 +2,12 @@
|
||||
.margin-top {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.my-label {
|
||||
background: #E1F3D8;
|
||||
}
|
||||
|
||||
.my-content {
|
||||
background: #FDE2E2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,6 +126,23 @@ Display multiple fields in list form.
|
||||
```
|
||||
:::
|
||||
|
||||
### Customized Style
|
||||
|
||||
:::demo
|
||||
|
||||
```html
|
||||
<el-descriptions title="Customized style list" :column="3" border>
|
||||
<el-descriptions-item label="Username" label-align="right" align="center" label-class-name="my-label" class-name="my-content" width="150px">kooriookami</el-descriptions-item>
|
||||
<el-descriptions-item label="Telephone" label-align="right" align="center">18100000000</el-descriptions-item>
|
||||
<el-descriptions-item label="Place" label-align="right" align="center">Suzhou</el-descriptions-item>
|
||||
<el-descriptions-item label="Remarks" label-align="right" align="center">
|
||||
<el-tag size="small">School</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="Address" label-align="right" align="center">No.1188, Wuzhong Avenue, Wuzhong District, Suzhou, Jiangsu Province</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
```
|
||||
:::
|
||||
|
||||
### Descriptions Attributes
|
||||
| Attribute | Description | Type | Accepted Values | Default |
|
||||
|------------- |---------------- |---------------- |---------------------- |-------- |
|
||||
@@ -148,6 +165,12 @@ Display multiple fields in list form.
|
||||
|------------- |---------------- |---------------- |---------------------- |-------- |
|
||||
| label | label text | string | — | — |
|
||||
| span | colspan of column | number | — | 1 |
|
||||
| width | column width, the width of the same column in different rows is set by the max value | string / number | — | — |
|
||||
| min-width | column minimum width, columns with `width` has a fixed width, while columns with `min-width` has a width that is distributed in proportion | string / number | — | — |
|
||||
| align | column content alignment | string | left / center / right | left |
|
||||
| label-align | column label alignment, if omitted, the value of the above `align` attribute will be applied | string | left / center / right | — |
|
||||
| class-name | column content custom class name | string | — | — |
|
||||
| label-class-name | column label custom class name | string | — | — |
|
||||
|
||||
### Descriptions Item Slots
|
||||
|
||||
|
||||
@@ -148,6 +148,12 @@ Display multiple fields in list form.
|
||||
|------------- |---------------- |---------------- |---------------------- |-------- |
|
||||
| label | label text | string | — | — |
|
||||
| span | colspan of column | number | — | 1 |
|
||||
| width | column width, the width of the same column in different rows is set by the max value | string / number | — | — |
|
||||
| min-width | column minimum width, columns with `width` has a fixed width, while columns with `min-width` has a width that is distributed in proportion | string / number | — | — |
|
||||
| align | column content alignment | string | left / center / right | left |
|
||||
| label-align | column label alignment, if omitted, the value of the above `align` attribute will be applied | string | left / center / right | — |
|
||||
| class-name | column content custom class name | string | — | — |
|
||||
| label-class-name | column label custom class name | string | — | — |
|
||||
|
||||
### Descriptions Item Slots
|
||||
|
||||
|
||||
@@ -148,6 +148,12 @@ Display multiple fields in list form.
|
||||
|------------- |---------------- |---------------- |---------------------- |-------- |
|
||||
| label | label text | string | — | — |
|
||||
| span | colspan of column | number | — | 1 |
|
||||
| width | column width, the width of the same column in different rows is set by the max value | string / number | — | — |
|
||||
| min-width | column minimum width, columns with `width` has a fixed width, while columns with `min-width` has a width that is distributed in proportion | string / number | — | — |
|
||||
| align | column content alignment | string | left / center / right | left |
|
||||
| label-align | column label alignment, if omitted, the value of the above `align` attribute will be applied | string | left / center / right | — |
|
||||
| class-name | column content custom class name | string | — | — |
|
||||
| label-class-name | column label custom class name | string | — | — |
|
||||
|
||||
### Descriptions Item Slots
|
||||
|
||||
|
||||
@@ -148,6 +148,12 @@ Display multiple fields in list form.
|
||||
|------------- |---------------- |---------------- |---------------------- |-------- |
|
||||
| label | label text | string | — | — |
|
||||
| span | colspan of column | number | — | 1 |
|
||||
| width | column width, the width of the same column in different rows is set by the max value | string / number | — | — |
|
||||
| min-width | column minimum width, columns with `width` has a fixed width, while columns with `min-width` has a width that is distributed in proportion | string / number | — | — |
|
||||
| align | column content alignment | string | left / center / right | left |
|
||||
| label-align | column label alignment, if omitted, the value of the above `align` attribute will be applied | string | left / center / right | — |
|
||||
| class-name | column content custom class name | string | — | — |
|
||||
| label-class-name | column label custom class name | string | — | — |
|
||||
|
||||
### Descriptions Item Slots
|
||||
|
||||
|
||||
@@ -126,6 +126,23 @@
|
||||
```
|
||||
:::
|
||||
|
||||
### 自定义样式
|
||||
|
||||
:::demo
|
||||
|
||||
```html
|
||||
<el-descriptions title="自定义样式列表" :column="3" border>
|
||||
<el-descriptions-item label="用户名" label-align="right" align="center" label-class-name="my-label" class-name="my-content" width="150px">kooriookami</el-descriptions-item>
|
||||
<el-descriptions-item label="手机号" label-align="right" align="center">18100000000</el-descriptions-item>
|
||||
<el-descriptions-item label="居住地" label-align="right" align="center">苏州市</el-descriptions-item>
|
||||
<el-descriptions-item label="备注" label-align="right" align="center">
|
||||
<el-tag size="small">学校</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="联系地址" label-align="right" align="center">江苏省苏州市吴中区吴中大道 1188 号</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
```
|
||||
:::
|
||||
|
||||
### Descriptions Attributes
|
||||
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
||||
|------------- |---------------- |---------------- |---------------------- |-------- |
|
||||
@@ -148,6 +165,12 @@
|
||||
|------------- |---------------- |---------------- |---------------------- |-------- |
|
||||
| label | 标签文本 | string | — | — |
|
||||
| span | 列的数量 | number | — | 1 |
|
||||
| width | 列的宽度,不同行相同列的宽度按最大值设定 | string / number | — | — |
|
||||
| min-width | 列的最小宽度,与 `width` 的区别是 `width` 是固定的,`min-width` 会把剩余宽度按比例分配给设置了 `min-width` 的列 | string / number | — | — |
|
||||
| align | 列的内容对齐方式 | string | left / center / right | left |
|
||||
| label-align | 列的标签对齐方式,若不设置该项,则使用内容的对齐方式 | string | left / center / right | — |
|
||||
| class-name | 列的内容自定义类名 | string | — | — |
|
||||
| label-class-name | 列的标签自定义类名 | string | — | — |
|
||||
|
||||
### Descriptions Item Slots
|
||||
|
||||
|
||||
Reference in New Issue
Block a user