Skip to content
This repository has been archived by the owner on Aug 28, 2024. It is now read-only.

Commit

Permalink
feat: add description component demo (#327)
Browse files Browse the repository at this point in the history
  • Loading branch information
DesignHhuang authored Feb 2, 2024
1 parent daad5aa commit 385e1c2
Show file tree
Hide file tree
Showing 11 changed files with 2,165 additions and 1,285 deletions.
77 changes: 77 additions & 0 deletions apps/admin/src/pages/demo/Desc.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<script lang="ts" setup>
import { Description, DescItem, useDescription } from '@vben/components'
const mockData: Recordable = {
username: 'test',
nickName: 'VB',
age: '123',
phone: '15695909xxx',
email: '[email protected]',
addr: '厦门市思明区',
sex: '',
certy: '3504256199xxxxxxxxx',
tag: 'orange',
}
const schema: DescItem[] = [
{
field: 'username',
label: '用户名',
},
{
field: 'nickName',
label: '昵称',
render: (curVal, data) => {
return `${data.username}-${curVal}`
},
},
{
field: 'phone',
label: '联系电话',
},
{
field: 'email',
label: '邮箱',
},
{
field: 'addr',
label: '地址',
},
]
const [register] = useDescription({
title: 'useDescription',
data: mockData,
schema: schema,
labelPlacement: 'left',
})
const [register1] = useDescription({
title: '无边框',
bordered: false,
data: mockData,
schema: schema,
labelPlacement: 'left',
})
</script>

<template>
<VbenCard title="详情组件示例">
<Description
title="基础示例"
:column="3"
label-placement="left"
:data="mockData"
:schema="schema"
/>

<Description
class="mt-4"
title="垂直示例"
:column="2"
:data="mockData"
:schema="schema"
/>

<Description @register="register" class="mt-4" />
<Description @register="register1" class="mt-4" />
</VbenCard>
</template>
3 changes: 3 additions & 0 deletions packages/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@ export { default as ClickOutside } from './src/click-outside/index.vue'
export { default as IconPicker } from './src/icon-picker/index.vue'
export { default as CropperImage } from './src/cropper/index.vue'
export { default as CropperAvatar } from './src/cropper/cropper-avatar.vue'
export { default as Description } from './src/description/index.vue'
export { useDescription } from './src/description/useDescription'

export { default as CollapseTransition } from './src/transition/collapse-transition.vue'
export { default as CustomTransition } from './src/transition/index'

export type { QrCodeActionType } from './src/qrcode/typing'
export * from './src/description/typing'
174 changes: 174 additions & 0 deletions packages/components/src/description/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
<script lang="tsx">
import type { DescriptionProps, DescInstance, DescItem } from './typing'
import {
type CSSProperties,
type PropType,
defineComponent,
computed,
ref,
unref,
toRefs,
useAttrs,
} from 'vue'
import { getSlot, isFunction, get } from '@vben/utils'
const props = {
useContainer: { type: Boolean, default: true },
title: { type: String, default: '' },
size: {
type: String,
validator: (v: string) =>
['small', 'medium', 'large', undefined].includes(v),
default: 'medium',
},
bordered: { type: Boolean, default: true },
column: {
type: Number,
default: 3,
},
schema: {
type: Array as PropType<DescItem[]>,
default: () => [],
},
data: { type: Object },
}
export default defineComponent({
name: 'Description',
props,
emits: ['register'],
setup(props, { slots, emit }) {
const propsRef = ref<Partial<DescriptionProps> | null>(null)
const attrs = useAttrs()
// Custom title component: get title
const getMergeProps = computed(() => {
return {
...props,
...(unref(propsRef) as any),
}
})
const getProps = computed(() => {
const opt = {
...unref(getMergeProps),
title: undefined,
}
return opt as DescriptionProps
})
/**
* @description: Whether to setting title
*/
const useWrapper = computed(() => !!unref(getMergeProps).title)
const getDescriptionsProps = computed(() => {
return { ...unref(attrs), ...unref(getProps) }
})
/**
* @description:设置desc
*/
function setDescProps(descProps: Partial<DescriptionProps>): void {
// Keep the last setDrawerProps
propsRef.value = {
...(unref(propsRef) as Record<string, any>),
...descProps,
} as Record<string, any>
}
// Prevent line breaks
function renderLabel({ label, labelMinWidth, labelStyle }: DescItem) {
if (!labelStyle && !labelMinWidth) {
return label
}
const labelStyles: CSSProperties = {
...labelStyle,
minWidth: `${labelMinWidth}px `,
}
return <div style={labelStyles}>{label}</div>
}
function renderItem() {
const { schema, data } = unref(getProps)
return unref(schema)
.map((item) => {
const { render, field, span, show, contentMinWidth } = item
if (show && isFunction(show) && !show(data)) {
return null
}
const getContent = () => {
const _data = unref(getProps)?.data
if (!_data) {
return null
}
const getField = get(_data, field)
// eslint-disable-next-line
if (getField && !_data.hasOwnProperty(field)) {
return isFunction(render) ? render('', _data) : ''
}
return isFunction(render) ? render(getField, _data) : getField ?? ''
}
const width = contentMinWidth
return (
<VbenDescItem label={renderLabel(item)} key={field} span={span}>
{() => {
if (!contentMinWidth) {
return getContent()
}
const style: CSSProperties = {
minWidth: `${width}px`,
}
return <div style={style}>{getContent()}</div>
}}
</VbenDescItem>
)
})
.filter((item) => !!item)
}
const renderDesc = () => {
return (
<VbenDesc class="description" {...(unref(getDescriptionsProps) as any)}>
{renderItem()}
</VbenDesc>
)
}
const renderContainer = () => {
const content = props.useContainer ? (
renderDesc()
) : (
<div>{renderDesc()}</div>
)
// Reduce the dom level
if (!props.useContainer) {
return content
}
const { title } = unref(getMergeProps)
return (
<vben-card title={title}>
{{
default: () => content,
action: () => getSlot(slots, 'action'),
}}
</vben-card>
)
}
const methods: DescInstance = {
setDescProps,
}
emit('register', methods)
return () => (unref(useWrapper) ? renderContainer() : renderDesc())
},
})
</script>
44 changes: 44 additions & 0 deletions packages/components/src/description/typing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import type { VNode, CSSProperties } from 'vue'

declare type Recordable<T = any> = Record<string, T>

export interface DescItem {
labelMinWidth?: number
contentMinWidth?: number
labelStyle?: CSSProperties
field: string
label: string | VNode | JSX.Element
// Merge column
span?: number
show?: (...arg: any) => boolean
// render
render?: (
val: any,
data: Recordable,
) => VNode | undefined | JSX.Element | Element | string | number
}

export interface DescriptionProps {
useContainer?: boolean
/**
* item configuration
* @type DescItem
*/
schema: DescItem[]
/**
* 数据
* @type object
*/
data: Recordable
}

export interface DescInstance {
setDescProps(descProps: Partial<DescriptionProps>): void
}

export type Register = (descInstance: DescInstance) => void

/**
* @description:
*/
export type UseDescReturnType = [Register, DescInstance]
36 changes: 36 additions & 0 deletions packages/components/src/description/useDescription.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type {
DescriptionProps,
DescInstance,
UseDescReturnType,
} from './typing'
import { ref, getCurrentInstance, unref } from 'vue'
import { isProdMode } from '@vben/utils'

export function useDescription(
props?: Partial<DescriptionProps>,
): UseDescReturnType {
if (!getCurrentInstance()) {
throw new Error(
'useDescription() can only be used inside setup() or functional components!',
)
}
const desc = ref<Nullable<DescInstance>>(null)
const loaded = ref(false)

function register(instance: DescInstance) {
if (unref(loaded) && isProdMode()) {
return
}
desc.value = instance
props && instance.setDescProps(props)
loaded.value = true
}

const methods: DescInstance = {
setDescProps: (descProps: Partial<DescriptionProps>): void => {
unref(desc)?.setDescProps(descProps)
},
}

return [register, methods]
}
1 change: 1 addition & 0 deletions packages/locale/src/lang/en/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,5 +96,6 @@ export default {
steps: {
page: 'Intro page',
},
desc: 'Desc',
},
}
1 change: 1 addition & 0 deletions packages/locale/src/lang/zh-CN/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,5 +97,6 @@ export default {
steps: {
page: '引导页',
},
desc: '详情组件',
},
}
8 changes: 8 additions & 0 deletions packages/router/src/routes/modules/demo/demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,14 @@ const dashboard: RouteRecordItem = {
title: 'routes.demo.cropperImage',
},
},
{
path: 'desc',
name: 'Desc',
component: () => import('@/pages/demo/Desc.vue'),
meta: {
title: 'routes.demo.desc',
},
},
],
}

Expand Down
Loading

0 comments on commit 385e1c2

Please sign in to comment.