This commit is contained in:
oneao committed 2025-09-21 21:52:08 +08:00
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
}
},
})