Build lightweight AI agent admin

This commit is contained in:
Codex
2026-06-08 18:14:59 +08:00
commit e164840f43
2530 changed files with 435693 additions and 0 deletions
@@ -0,0 +1,2 @@
export { default as ZqDesc } from './zq-desc.vue';
export { default as ZqDescItem } from './zq-desc-item.vue';
@@ -0,0 +1,15 @@
<script lang="ts" setup>
defineOptions({ name: 'ZqDescItem' });
withDefaults(
defineProps<{
label: string;
span?: number;
}>(),
{
span: 1,
},
);
</script>
<template></template>
@@ -0,0 +1,156 @@
<script lang="ts" setup>
import type { VNode } from 'vue';
import { Comment, Fragment, computed, useSlots } from 'vue';
export interface ZqDescRowItem {
label: string;
span: number;
vnode: VNode;
}
const props = withDefaults(
defineProps<{
column?: number;
}>(),
{
column: 2,
},
);
const slots = useSlots();
function flattenVNodes(vnodes: VNode[]): VNode[] {
const result: VNode[] = [];
for (const vnode of vnodes) {
if (!vnode || vnode.type === Comment) {
continue;
}
if (vnode.type === Fragment && Array.isArray(vnode.children)) {
result.push(...flattenVNodes(vnode.children as VNode[]));
continue;
}
result.push(vnode);
}
return result;
}
function isDescItem(vnode: VNode) {
const type = vnode.type as { name?: string; __name?: string } | string;
if (typeof type === 'object') {
return type.name === 'ZqDescItem' || type.__name === 'ZqDescItem';
}
return false;
}
const parsedRows = computed(() => {
const vnodes = flattenVNodes(slots.default?.() ?? []);
const items: ZqDescRowItem[] = vnodes
.filter(isDescItem)
.map((vnode) => ({
label: String(vnode.props?.label ?? ''),
span: Math.min(Number(vnode.props?.span ?? 1), props.column),
vnode,
}));
const rows: ZqDescRowItem[][] = [];
let currentRow: ZqDescRowItem[] = [];
let usedColumns = 0;
for (const item of items) {
if (usedColumns + item.span > props.column) {
if (currentRow.length > 0) {
rows.push(currentRow);
}
currentRow = [];
usedColumns = 0;
}
currentRow.push(item);
usedColumns += item.span;
if (usedColumns >= props.column) {
rows.push(currentRow);
currentRow = [];
usedColumns = 0;
}
}
if (currentRow.length > 0) {
rows.push(currentRow);
}
return rows;
});
function getContentColspan(span: number) {
return span * 2 - 1;
}
function renderItemContent(vnode: VNode) {
const render = vnode.children as { default?: () => VNode[] } | undefined;
return render?.default?.() ?? [];
}
</script>
<template>
<div class="zq-desc">
<table class="zq-desc__table">
<tbody>
<tr v-for="(row, rowIndex) in parsedRows" :key="rowIndex">
<template v-for="(item, itemIndex) in row" :key="itemIndex">
<th class="zq-desc__label">{{ item.label }}</th>
<td
class="zq-desc__content"
:colspan="getContentColspan(item.span)"
>
<component :is="() => renderItemContent(item.vnode)" />
</td>
</template>
</tr>
</tbody>
</table>
</div>
</template>
<style scoped>
.zq-desc {
overflow: hidden;
border: 1px solid var(--el-border-color-lighter);
border-radius: 8px;
}
.zq-desc__table {
width: 100%;
border-collapse: collapse;
table-layout: fixed;
}
.zq-desc__label {
width: 120px;
padding: 12px 16px;
font-size: 14px;
font-weight: normal;
color: hsl(var(--muted-foreground));
text-align: left;
vertical-align: top;
background-color: hsl(var(--background-deep));
border-right: 1px solid var(--el-border-color-lighter);
border-bottom: 1px solid var(--el-border-color-lighter);
}
.zq-desc__content {
padding: 12px 16px;
font-size: 14px;
color: var(--el-text-color-primary);
word-break: break-word;
vertical-align: top;
background-color: var(--el-bg-color);
border-bottom: 1px solid var(--el-border-color-lighter);
}
.zq-desc__table tr:last-child .zq-desc__label,
.zq-desc__table tr:last-child .zq-desc__content {
border-bottom: none;
}
</style>