卡片列表 ​
AsamCardList 为默认插槽中的卡片提供网格布局。数据加载、分页和选择状态由使用方维护,卡片内容可使用 AsamCard 或 AsamTaskCard。
自动宽度与固定列 ​
示例包含独立的卡片子组件和数据文件。切换布局可比较自动宽度与三列网格,切换等高开关可观察不同长度内容。
自动宽度与固定列
<template><StandaloneDemo /></template>
<script lang="ts" setup>
import * as __external0 from "vue";
import * as __external1 from "@asam-ui/ui";
const __external: any = {"vue": { ...__external0, __esModule: true },
"@asam-ui/ui": { ...__external1, __esModule: true }};
const __modules: any = {
"Demo.vue": (module: any, exports: any, require: any) => {
"use strict";
var __importDefault: any = function (mod?: any) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const vue_1: any = require("vue");
const vue_2: any = require("vue");
const _hoisted_1: any = { class: "demo-controls" };
const vue_3: any = require("vue");
const ui_1: any = require("@asam-ui/ui");
const ItemCard_vue_1: any = __importDefault(require("./ItemCard.vue"));
const cards_js_1: any = require("./cards.js");
exports.default = (0, vue_1.defineComponent)({
...{ name: 'AsamCardListBasicDemo' },
__name: 'Demo',
setup(__props?: any) {
const fixed: any = (0, vue_3.ref)(false);
const equalHeight: any = (0, vue_3.ref)(true);
return (_ctx?: any, _cache?: any) => {
return ((0, vue_2.openBlock)(), (0, vue_2.createElementBlock)(vue_2.Fragment, null, [
(0, vue_2.createElementVNode)("div", _hoisted_1, [
(0, vue_2.createVNode)((0, vue_2.unref)(ui_1.AsamButton), {
type: "default",
onClick: _cache[0] || (_cache[0] = ($event?: any) => (fixed.value = !fixed.value))
}, {
default: (0, vue_2.withCtx)(() => [
(0, vue_2.createTextVNode)((0, vue_2.toDisplayString)(fixed.value ? '改用自动宽度' : '改用三列'), 1 /* TEXT */)
]),
_: 1 /* STABLE */
}),
(0, vue_2.createVNode)((0, vue_2.unref)(ui_1.AsamButton), {
type: "default",
onClick: _cache[1] || (_cache[1] = ($event?: any) => (equalHeight.value = !equalHeight.value))
}, {
default: (0, vue_2.withCtx)(() => [
(0, vue_2.createTextVNode)(" 等高:" + (0, vue_2.toDisplayString)(equalHeight.value ? '开' : '关'), 1 /* TEXT */)
]),
_: 1 /* STABLE */
})
]),
(0, vue_2.createVNode)((0, vue_2.unref)(ui_1.AsamCardList), {
columns: fixed.value ? 3 : null,
"min-card-width": 200,
"max-card-width": 320,
gap: 16,
"row-gap": 24,
align: "stretch",
"equal-height": equalHeight.value
}, {
default: (0, vue_2.withCtx)(() => [
((0, vue_2.openBlock)(true), (0, vue_2.createElementBlock)(vue_2.Fragment, null, (0, vue_2.renderList)((0, vue_2.unref)(cards_js_1.cards), (card?: any) => {
return ((0, vue_2.openBlock)(), (0, vue_2.createBlock)(ItemCard_vue_1.default, {
key: card.id,
title: card.title,
description: card.description,
details: card.details
}, null, 8 /* PROPS */, ["title", "description", "details"]));
}), 128 /* KEYED_FRAGMENT */))
]),
_: 1 /* STABLE */
}, 8 /* PROPS */, ["columns", "equal-height"])
], 64 /* STABLE_FRAGMENT */));
};
}
});
exports.default.__scopeId = "data-v-2b797e16";
},
"ItemCard.vue": (module: any, exports: any, require: any) => {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const vue_1: any = require("vue");
const vue_2: any = require("vue");
const _hoisted_1: any = { class: "example-paragraph" };
const _hoisted_2: any = { key: 0 };
const ui_1: any = require("@asam-ui/ui");
exports.default = (0, vue_1.defineComponent)({
__name: 'ItemCard',
props: {
title: { type: String, required: true },
description: { type: String, required: true },
details: { type: Array, required: true }
},
setup(__props?: any) {
return (_ctx?: any, _cache?: any) => {
return ((0, vue_2.openBlock)(), (0, vue_2.createBlock)((0, vue_2.unref)(ui_1.AsamCard), {
title: __props.title,
shadow: "never"
}, {
default: (0, vue_2.withCtx)(() => [
(0, vue_2.createElementVNode)("p", _hoisted_1, (0, vue_2.toDisplayString)(__props.description), 1 /* TEXT */),
(__props.details.length)
? ((0, vue_2.openBlock)(), (0, vue_2.createElementBlock)("ul", _hoisted_2, [
((0, vue_2.openBlock)(true), (0, vue_2.createElementBlock)(vue_2.Fragment, null, (0, vue_2.renderList)(__props.details, (detail?: any) => {
return ((0, vue_2.openBlock)(), (0, vue_2.createElementBlock)("li", { key: detail }, (0, vue_2.toDisplayString)(detail), 1 /* TEXT */));
}), 128 /* KEYED_FRAGMENT */))
]))
: (0, vue_2.createCommentVNode)("v-if", true)
]),
_: 1 /* STABLE */
}, 8 /* PROPS */, ["title"]));
};
}
});
exports.default.__scopeId = "data-v-f67315ad";
},
"cards.ts": (module: any, exports: any, require: any) => {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.cards = void 0;
exports.cards = [
{
id: 'a',
title: '待核任务',
description: '核对任务参数。',
details: []
},
{
id: 'b',
title: '正在执行',
description: '处理步骤较多时保留自然内容高度;切换等高可比较同一网格行中的卡片。',
details: [
'核对请求编号与申请人。',
'确认目标环境与当前版本。',
'检查提交参数与依赖条件。',
'核对权限与配置来源。',
'确认待办记录的当前状态。',
'检查关联任务的处理结果。',
'记录本次执行的起止时间。',
'补充操作说明与异常信息。',
'保全执行日志和结果文件。',
'完成复核后更新任务状态。'
]
},
{
id: 'c',
title: '已完成',
description: '检查结果已保全。',
details: []
}
];
},
};
const __dependencies: any = {"Demo.vue":{"./ItemCard.vue":"ItemCard.vue","./cards.js":"cards.ts"},"ItemCard.vue":{},"cards.ts":{}};
const __cache: any = Object.create(null);
function __require(id: any): any {
if (__external[id]) return __external[id];
if (__cache[id]) return __cache[id].exports;
const module: any = { exports: {} };
__cache[id] = module;
__modules[id](module, module.exports, (dependency: any) => __require(__dependencies[id][dependency] || dependency));
return module.exports;
}
const StandaloneDemo = __require('Demo.vue').default;
</script>
<style>
.demo-controls[data-v-2b797e16] {
display: flex;
flex-wrap: wrap;
gap: var(--asam-space-sm);
margin-bottom: var(--asam-space-lg);
}
ul[data-v-f67315ad] {
margin: 0;
padding-left: 20px;
}
li + li[data-v-f67315ad] {
margin-top: 8px;
}
p[data-v-f67315ad]:where(.example-paragraph) {
margin-block: 16px;
line-height: 1.8;
overflow-wrap: anywhere;
}
</style>
<template><StandaloneDemo /></template>
<script setup>
import * as __external0 from "vue";
import * as __external1 from "@asam-ui/ui";
const __external = {"vue": { ...__external0, __esModule: true },
"@asam-ui/ui": { ...__external1, __esModule: true }};
const __modules = {
"Demo.vue": (module, exports, require) => {
"use strict";
var __importDefault = function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const vue_1 = require("vue");
const _hoisted_1 = { class: "demo-controls" };
const vue_2 = require("vue");
const ui_1 = require("@asam-ui/ui");
const ItemCard_vue_1 = __importDefault(require("./ItemCard.vue"));
const cards_js_1 = require("./cards.js");
exports.default = Object.assign({ name: 'AsamCardListBasicDemo' }, {
__name: 'Demo',
setup(__props) {
const fixed = (0, vue_2.ref)(false);
const equalHeight = (0, vue_2.ref)(true);
return (_ctx, _cache) => {
return ((0, vue_1.openBlock)(), (0, vue_1.createElementBlock)(vue_1.Fragment, null, [
(0, vue_1.createElementVNode)("div", _hoisted_1, [
(0, vue_1.createVNode)((0, vue_1.unref)(ui_1.AsamButton), {
type: "default",
onClick: _cache[0] || (_cache[0] = $event => (fixed.value = !fixed.value))
}, {
default: (0, vue_1.withCtx)(() => [
(0, vue_1.createTextVNode)((0, vue_1.toDisplayString)(fixed.value ? '改用自动宽度' : '改用三列'), 1 /* TEXT */)
]),
_: 1 /* STABLE */
}),
(0, vue_1.createVNode)((0, vue_1.unref)(ui_1.AsamButton), {
type: "default",
onClick: _cache[1] || (_cache[1] = $event => (equalHeight.value = !equalHeight.value))
}, {
default: (0, vue_1.withCtx)(() => [
(0, vue_1.createTextVNode)(" 等高:" + (0, vue_1.toDisplayString)(equalHeight.value ? '开' : '关'), 1 /* TEXT */)
]),
_: 1 /* STABLE */
})
]),
(0, vue_1.createVNode)((0, vue_1.unref)(ui_1.AsamCardList), {
columns: fixed.value ? 3 : null,
"min-card-width": 200,
"max-card-width": 320,
gap: 16,
"row-gap": 24,
align: "stretch",
"equal-height": equalHeight.value
}, {
default: (0, vue_1.withCtx)(() => [
((0, vue_1.openBlock)(true), (0, vue_1.createElementBlock)(vue_1.Fragment, null, (0, vue_1.renderList)((0, vue_1.unref)(cards_js_1.cards), (card) => {
return ((0, vue_1.openBlock)(), (0, vue_1.createBlock)(ItemCard_vue_1.default, {
key: card.id,
title: card.title,
description: card.description,
details: card.details
}, null, 8 /* PROPS */, ["title", "description", "details"]));
}), 128 /* KEYED_FRAGMENT */))
]),
_: 1 /* STABLE */
}, 8 /* PROPS */, ["columns", "equal-height"])
], 64 /* STABLE_FRAGMENT */));
};
}
});
exports.default.__scopeId = "data-v-196c4408";
},
"ItemCard.vue": (module, exports, require) => {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const vue_1 = require("vue");
const _hoisted_1 = { class: "example-paragraph" };
const _hoisted_2 = { key: 0 };
const ui_1 = require("@asam-ui/ui");
exports.default = {
__name: 'ItemCard',
props: {
title: { type: String, required: true },
description: { type: String, required: true },
details: { type: Array, required: true }
},
setup(__props) {
return (_ctx, _cache) => {
return ((0, vue_1.openBlock)(), (0, vue_1.createBlock)((0, vue_1.unref)(ui_1.AsamCard), {
title: __props.title,
shadow: "never"
}, {
default: (0, vue_1.withCtx)(() => [
(0, vue_1.createElementVNode)("p", _hoisted_1, (0, vue_1.toDisplayString)(__props.description), 1 /* TEXT */),
(__props.details.length)
? ((0, vue_1.openBlock)(), (0, vue_1.createElementBlock)("ul", _hoisted_2, [
((0, vue_1.openBlock)(true), (0, vue_1.createElementBlock)(vue_1.Fragment, null, (0, vue_1.renderList)(__props.details, (detail) => {
return ((0, vue_1.openBlock)(), (0, vue_1.createElementBlock)("li", { key: detail }, (0, vue_1.toDisplayString)(detail), 1 /* TEXT */));
}), 128 /* KEYED_FRAGMENT */))
]))
: (0, vue_1.createCommentVNode)("v-if", true)
]),
_: 1 /* STABLE */
}, 8 /* PROPS */, ["title"]));
};
}
};
exports.default.__scopeId = "data-v-254da99d";
},
"cards.js": (module, exports, require) => {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.cards = void 0;
exports.cards = [
{
id: 'a',
title: '待核任务',
description: '核对任务参数。',
details: []
},
{
id: 'b',
title: '正在执行',
description: '处理步骤较多时保留自然内容高度;切换等高可比较同一网格行中的卡片。',
details: [
'核对请求编号与申请人。',
'确认目标环境与当前版本。',
'检查提交参数与依赖条件。',
'核对权限与配置来源。',
'确认待办记录的当前状态。',
'检查关联任务的处理结果。',
'记录本次执行的起止时间。',
'补充操作说明与异常信息。',
'保全执行日志和结果文件。',
'完成复核后更新任务状态。'
]
},
{
id: 'c',
title: '已完成',
description: '检查结果已保全。',
details: []
}
];
},
};
const __dependencies = {"Demo.vue":{"./ItemCard.vue":"ItemCard.vue","./cards.js":"cards.js"},"ItemCard.vue":{},"cards.js":{}};
const __cache = Object.create(null);
function __require(id) {
if (__external[id]) return __external[id];
if (__cache[id]) return __cache[id].exports;
const module = { exports: {} };
__cache[id] = module;
__modules[id](module, module.exports, (dependency) => __require(__dependencies[id][dependency] || dependency));
return module.exports;
}
const StandaloneDemo = __require('Demo.vue').default;
</script>
<style>
.demo-controls[data-v-196c4408] {
display: flex;
flex-wrap: wrap;
gap: var(--asam-space-sm);
margin-bottom: var(--asam-space-lg);
}
ul[data-v-254da99d] {
margin: 0;
padding-left: 20px;
}
li + li[data-v-254da99d] {
margin-top: 8px;
}
p[data-v-254da99d]:where(.example-paragraph) {
margin-block: 16px;
line-height: 1.8;
overflow-wrap: anywhere;
}
</style>
columns 为正整数时固定列数;省略、传入 null 或无效列数时自动排列。自动模式通过 minCardWidth 和 maxCardWidth 控制卡片宽度,并将单卡宽度限制在容器内;固定列模式按剩余宽度等分。
间距与对齐 ​
宽度和间距接受数字或 CSS 长度字符串,数字按 px 处理。省略时使用卡片布局 tokens,rowGap 省略时沿用 gap。align 接受 start、center、stretch,equalHeight 默认开启。
自动模式中,数字或 px 字符串表示的最小宽度大于最大宽度时,最大宽度按最小宽度处理。其他 CSS 单位不会作跨单位比较,使用方应提供一致且有效的范围。狭窄容器需要避免强制过多固定列。
使用边界 ​
组件仅提供默认插槽,不提供列表事件或实例方法。容器间距和卡片宽度不是控件尺寸;不要将 size 或数据表格的分页参数传给它。
组合用法 ​
在 AsamCard 的完整场景中使用本入口;包含 basic、simple、with-images、shadow。
固定列、自动宽度与异常范围 ​
正整数 columns 使用固定列;null、0、负数和非整数回到自动布局。 完整说明。
固定列、自动宽度与异常范围
<template>
<div class="demo-root-layout">
<AsamButton
type="default"
native-type="button"
@click="mode = (mode + 1) % scenarios.length"
>切换布局输入</AsamButton
>
<output v-bind="domDataAttrs('data-list-mode')">{{ mode }}</output>
<AsamCardList v-bind="{ ...scenarios[mode], ...domDataAttrs('data-list-host') }">
<AsamCard
v-for="(height, index) in [50, 120, 80]"
:key="index"
:title="'卡片 ' + index"
>
<div :style="{ minHeight: height + 'px' }">不等高正文</div>
</AsamCard>
</AsamCardList>
</div>
</template>
<script lang="ts" setup>
import type { HTMLAttributes } from 'vue';
import { ref } from 'vue';
import { AsamButton, AsamCardList, AsamCard } from '@asam-ui/ui';
import type { AsamCardListProps } from '@asam-ui/ui';
defineOptions({ name: 'AsamCardListBoundariesDemo' });
const mode = ref(0);
const scenarios: AsamCardListProps[] = [
{ columns: 3, gap: 12, rowGap: 20, align: 'stretch', equalHeight: true },
{
columns: null,
minCardWidth: 240,
maxCardWidth: '160px',
gap: '1rem',
align: 'center',
equalHeight: false
},
{ columns: 0, minCardWidth: '12rem', maxCardWidth: '20rem', gap: '', align: 'start' },
{ columns: -1, gap: '8px' },
{ columns: 1.5, gap: 0 }
];
type DemoDataAttributes = Record<`data-${string}`, string | number | boolean | null | undefined>;
const domDataAttrs = (
name: `data-${string}`,
value: string | number | boolean | null | undefined = ''
): Pick<HTMLAttributes, 'class'> & DemoDataAttributes => ({
class: 'ui032-data-marker',
[name]: value
});
</script>
<style scoped>
.demo-root-layout {
display: grid;
gap: var(--asam-space-md);
justify-items: start;
}
.demo-root-layout > :is(p, pre, output) {
margin-block: 0;
}
</style>
<template>
<div class="demo-root-layout">
<AsamButton
type="default"
native-type="button"
@click="mode = (mode + 1) % scenarios.length"
>切换布局输入</AsamButton
>
<output v-bind="domDataAttrs('data-list-mode')">{{ mode }}</output>
<AsamCardList v-bind="{ ...scenarios[mode], ...domDataAttrs('data-list-host') }">
<AsamCard
v-for="(height, index) in [50, 120, 80]"
:key="index"
:title="'卡片 ' + index"
>
<div :style="{ minHeight: height + 'px' }">不等高正文</div>
</AsamCard>
</AsamCardList>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { AsamButton, AsamCardList, AsamCard } from '@asam-ui/ui';
defineOptions({ name: 'AsamCardListBoundariesDemo' });
const mode = ref(0);
const scenarios = [
{ columns: 3, gap: 12, rowGap: 20, align: 'stretch', equalHeight: true },
{
columns: null,
minCardWidth: 240,
maxCardWidth: '160px',
gap: '1rem',
align: 'center',
equalHeight: false
},
{ columns: 0, minCardWidth: '12rem', maxCardWidth: '20rem', gap: '', align: 'start' },
{ columns: -1, gap: '8px' },
{ columns: 1.5, gap: 0 }
];
const domDataAttrs = (name, value = '') => ({ class: 'ui032-data-marker', [name]: value });
</script>
<style scoped>
.demo-root-layout {
display: grid;
gap: var(--asam-space-md);
justify-items: start;
}
.demo-root-layout > :is(p, pre, output) {
margin-block: 0;
}
</style>
API
属性 AsamCardListProps
| 属性名 | 说明 | 类型 | 默认值 |
|---|---|---|---|
columns | — | number | null | undefined | null |
minCardWidth | — | AsamCssSize | undefined | — |
maxCardWidth | — | AsamCssSize | undefined | — |
gap | — | AsamCssSize | undefined | — |
rowGap | — | AsamCssSize | undefined | — |
align | 项目的水平对齐方式,与垂直等高行为独立。 | enum"start" | "center" | "stretch"完整类型:AsamCardListAlign | undefined | 'start' |
equalHeight | 将每行卡片拉伸至该行最高卡片;false 保留自然高度。 | boolean | undefined | true |
插槽 AsamCardListSlots
| 属性名 | 说明 | 类型 | 默认值 |
|---|---|---|---|
default | — | (() => unknown) | undefined | — |
类型 AsamCssSize
类型 AsamCardListAlign
用法与契约补充说明 ​
固定列、自动宽度与异常范围 ​
正整数 columns 使用固定列;null、0、负数和非整数回到自动布局。minCardWidth 大于 maxCardWidth 且二者可比较为 px 时,最大宽度钳制到最小宽度。gap 数字转 px,rowGap 省略继承 gap,空白字符串使用 token 默认值。align 控制水平方向对齐;equalHeight 默认开启,在每个网格行内将卡片拉伸到该行最高卡片的高度。关闭时使用纵向起始对齐,各卡片保持自然内容高度及自身最小高度。二者独立;自动模式变为单列时,每行只有一张卡片,不会将不同行强制拉成同高。
关联组件
参数与事件补充说明
参数与事件说明
columns
必填:否;声明来源:ASAM 声明。
默认值与来源
null(源码表达式;ASAM 声明)minCardWidth
必填:否;声明来源:ASAM 声明。
maxCardWidth
必填:否;声明来源:ASAM 声明。
gap
必填:否;声明来源:ASAM 声明。
rowGap
必填:否;声明来源:ASAM 声明。
align
必填:否;声明来源:ASAM 声明。
align 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
枚举:"start" / "center" / "stretch"
默认值与来源
'start'(源码表达式;ASAM 声明)equalHeight
必填:否;声明来源:ASAM 声明。
equalHeight 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
枚举:false / true
默认值与来源
true(源码表达式;ASAM 声明)AsamCardListProps 公开类型视图
import type { AsamCardListProps, AsamCssSize, AsamCardListAlign } from '@asam-ui/ui';
// AsamCardListProps
type PublicContract = {
"columns"?: number | null | undefined;
"minCardWidth"?: AsamCssSize | undefined;
"maxCardWidth"?: AsamCssSize | undefined;
"gap"?: AsamCssSize | undefined;
"rowGap"?: AsamCssSize | undefined;
"align"?: AsamCardListAlign | undefined;
"equalHeight"?: boolean | undefined;
};
参数与事件说明
default
必填:否;声明来源:ASAM 声明。
default 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
事件参数:(): unknown
AsamCardListSlots 公开类型视图
import type { AsamCardListSlots } from '@asam-ui/ui';
// AsamCardListSlots
type PublicContract = {
"default"?: (() => unknown) | undefined;
};
组合契约按分支生效;请同时查看公开类型。
AsamCssSize 公开类型视图
import type { AsamCssSize } from '@asam-ui/ui';
// AsamCssSize
type PublicContract = AsamCssSize;
组合契约按分支生效;请同时查看公开类型。
AsamCardListAlign 公开类型视图
import type { AsamCardListAlign } from '@asam-ui/ui';
// AsamCardListAlign
type PublicContract = AsamCardListAlign;