feat: 🚀 订阅功能

This commit is contained in:
2025-09-16 16:38:30 +08:00
parent eb1b66a066
commit d3a3ef2911
456 changed files with 40544 additions and 124 deletions

View File

@@ -0,0 +1,68 @@
<template>
<div v-show="isShow" :style="style">
<slot></slot>
</div>
</template>
<script setup lang="ts" name="GridItem">
import { computed, inject, Ref, ref, useAttrs, watch } from "vue";
import { BreakPoint, Responsive } from "../interface/index";
type Props = {
offset?: number;
span?: number;
suffix?: boolean;
xs?: Responsive;
sm?: Responsive;
md?: Responsive;
lg?: Responsive;
xl?: Responsive;
};
const props = withDefaults(defineProps<Props>(), {
offset: 0,
span: 1,
suffix: false,
xs: undefined,
sm: undefined,
md: undefined,
lg: undefined,
xl: undefined
});
const attrs = useAttrs() as { index: string };
const isShow = ref(true);
// 注入断点
const breakPoint = inject<Ref<BreakPoint>>("breakPoint", ref("xl"));
const shouldHiddenIndex = inject<Ref<number>>("shouldHiddenIndex", ref(-1));
watch(
() => [shouldHiddenIndex.value, breakPoint.value],
n => {
if (!!attrs.index) {
isShow.value = !(n[0] !== -1 && parseInt(attrs.index) >= Number(n[0]));
}
},
{ immediate: true }
);
const gap = inject("gap", 0);
const cols = inject("cols", ref(4));
const style = computed(() => {
let span = props[breakPoint.value]?.span ?? props.span;
let offset = props[breakPoint.value]?.offset ?? props.offset;
if (props.suffix) {
return {
gridColumnStart: cols.value - span - offset + 1,
gridColumnEnd: `span ${span + offset}`,
marginLeft: offset !== 0 ? `calc(((100% + ${gap}px) / ${span + offset}) * ${offset})` : "unset"
};
} else {
return {
gridColumn: `span ${span + offset > cols.value ? cols.value : span + offset}/span ${
span + offset > cols.value ? cols.value : span + offset
}`,
marginLeft: offset !== 0 ? `calc(((100% + ${gap}px) / ${span + offset}) * ${offset})` : "unset"
};
}
});
</script>

View File

@@ -0,0 +1,167 @@
<template>
<div :style="style">
<slot></slot>
</div>
</template>
<script setup lang="ts" name="Grid">
import {
ref,
watch,
useSlots,
computed,
provide,
onBeforeMount,
onMounted,
onUnmounted,
onDeactivated,
onActivated,
VNodeArrayChildren,
VNode
} from "vue";
import type { BreakPoint } from "./interface/index";
type Props = {
cols?: number | Record<BreakPoint, number>;
collapsed?: boolean;
collapsedRows?: number;
gap?: [number, number] | number;
};
const props = withDefaults(defineProps<Props>(), {
cols: () => ({ xs: 1, sm: 2, md: 2, lg: 3, xl: 4 }),
collapsed: false,
collapsedRows: 1,
gap: 0
});
onBeforeMount(() => props.collapsed && findIndex());
onMounted(() => {
resize({ target: { innerWidth: window.innerWidth } } as unknown as UIEvent);
window.addEventListener("resize", resize);
});
onActivated(() => {
resize({ target: { innerWidth: window.innerWidth } } as unknown as UIEvent);
window.addEventListener("resize", resize);
});
onUnmounted(() => {
window.removeEventListener("resize", resize);
});
onDeactivated(() => {
window.removeEventListener("resize", resize);
});
// 监听屏幕变化
const resize = (e: UIEvent) => {
let width = (e.target as Window).innerWidth;
switch (!!width) {
case width < 768:
breakPoint.value = "xs";
break;
case width >= 768 && width < 992:
breakPoint.value = "sm";
break;
case width >= 992 && width < 1200:
breakPoint.value = "md";
break;
case width >= 1200 && width < 1920:
breakPoint.value = "lg";
break;
case width >= 1920:
breakPoint.value = "xl";
break;
}
};
// 注入 gap 间距
provide("gap", Array.isArray(props.gap) ? props.gap[0] : props.gap);
// 注入响应式断点
let breakPoint = ref<BreakPoint>("xl");
provide("breakPoint", breakPoint);
// 注入要开始折叠的 index
const hiddenIndex = ref(-1);
provide("shouldHiddenIndex", hiddenIndex);
// 注入 cols
const gridCols = computed(() => {
if (typeof props.cols === "object") return props.cols[breakPoint.value] ?? props.cols;
return props.cols;
});
provide("cols", gridCols);
// 寻找需要开始折叠的字段 index
const slots = useSlots().default!();
const findIndex = () => {
let fields: VNodeArrayChildren = [];
let suffix: VNode | null = null;
slots.forEach((slot: any) => {
// suffix
if (typeof slot.type === "object" && slot.type.name === "GridItem" && slot.props?.suffix !== undefined) suffix = slot;
// slot children
if (typeof slot.type === "symbol" && Array.isArray(slot.children)) fields.push(...slot.children);
});
// 计算 suffix 所占用的列
let suffixCols = 0;
if (suffix) {
suffixCols =
((suffix as VNode).props![breakPoint.value]?.span ?? (suffix as VNode).props?.span ?? 1) +
((suffix as VNode).props![breakPoint.value]?.offset ?? (suffix as VNode).props?.offset ?? 0);
}
try {
let find = false;
fields.reduce((prev = 0, current, index) => {
prev +=
((current as VNode)!.props![breakPoint.value]?.span ?? (current as VNode)!.props?.span ?? 1) +
((current as VNode)!.props![breakPoint.value]?.offset ?? (current as VNode)!.props?.offset ?? 0);
if (Number(prev) > props.collapsedRows * gridCols.value - suffixCols) {
hiddenIndex.value = index;
find = true;
throw "find it";
}
return prev;
}, 0);
if (!find) hiddenIndex.value = -1;
} catch (e) {
// console.warn(e);
}
};
// 断点变化时 执行 findIndex
watch(
() => breakPoint.value,
() => {
if (props.collapsed) findIndex();
}
);
// 监听 collapsed
watch(
() => props.collapsed,
value => {
if (value) return findIndex();
hiddenIndex.value = -1;
}
);
// 设置间距
const gridGap = computed(() => {
if (typeof props.gap === "number") return `${props.gap}px`;
if (Array.isArray(props.gap)) return `${props.gap[1]}px ${props.gap[0]}px`;
return "unset";
});
// 设置 style
const style = computed(() => {
return {
display: "grid",
gridGap: gridGap.value,
gridTemplateColumns: `repeat(${gridCols.value}, minmax(0, 1fr))`
};
});
defineExpose({ breakPoint });
</script>

View File

@@ -0,0 +1,6 @@
export type BreakPoint = "xs" | "sm" | "md" | "lg" | "xl";
export type Responsive = {
span?: number;
offset?: number;
};