<template>
<div class="card" style="overflow: visible" ref="card">
<div class="card-header">
<div class="icon"></div>
<div class="title">
{{ props.title }}
<ShowInfo :info="info" v-if="showBtns || showQuestion" />
</div>
<div class="buttons">
<slot name="headerExtend"></slot>
<div class="card-links">
<el-link :underline="false" @click="expand" v-if="showBtns || showExpand">
<svg-icon name="Vector" />
</el-link>
<el-link :underline="false" @click="() => myEmit('download', props.title)" v-if="showBtns || showDownload">
<svg-icon name="download" />
</el-link>
</div>
</div>
</div>
<div
class="card-body"
:class="{ 'is-collapse': isCollapse }"
ref="bodyRef"
:style="{
resize: props.resize ? 'vertical' : 'none',
}"
style="overflow: visible"
>
<div style="position: relative; height: 100%; overflow: visible">
<slot />
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useWidthOverflow } from "@/hooks/dom";
import ShowInfo from "@/components/ShowInfo.vue";
const props = withDefaults(
defineProps<{
title: string;
legend?: { color: string; name: string }[];
bodyPadding?: boolean;
height?: string | number;
collapse?: boolean;
skipBodyHeight?: boolean;
resize?: boolean;
maxBodyHeight?: string;
overflowHidden?: boolean;
withTable?: boolean;
showBtns: boolean;
showQuestion: boolean;
showExpand: boolean;
showDownload: boolean;
info: {title: string, option: any[]};
}>(),
{
bodyPadding: true,
maxBodyHeight: "unset",
overflowHidden: true,
withTable: false,
showBtns: false,
showQuestion: false,
showExpand: false,
showDownload: false,
info: () => { return { title: '', option: [] } },
}
);
const hidden = ref(props.overflowHidden ? "hidden" : "scroll");
const padding = ref(props.bodyPadding ? "24px 16px" : "0px");
const withTableBorderRadius = ref(props.withTable ? "2px 2px 0 0" : "2px");
const bodyRef = ref<HTMLDivElement>();
const bodyHeight = ref(`calc(100% - ${props.bodyPadding ? 90 : 42}px)`);
const minBodyHeight = ref(bodyHeight.value);
onMounted(() => {
nextTick(() => {
if (!props.skipBodyHeight && bodyRef && bodyRef.value) {
bodyHeight.value = `${
bodyRef.value.offsetHeight + (props.bodyPadding && !props.collapse ? 48 : 0)
}px`;
}
if (
bodyRef &&
bodyRef.value &&
props.resize &&
window &&
"ResizeObserver" in window
) {
const resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
if (!collapsing.value) {
bodyHeight.value = `${entry.contentRect.height}px`;
if (minBodyHeight.value.startsWith("calc")) {
minBodyHeight.value = bodyHeight.value;
}
myEmit("resize-body-height", entry.contentRect.height);
}
}
});
resizeObserver.observe(bodyRef.value);
}
});
});
const myEmit = defineEmits<{
(event: "resize-body-height", height: number): void;
(event: 'download', title: string): void;
(event: 'expand'): void;
}>();
const height = computed(() => {
if (!props.height) {
return "100%";
}
return ["%", "px"].includes(`${props.height}`) ? props.height : `${props.height}px`;
});
const isCollapse = ref(false);
const collapsing = ref(false);
const extraRef = ref();
const dropdownRef = ref();
const { trigger } = useWidthOverflow(extraRef, dropdownRef);
defineExpose({
trigger,
});
const card = ref()
const isExpand = ref<boolean>(false)
const expand = () => {
let ref = card.value
if (!isExpand.value) {
ref.style.position = 'fixed'
ref.style.top = 0
ref.style.right = 0
ref.style.left = 0
ref.style.bottom = 0
ref.style.zIndex = 9999
ref.style.height = '100vh'
} else {
ref.style.position = 'relative'
ref.style.top = 'auto'
ref.style.right = 'auto'
ref.style.left = 'auto'
ref.style.bottom = 'auto'
ref.style.zIndex = 'auto'
ref.style.height = height.value
}
isExpand.value = !isExpand.value
}
</script>
<style scoped lang="scss">
.card {
box-sizing: border-box;
height: v-bind(height);
border: 1px solid var(--el-og-border-color);
border-radius: v-bind(withTableBorderRadius);
overflow: v-bind(hidden);
.card-header {
display: flex;
overflow: hidden;
justify-content: space-between;
padding: 0 15px;
align-items: center;
min-width: 0;
height: 40px;
border-bottom: 1px solid var(--el-og-border-color);
background-color: var(--background-color-1);
.icon {
width: 4px;
height: 14px;
background: var(--primary-6);
border-radius: 1px;
margin-right: 4px;
}
.title {
font-weight: 500;
font-size: 14px;
line-height: 30px;
flex-shrink: 0;
> svg {
position: relative;
top: -2px;
margin-right: 12px;
transition: transform var(--el-transition-duration);
&.is-collapse {
transform: rotate(-90deg);
}
}
.question {
margin: 0px 4px;
top: 40%
}
}
.tips {
margin-left: 6px;
padding-top: 2px;
}
.buttons {
position: relative;
display: flex;
justify-content: flex-end;
margin-left: 16px;
overflow: hidden;
flex: 1;
min-width: 0;
align-items: center;
font-size: 12px;
height: 40px;
> div {
display: flex;
flex-shrink: 0;
margin-right: 8px;
min-width: 0;
> span {
margin-left: 4px;
}
&:last-of-type {
margin-right: 0;
}
}
}
&--extra {
position: relative;
display: flex;
margin-left: 16px;
overflow: hidden;
flex: 1;
min-width: 0;
align-items: center;
font-size: 12px;
height: 40px;
> div {
display: flex;
flex-shrink: 0;
margin-right: 12px;
min-width: 0;
> span {
margin-left: 4px;
}
&:last-of-type {
margin-right: 0;
}
}
}
}
&-body {
position: relative;
box-sizing: border-box;
height: v-bind(bodyHeight);
min-height: v-bind(minBodyHeight);
max-height: v-bind(maxBodyHeight);
overflow: v-bind(hidden);
background-color: var(--el-bg-color-og);
border-radius: 0 0 8px 8px;
will-change: height;
> div {
box-sizing: border-box;
padding: v-bind(padding);
height: v-bind(bodyHeight);
}
&.is-collapse {
height: 0 !important;
}
}
}
.hover {
display: flex;
align-items: center;
> span {
margin-left: 4px;
}
}
.rect {
width: 16px;
height: 16px;
flex-shrink: 0;
}
</style>