u
This commit is contained in:
1 parent
d37d92996a
commit
a8f3e58a27
25 files changed
+1401
-399
No files matched your search
@@ -0,0 +1,48 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
function toggleLoading() {
|
||||
loading.value = !loading.value
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="main">
|
||||
<!-- 卡片 -->
|
||||
<ACard
|
||||
:loading="loading"
|
||||
title="标题"
|
||||
sub-title="副标题"
|
||||
style="width: 400px;"
|
||||
hover-shadow
|
||||
cover="https://tdesign.gtimg.com/site/source/card-demo.png"
|
||||
>
|
||||
<template #actions>
|
||||
actions
|
||||
</template>
|
||||
|
||||
这里是内容
|
||||
|
||||
<template #footer>
|
||||
这里是footer
|
||||
</template>
|
||||
</ACard>
|
||||
|
||||
<!-- 按钮 -->
|
||||
<button style="margin-top: 16px;" @click="toggleLoading">
|
||||
{{ loading ? '关闭 Loading' : '开启 Loading' }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.main {
|
||||
height: 800px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,92 @@
|
||||
@use '../_styles/_variables.scss' as *;
|
||||
@use 'sass:color';
|
||||
|
||||
$card-prefix: '#{$prefix}-card';
|
||||
|
||||
.#{$card-prefix} {
|
||||
font: 14px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
border-radius: $border-radius;
|
||||
background-color: $bg-color-container;
|
||||
color: $text-color-base;
|
||||
|
||||
&.#{$card-prefix}-hover {
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
box-shadow: $shadow-surface;
|
||||
}
|
||||
|
||||
.#{$card-prefix}-cover {
|
||||
margin-top: -1px;
|
||||
margin-inline-start: -1px;
|
||||
margin-inline-end: -1px;
|
||||
}
|
||||
}
|
||||
|
||||
&.#{$card-prefix}-border {
|
||||
border: 1px solid $border-color;
|
||||
}
|
||||
|
||||
.#{$card-prefix}-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 16px;
|
||||
position: relative;
|
||||
border-bottom: 1px solid transparent;
|
||||
|
||||
&.#{$card-prefix}-separator {
|
||||
border-color: $separator-color;
|
||||
}
|
||||
|
||||
.#{$card-prefix}-header-wrapper {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 16px;
|
||||
.#{$card-prefix}-title {
|
||||
font-size: 16px;
|
||||
color: $text-color-base;
|
||||
font-weight: 600;
|
||||
word-break: break-all;
|
||||
}
|
||||
.#{$card-prefix}-subtitle {
|
||||
font-size: 14px;
|
||||
color: $text-color-secondary;
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
|
||||
.#{$card-prefix}-actions {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
|
||||
.#{$card-prefix}-body {
|
||||
display: flow-root;
|
||||
padding: 16px;
|
||||
}
|
||||
.#{$card-prefix}-footer {
|
||||
border-top: 1px solid transparent;
|
||||
|
||||
&.#{$card-prefix}-separator {
|
||||
border-color: $separator-color;
|
||||
}
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px 16px;
|
||||
}
|
||||
|
||||
.#{$card-prefix}-cover {
|
||||
display: flow-root;
|
||||
img {
|
||||
display: flow-root;
|
||||
width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
import type { PropType, VNode } from 'vue'
|
||||
import { defineComponent } from 'vue'
|
||||
import { getPrefixCls } from '../_utils/getPrefixCls'
|
||||
import Spin from '../spin/index'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Card',
|
||||
props: {
|
||||
title: {
|
||||
type: [String, Object, Function] as PropType<string | VNode | (() => VNode)>,
|
||||
default: '',
|
||||
},
|
||||
subTitle: {
|
||||
type: [String, Object, Function] as PropType<string | VNode | (() => VNode)>,
|
||||
default: '',
|
||||
},
|
||||
hoverShadow: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
// 分割线
|
||||
separator: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
// 边框线
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
cover: {
|
||||
type: String,
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
loadingProp: {
|
||||
type: Object as PropType<Record<string, any>>,
|
||||
default: () => ({}),
|
||||
},
|
||||
class: {
|
||||
type: [String, Array, Object],
|
||||
default: '',
|
||||
},
|
||||
style: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
setup(props, { slots }) {
|
||||
const prefixCls = getPrefixCls('card')
|
||||
|
||||
const renderHeader = () => {
|
||||
if (slots.header) {
|
||||
return <div class={`${prefixCls}-header`}>{slots.header()}</div>
|
||||
}
|
||||
|
||||
const titleContent = slots.title?.() ?? (typeof props.title === 'function' ? props.title() : props.title)
|
||||
const subTitleContent = slots.subTitle?.() ?? (typeof props.subTitle === 'function' ? props.subTitle() : props.subTitle)
|
||||
const actionsContent = slots.actions?.()
|
||||
|
||||
if (!titleContent && !subTitleContent && !actionsContent)
|
||||
return null
|
||||
|
||||
return (
|
||||
<div
|
||||
class={[
|
||||
`${prefixCls}-header`,
|
||||
{ [`${prefixCls}-separator`]: props.separator },
|
||||
]}
|
||||
>
|
||||
<div class={`${prefixCls}-header-wrapper`}>
|
||||
<div>
|
||||
{titleContent && <div class={`${prefixCls}-title`}>{titleContent}</div>}
|
||||
{subTitleContent && <div class={`${prefixCls}-sub-title`}>{subTitleContent}</div>}
|
||||
</div>
|
||||
</div>
|
||||
{actionsContent && <div class={`${prefixCls}-header-actions`}>{actionsContent}</div>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const renderBody = () => {
|
||||
if (!slots.default)
|
||||
return null
|
||||
return <div class={`${prefixCls}-body`}>{slots.default()}</div>
|
||||
}
|
||||
|
||||
const renderFooter = () => {
|
||||
if (!slots.footer)
|
||||
return null
|
||||
return (
|
||||
<div
|
||||
class={[
|
||||
`${prefixCls}-footer`,
|
||||
{ [`${prefixCls}-separator`]: props.separator },
|
||||
]}
|
||||
>
|
||||
{slots.footer()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
const renderCover = () => {
|
||||
if (slots.cover) {
|
||||
return <div class={`${prefixCls}-cover`}>{slots.cover()}</div>
|
||||
}
|
||||
|
||||
if (props.cover) {
|
||||
return (
|
||||
<div class={`${prefixCls}-cover`}>
|
||||
<img src={props.cover} alt="" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
return () => {
|
||||
const cardNode = (
|
||||
<div
|
||||
class={[
|
||||
prefixCls,
|
||||
props.class,
|
||||
{ [`${prefixCls}-hover`]: props.hoverShadow },
|
||||
{ [`${prefixCls}-border`]: props.border },
|
||||
]}
|
||||
style={{ ...props.style }}
|
||||
>
|
||||
{renderHeader()}
|
||||
{renderBody()}
|
||||
{renderCover()}
|
||||
{renderFooter()}
|
||||
</div>
|
||||
)
|
||||
|
||||
// 如果 loading 就包一层 Spin
|
||||
if (props.loading) {
|
||||
return (
|
||||
<Spin {...props.loadingProp} loading={props.loading}>
|
||||
{cardNode}
|
||||
</Spin>
|
||||
)
|
||||
}
|
||||
|
||||
return cardNode
|
||||
}
|
||||
},
|
||||
})
|
||||
Reference in new issue
Block a user