效果如下图:在线预览
## APIs
Card
参数 | 说明 | 类型 | 默认值 |
---|---|---|---|
width | 卡片宽度,单位 px | number | string | ‘auto’ |
bordered | 是否有边框 | boolean | true |
size | 卡片的尺寸 | ‘small’ | ‘middle’ | ‘large’ | ‘middle’ |
hoverable | 鼠标移过时可浮起 | boolean | false |
loading | 当卡片内容还在加载中时,可以用 loading 展示一个占位 | boolean | false |
skeletonProps | 加载中时,骨架屏的属性配置,参考 Skeleton Props | object | {} |
title | 卡片标题 | string | slot | undefined |
extra | 卡片右上角的操作区域 | string | slot | undefined |
headStyle | 自定义标题区域样式 | CSSProperties | {} |
bodyStyle | 自定义内容区域样式 | CSSProperties | {} |
创建卡片组件Card.vue
其中引入使用了以下组件和工具函数:
<script setup lang="ts">
import { computed } from 'vue'
import type { CSSProperties } from 'vue'
import Skeleton from '../skeleton'
import { useSlotsExist } from '../utils'
interface Props {
width?: number | string // 卡片宽度,单位 px
bordered?: boolean // 是否有边框
size?: 'small' | 'middle' | 'large' // 卡片的尺寸
hoverable?: boolean // 鼠标移过时可浮起
loading?: boolean // 当卡片内容还在加载中时,可以用 loading 展示一个占位
skeletonProps?: object // 加载中时,骨架屏的属性配置,参考 Skeleton Props
title?: string // 卡片标题 string | slot
extra?: string // 卡片右上角的操作区域 string | slot
headStyle?: CSSProperties // 自定义标题区域样式
bodyStyle?: CSSProperties // 自定义内容区域样式
}
const props = withDefaults(defineProps<Props>(), {
width: 'auto',
bordered: true,
size: 'middle',
hoverable: false,
loading: false,
skeletonProps: () => ({}),
title: undefined,
extra: undefined,
headStyle: () => ({}),
bodyStyle: () => ({})
})
const cardWidth = computed(() => {
if (typeof props.width === 'number') {
return props.width + 'px'
}
return props.width
})
const slotsExist = useSlotsExist(['title', 'extra'])
const showHeader = computed(() => {
return slotsExist.title || slotsExist.extra || props.title || props.extra
})
const showTitle = computed(() => {
return Boolean(slotsExist.title || props.title)
})
const showExtra = computed(() => {
return Boolean(slotsExist.extra || props.extra)
})
</script>
<template>
<div
class="m-card"
:class="{
'card-bordered': bordered,
'card-small': size === 'small',
'card-middle': size === 'middle',
'card-large': size === 'large',
'card-hoverable': hoverable
}"
:style="`width: ${cardWidth};`"
>
<div class="m-card-head" :style="headStyle" v-if="showHeader">
<div class="m-head-wrapper">
<div v-if="showTitle" class="head-title">
<slot name="title">{{ title }}</slot>
</div>
<div v-if="showExtra" class="head-extra">
<slot name="extra">{{ extra }}</slot>
</div>
</div>
</div>
<div class="m-card-body" :style="bodyStyle">
<Skeleton :title="false" :loading="loading" v-bind="skeletonProps">
<slot></slot>
</Skeleton>
</div>
</div>
</template>
<style lang="less" scoped>
.m-card {
font-size: 14px;
color: rgba(0, 0, 0, 0.88);
line-height: 1.5714285714285714;
position: relative;
background: #ffffff;
border-radius: 8px;
text-align: left;
transition: width 0.2s;
.m-card-head {
display: flex;
justify-content: center;
flex-direction: column;
margin-bottom: -1px;
color: rgba(0, 0, 0, 0.88);
font-weight: 600;
background: transparent;
border-bottom: 1px solid #f0f0f0;
border-radius: 8px 8px 0 0;
transition: all 0.2s;
.m-head-wrapper {
width: 100%;
display: flex;
align-items: center;
gap: 8px;
.head-title {
display: inline-block;
flex: 1;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.head-extra {
margin-left: auto;
font-weight: normal;
font-size: 14px;
transition: font-size 0.2s;
}
}
}
.m-card-body {
border-radius: 0 0 8px 8px;
transition: padding 0.2s;
}
}
.card-bordered {
border: 1px solid #f0f0f0;
}
.card-small {
.m-card-head {
min-height: 38px;
padding: 0 12px;
font-size: 14px;
}
.m-card-body {
padding: 12px;
}
}
.card-middle {
.m-card-head {
min-height: 56px;
padding: 0 24px;
font-size: 16px;
}
.m-card-body {
padding: 24px;
}
}
.card-large {
font-size: 16px;
.m-card-head {
min-height: 74px;
padding: 0 36px;
font-size: 18px;
.m-head-wrapper .head-extra {
font-size: 16px;
}
}
.m-card-body {
padding: 36px;
}
}
.card-hoverable {
cursor: pointer;
transition: box-shadow 0.2s, border-color 0.2s;
&:hover {
box-shadow:
0 1px 2px -2px rgba(0, 0, 0, 0.16),
0 3px 6px 0 rgba(0, 0, 0, 0.12),
0 5px 12px 4px rgba(0, 0, 0, 0.09);
}
}
</style>
在要使用的页面引入
其中引入使用了以下组件:
<script setup lang="ts">
import Card from './Card.vue'
import { ref } from 'vue'
const sizeOptions = [
{
label: 'small',
value: 'small'
},
{
label: 'middle',
value: 'middle'
},
{
label: 'large',
value: 'large'
}
]
const cardWidth = {
small: 240,
middle: 300,
large: 360
}
const size = ref('middle')
const loading = ref(true)
</script>
<template>
<div>
<h1>{{ $route.name }} {{ $route.meta.title }}</h1>
<h2 class="mt30 mb10">基本使用</h2>
<Card title="Default size card" :width="300">
<template #extra>
<a href="#">more</a>
</template>
<p>card content</p>
<p>card content</p>
<p>card content</p>
</Card>
<h2 class="mt30 mb10">在灰色背景上使用无边框的卡片</h2>
<div style="display: inline-block; background: #ececec; padding: 30px; border-radius: 8px">
<Card title="Card title" :bordered="false" :width="300">
<p>Card content</p>
<p>Card content</p>
<p>Card content</p>
</Card>
</div>
<h2 class="mt30 mb10">简洁卡片</h2>
<Card hoverable :width="300">
<p>Card content</p>
<p>Card content</p>
<p>Card content</p>
</Card>
<h2 class="mt30 mb10">三种尺寸</h2>
<Space vertical>
<Radio :options="sizeOptions" v-model:value="size" button button-style="solid" />
<Card :size="size" :title="`${size.toUpperCase()} size card`" :width="cardWidth[size as keyof typeof cardWidth]">
<template #extra>
<a href="#">more</a>
</template>
<p>card content</p>
<p>card content</p>
<p>card content</p>
</Card>
</Space>
<h2 class="mt30 mb10">鼠标移过时可浮起</h2>
<Card hoverable title="Card title" :width="300">
<p>Card content</p>
<p>Card content</p>
<p>Card content</p>
</Card>
<h2 class="mt30 mb10">预加载卡片</h2>
<Space vertical>
<Space align="center">Loading State:<Switch v-model="loading" /></Space>
<Card :loading="loading" title="Card title" :width="300">
<p>Card content</p>
<p>Card content</p>
<p>Card content</p>
</Card>
</Space>
<h2 class="mt30 mb10">自定义样式</h2>
<Card
title="Default size card"
:width="300"
:headStyle="{ fontSize: '18px', color: '#fff', backgroundColor: '#1677ff' }"
:bodyStyle="{ fontSize: '16px', color: '#fff', backgroundColor: '#52c41a' }"
>
<template #extra>
<a href="#">more</a>
</template>
<p>card content</p>
<p>card content</p>
<p>card content</p>
</Card>
<h2 class="mt30 mb10">内部嵌套卡片</h2>
<Card title="Card title" :width="360">
<p style="font-size: 14px; color: rgba(0, 0, 0, 0.85); margin-bottom: 16px; font-weight: 500">Group title</p>
<Card title="Inner card title">
<template #extra>
<a href="#">More</a>
</template>
Inner Card content
</Card>
<Card title="Inner card title" :style="{ marginTop: '16px' }">
<template #extra>
<a href="#">More</a>
</template>
Inner Card content
</Card>
</Card>
<h2 class="mt30 mb10">栅格卡片</h2>
<div style="background-color: #ececec; padding: 20px; border-radius: 8px">
<Row :gutter="16">
<Col :span="8">
<Card title="Card title" :bordered="false">
<p>card content</p>
</Card>
</Col>
<Col :span="8">
<Card title="Card title" :bordered="false">
<p>card content</p>
</Card>
</Col>
<Col :span="8">
<Card title="Card title" :bordered="false">
<p>card content</p>
</Card>
</Col>
</Row>
</div>
</div>
</template>