feat(P4): precompute移植+app.js sources双向回填+回归1003/1003+验收体积/文件/data.json
This commit is contained in:
@@ -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>
|
||||
@@ -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>}
|
||||
/>
|
||||
*/
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user