feat(P4): precompute移植+app.js sources双向回填+回归1003/1003+验收体积/文件/data.json
Deploy to GitHub Pages / deploy (push) Failing after 16s
Regression / regression (push) Failing after 15m2s

This commit is contained in:
aurora-admin
2026-09-12 14:18:41 +08:00
parent 16f365a045
commit c65a69c34e
401 changed files with 22932 additions and 140 deletions
+127
View File
@@ -0,0 +1,127 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Aurora Admin · Tabs (H5)</title>
<link rel="stylesheet" href="../.design_library/aurora-admin/colors_and_type.css">
<link rel="stylesheet" href="./Tabs.css">
<style>
.demo { padding: 24px; background: var(--au-color-page-bg); font-family: var(--font-family); }
h1 { font-size: 20px; margin: 0 0 4px; color: var(--au-color-text-title); }
.sub { color: var(--au-color-text-secondary); margin: 0 0 24px; font-size: 12px; }
.demo-block { background: var(--au-color-card-bg); border-radius: 8px; padding: 16px; margin-bottom: 24px; }
.demo-block > .hint { color: var(--au-color-text-secondary); font-size: 12px; margin: 0 0 12px; }
</style>
</head>
<body>
<div class="demo">
<h1>Aurora Admin · Tabs 组件(纯 H5)</h1>
<p class="sub">对齐 PageTabs 规范:线型 / 卡片型 / 胶囊型,支持禁用、关闭、新增</p>
<!-- 线型 -->
<div class="demo-block">
<p class="hint">线型(默认)</p>
<div class="aa-tabs type-line" data-tabs id="tabs-line">
<div class="aa-tabs-nav" role="tablist">
<div class="aa-tabs-tab is-active" role="tab" tabindex="0" data-key="basic">基本信息</div>
<div class="aa-tabs-tab" role="tab" tabindex="0" data-key="safe">安全设置</div>
<div class="aa-tabs-tab" role="tab" tabindex="0" data-key="notify">通知</div>
<div class="aa-tabs-tab is-disabled" role="tab" tabindex="0" data-key="auth">授权</div>
</div>
<div class="aa-tabs-content" data-panels>
<div data-panel="basic">这里是「基本信息」面板内容。</div>
<div data-panel="safe" hidden>这里是「安全设置」面板内容。</div>
<div data-panel="notify" hidden>这里是「通知」面板内容。</div>
<div data-panel="auth" hidden>这里是「授权」面板内容。</div>
</div>
</div>
</div>
<!-- 卡片型(可关闭 + 新增) -->
<div class="demo-block">
<p class="hint">卡片型(可关闭 / 新增)</p>
<div class="aa-tabs type-card" data-tabs id="tabs-card">
<div class="aa-tabs-nav" role="tablist">
<div class="aa-tabs-tab is-active" role="tab" tabindex="0" data-key="t1">订单 <span class="aa-tabs-close" data-close>×</span></div>
<div class="aa-tabs-tab" role="tab" tabindex="0" data-key="t2">退款 <span class="aa-tabs-close" data-close>×</span></div>
<div class="aa-tabs-add" role="button" tabindex="0" aria-label="新增标签页" data-add>+</div>
</div>
<div class="aa-tabs-content" data-panels>
<div data-panel="t1">订单标签页内容。</div>
<div data-panel="t2" hidden>退款标签页内容。</div>
</div>
</div>
</div>
<!-- 胶囊型 -->
<div class="demo-block">
<p class="hint">胶囊型</p>
<div class="aa-tabs type-pill" data-tabs id="tabs-pill">
<div class="aa-tabs-nav" role="tablist">
<div class="aa-tabs-tab is-active" role="tab" tabindex="0" data-key="all">全部</div>
<div class="aa-tabs-tab" role="tab" tabindex="0" data-key="todo">待办</div>
<div class="aa-tabs-tab" role="tab" tabindex="0" data-key="done">已完成</div>
</div>
<div class="aa-tabs-content" data-panels>
<div data-panel="all">全部任务。</div>
<div data-panel="todo" hidden>待办任务。</div>
<div data-panel="done" hidden>已完成任务。</div>
</div>
</div>
</div>
</div>
<script>
function initTabs(root) {
const nav = root.querySelector('.aa-tabs-nav');
const panels = root.querySelector('[data-panels]');
function activate(key) {
root.querySelectorAll('.aa-tabs-tab').forEach(t => {
const on = t.dataset.key === key;
t.classList.toggle('is-active', on);
});
if (panels) panels.querySelectorAll('[data-panel]').forEach(p => {
p.hidden = p.dataset.panel !== key;
});
}
nav.addEventListener('click', (e) => {
const closeBtn = e.target.closest('[data-close]');
const addBtn = e.target.closest('[data-add]');
const tab = e.target.closest('.aa-tabs-tab');
if (closeBtn && tab) {
e.stopPropagation();
const removedKey = tab.dataset.key;
tab.remove();
const first = root.querySelector('.aa-tabs-tab');
if (first) activate(first.dataset.key);
return;
}
if (addBtn) {
const n = root.querySelectorAll('.aa-tabs-tab').length + 1;
const key = 'new' + n;
const el = document.createElement('div');
el.className = 'aa-tabs-tab';
el.dataset.key = key;
el.innerHTML = '新标签 <span class="aa-tabs-close" data-close>×</span>';
addBtn.before(el);
const p = document.createElement('div');
p.dataset.panel = key;
p.textContent = '新标签内容 #' + n;
panels.appendChild(p);
activate(key);
return;
}
if (tab && !tab.classList.contains('is-disabled')) {
activate(tab.dataset.key);
}
});
}
document.querySelectorAll('[data-tabs]').forEach(initTabs);
</script>
</body>
</html>
+83
View File
@@ -0,0 +1,83 @@
import React from 'react';
import './Tabs.css';
/**
* Aurora Admin Tabs(React)
* 对齐 组件7.txt PageTabs 规范:线型 / 卡片型 / 胶囊型,支持禁用、关闭、新增
*
* props:
* - value / defaultValue : 当前激活标签 key(受控 / 非受控)
* - tabs : [{ key, label, disabled?, preventClose? }]
* - type : 'line' | 'card' | 'pill'(默认 'line')
* - closable / addable : Boolean
* - onChange(key) / onTabRemove(key) / onTabAdd()
* - renderPanel(key) / children(key) : 返回当前激活标签的内容
*/
export default function Tabs({
value,
defaultValue,
tabs = [],
type = 'line',
closable = false,
addable = false,
onChange,
onTabRemove,
onTabAdd,
renderPanel
}) {
const current = value !== undefined ? value : defaultValue;
const panelFn = renderPanel || (typeof children === 'function' ? children : null);
const choose = (tab) => {
if (tab.disabled) return;
onChange && onChange(tab.key);
};
return (
<div className={`aa-tabs type-${type}`}>
<div className="aa-tabs-nav">
{tabs.map((tab) => (
<div
key={tab.key}
className={`aa-tabs-tab${current === tab.key ? ' is-active' : ''}${tab.disabled ? ' is-disabled' : ''}`}
onClick={() => choose(tab)}
>
{tab.label}
{closable && !tab.disabled && !tab.preventClose && (
<span
className="aa-tabs-close"
title="关闭"
onClick={(e) => { e.stopPropagation(); onTabRemove && onTabRemove(tab.key); }}
>×</span>
)}
</div>
))}
{addable && (
<div className="aa-tabs-add" title="新增标签" onClick={() => onTabAdd && onTabAdd()}>+</div>
)}
</div>
<div className="aa-tabs-content">
{panelFn ? panelFn(current) : null}
</div>
</div>
);
}
/*
用法示例:
import Tabs from './Tabs';
const tabs = [
{ key: 'basic', label: '基本信息' },
{ key: 'safe', label: '安全设置' },
{ key: 'auth', label: '授权', disabled: true }
];
<Tabs
type="line"
tabs={tabs}
defaultValue="basic"
onChange={setActive}
renderPanel={(key) => <div>当前标签:{key}</div>}
/>
*/
+90
View File
@@ -0,0 +1,90 @@
<template>
<div class="aa-tabs" :class="['type-' + type]">
<div class="aa-tabs-nav">
<div
v-for="tab in tabs" :key="tab.key"
class="aa-tabs-tab"
:class="{ 'is-active': value === tab.key, 'is-disabled': tab.disabled }"
@click="choose(tab)"
>
<slot :name="'label-' + tab.key">{{ tab.label }}</slot>
<span
v-if="closable && !tab.disabled && !tab.preventClose"
class="aa-tabs-close" title="关闭"
@click.stop="remove(tab)"
>×</span>
</div>
<div v-if="addable" class="aa-tabs-add" title="新增标签" @click="$emit('tab-add')">+</div>
</div>
<div class="aa-tabs-content">
<!-- 优先渲染与当前激活标签同名的具名插槽,否则回退到默认插槽 -->
<slot :name="value">
<slot />
</slot>
</div>
</div>
</template>
<script>
export default {
name: 'AaTabs',
props: {
value: { type: [String, Number], default: '' }, // 当前激活标签 key(v-model)
tabs: { type: Array, required: true }, // [{ key, label, disabled?, preventClose? }]
type: { type: String, default: 'line' }, // line | card | pill
closable: { type: Boolean, default: false },
addable: { type: Boolean, default: false }
},
methods: {
choose(tab) {
if (tab.disabled) return;
this.$emit('input', tab.key);
this.$emit('change', tab.key);
},
remove(tab) {
this.$emit('tab-remove', tab.key);
}
}
};
</script>
<!-- 样式对齐 组件7.txt PageTabs 规范 -->
<style>
.aa-tabs { font-family: var(--font-family, -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif); color: #262626; }
.aa-tabs-nav { display: flex; align-items: flex-end; gap: 4px; position: relative; padding: 0 4px; }
.aa-tabs.type-line .aa-tabs-nav { border-bottom: 1px solid #E8ECF1; }
.aa-tabs.type-pill .aa-tabs-nav { background: #F5F7FA; border-radius: 16px; padding: 4px; gap: 4px; }
.aa-tabs-tab { position: relative; box-sizing: border-box; display: inline-flex; align-items: center; gap: 6px;
height: 40px; padding: 0 16px; cursor: pointer; font-size: 14px; line-height: 40px; color: #262626;
border: 1px solid transparent; background: none; transition: color .15s, background .15s, border-color .15s;
user-select: none; white-space: nowrap; }
.aa-tabs-tab:hover:not(.is-active):not(.is-disabled) { color: #2F54EB; }
.aa-tabs.type-line .aa-tabs-tab { border-bottom: 2px solid transparent; margin-bottom: -1px; border-radius: 0; }
.aa-tabs.type-line .aa-tabs-tab.is-active { color: #2F54EB; font-weight: 500; border-bottom-color: #2F54EB; }
.aa-tabs.type-card .aa-tabs-nav { border-bottom: 1px solid #E8ECF1; }
.aa-tabs.type-card .aa-tabs-tab { border: 1px solid #E8ECF1; border-bottom: none; border-radius: 6px 6px 0 0; background: #F5F7FA; }
.aa-tabs.type-card .aa-tabs-tab.is-active { background: #fff; color: #2F54EB; border-color: #E8ECF1; }
.aa-tabs.type-pill .aa-tabs-tab { border: none; border-radius: 14px; height: 32px; line-height: 32px; padding: 0 14px; }
.aa-tabs.type-pill .aa-tabs-tab.is-active { background: #2F54EB; color: #fff; font-weight: 500; }
.aa-tabs-tab.is-disabled { color: #BFBFBF; cursor: not-allowed; }
.aa-tabs.type-card .aa-tabs-tab.is-disabled { background: #F5F5F5; }
.aa-tabs-close { display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px;
margin-left: 4px; border-radius: 50%; font-size: 12px; color: #6E6E6E; }
.aa-tabs-close:hover { background: rgba(0,0,0,0.06); color: #262626; }
.aa-tabs.type-pill .aa-tabs-close { color: rgba(255,255,255,0.85); }
.aa-tabs.type-pill .aa-tabs-close:hover { background: rgba(255,255,255,0.25); color: #fff; }
.aa-tabs-add { display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px;
margin-left: 4px; border: 1px dashed #E8ECF1; border-radius: 6px; color: #6E6E6E; cursor: pointer; font-size: 16px; }
.aa-tabs-add:hover { color: #2F54EB; border-color: #2F54EB; }
.aa-tabs-content { padding: 16px; font-size: 14px; color: #262626; }
.aa-tabs.type-card .aa-tabs-content { border: 1px solid #E8ECF1; border-top: none; border-radius: 0 0 6px 6px; }
</style>
+87
View File
@@ -0,0 +1,87 @@
<template>
<div class="aa-tabs" :class="['type-' + type]">
<div class="aa-tabs-nav">
<div
v-for="tab in tabs" :key="tab.key"
class="aa-tabs-tab"
:class="{ 'is-active': modelValue === tab.key, 'is-disabled': tab.disabled }"
@click="choose(tab)"
>
<slot :name="'label-' + tab.key">{{ tab.label }}</slot>
<span
v-if="closable && !tab.disabled && !tab.preventClose"
class="aa-tabs-close" title="关闭"
@click.stop="remove(tab)"
>×</span>
</div>
<div v-if="addable" class="aa-tabs-add" title="新增标签" @click="$emit('tab-add')">+</div>
</div>
<div class="aa-tabs-content">
<!-- 优先渲染与当前激活标签同名的具名插槽,否则回退到默认插槽 -->
<slot :name="modelValue">
<slot />
</slot>
</div>
</div>
</template>
<script setup>
const props = defineProps({
modelValue: { type: [String, Number], default: '' }, // 当前激活标签 key(v-model)
tabs: { type: Array, required: true }, // [{ key, label, disabled?, preventClose? }]
type: { type: String, default: 'line' }, // line | card | pill
closable: { type: Boolean, default: false },
addable: { type: Boolean, default: false }
});
const emit = defineEmits(['update:modelValue', 'change', 'tab-remove', 'tab-add']);
function choose(tab) {
if (tab.disabled) return;
emit('update:modelValue', tab.key);
emit('change', tab.key);
}
function remove(tab) {
emit('tab-remove', tab.key);
}
</script>
<!-- 样式对齐 组件7.txt PageTabs 规范 -->
<style scoped>
.aa-tabs { font-family: var(--font-family, -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif); color: #262626; }
.aa-tabs-nav { display: flex; align-items: flex-end; gap: 4px; position: relative; padding: 0 4px; }
.aa-tabs.type-line .aa-tabs-nav { border-bottom: 1px solid #E8ECF1; }
.aa-tabs.type-pill .aa-tabs-nav { background: #F5F7FA; border-radius: 16px; padding: 4px; gap: 4px; }
.aa-tabs-tab { position: relative; box-sizing: border-box; display: inline-flex; align-items: center; gap: 6px;
height: 40px; padding: 0 16px; cursor: pointer; font-size: 14px; line-height: 40px; color: #262626;
border: 1px solid transparent; background: none; transition: color .15s, background .15s, border-color .15s;
user-select: none; white-space: nowrap; }
.aa-tabs-tab:hover:not(.is-active):not(.is-disabled) { color: #2F54EB; }
.aa-tabs.type-line .aa-tabs-tab { border-bottom: 2px solid transparent; margin-bottom: -1px; border-radius: 0; }
.aa-tabs.type-line .aa-tabs-tab.is-active { color: #2F54EB; font-weight: 500; border-bottom-color: #2F54EB; }
.aa-tabs.type-card .aa-tabs-nav { border-bottom: 1px solid #E8ECF1; }
.aa-tabs.type-card .aa-tabs-tab { border: 1px solid #E8ECF1; border-bottom: none; border-radius: 6px 6px 0 0; background: #F5F7FA; }
.aa-tabs.type-card .aa-tabs-tab.is-active { background: #fff; color: #2F54EB; border-color: #E8ECF1; }
.aa-tabs.type-pill .aa-tabs-tab { border: none; border-radius: 14px; height: 32px; line-height: 32px; padding: 0 14px; }
.aa-tabs.type-pill .aa-tabs-tab.is-active { background: #2F54EB; color: #fff; font-weight: 500; }
.aa-tabs-tab.is-disabled { color: #BFBFBF; cursor: not-allowed; }
.aa-tabs.type-card .aa-tabs-tab.is-disabled { background: #F5F5F5; }
.aa-tabs-close { display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px;
margin-left: 4px; border-radius: 50%; font-size: 12px; color: #6E6E6E; }
.aa-tabs-close:hover { background: rgba(0,0,0,0.06); color: #262626; }
.aa-tabs.type-pill .aa-tabs-close { color: rgba(255,255,255,0.85); }
.aa-tabs.type-pill .aa-tabs-close:hover { background: rgba(255,255,255,0.25); color: #fff; }
.aa-tabs-add { display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px;
margin-left: 4px; border: 1px dashed #E8ECF1; border-radius: 6px; color: #6E6E6E; cursor: pointer; font-size: 16px; }
.aa-tabs-add:hover { color: #2F54EB; border-color: #2F54EB; }
.aa-tabs-content { padding: 16px; font-size: 14px; color: #262626; }
.aa-tabs.type-card .aa-tabs-content { border: 1px solid #E8ECF1; border-top: none; border-radius: 0 0 6px 6px; }
</style>