同步「扁平简约图示组件」到本分支,并在 TeX Live 2024 下重新编译

内容与 main 一致(详见 main 上的提交说明):
- theme/beamerinnerthemeOUCSlide.sty 新增 9 个纯 TikZ 扁平图示组件
  (latflow latstack latcycle lathub latsteps
    lattime latquad latpyramid latcompare),
  颜色列表写在方括号里,支持多色循环取色,并有误用花括号的明确报错。
- slides.tex 新增 5 页演示;README 新增「图示组件(扁平风格)」一节 + figs/fig16.png。
- 设计基调为圆角色块 + 实心纯色 + 白字 + 细箭头,无渐变、无阴影、无立体。

本分支已用 TeX Live 2024 连编三遍验证:0 错误、0 溢出。
This commit is contained in:
2026-10-13 15:10:38 +08:00
parent 12a127825d
commit 9b009c9bf0
5 changed files with 398 additions and 1 deletions
+269 -1
View File
@@ -299,6 +299,9 @@
{ \ouc_pbg_typeset: }
\ExplSyntaxOff
%% ======================================================================
%文本框textbox
\newtcolorbox{textbox}[1]{
colback=#1!5!white,
@@ -323,4 +326,269 @@
\renewcommand{\figurename}{图}
\renewcommand{\tablename}{表}
\renewcommand{\lstlistingname}{代码}
\renewcommand{\abstractname}{摘要}
\renewcommand{\abstractname}{摘要}
%% ======================================================================
%% 扁平简约图示组件 (flat diagrams)
%% ---------------------------------------------------------------------
%% 设计基调:圆角块 + 实心纯色 + 白字 + 细箭头
%% **无渐变、无阴影、无立体**
%%
%% 所有组件都支持「颜色列表」:写一个颜色则全部同色;
%% 写多个颜色(逗号分隔)则按顺序循环取色。
%% \flatflow{oucblue} % 4 个块全为海大蓝
%% \flatflow{oucblue,oucred,oucpine} % 4 个块循环取这 3 色
%% \flatflow{oucblue,oucred,oucpine,oucdusk,oucamber} % 5 色各不同
%% ======================================================================
\RequirePackage{tikz}
\usetikzlibrary{positioning,arrows.meta,calc,shapes.geometric}
%% ---------------- 防错:颜色参数必须写在 [] 里 ----------------
%% \flatflow 这类命令的「颜色列表」是可选参数,必须写成方括号 [颜色]。
%% 若误写成花括号 {颜色},颜色会被当成第一个条目(静默出错、极难排查),
%% 因此这里检查每个条目是不是模板的颜色名,是则明确报错。
\newcommand{\flcheckitems}[1]{%
\foreach \flci in {#1}{%
%% 注意:\foreach 每轮迭代会开组,内层的 \def 出组即失效,
%% 因此这里必须用 \global\def(否则检测永远不触发)。
\global\def\flhit{0}%
\foreach \flck in {oucblue,ouclightblue,oucred,ouccherry,oucbrown,%
oucgolden,oucsilver,oucpine,oucdusk,oucamber}{%
%% 注意:此处 \ifstrequal 在本机不生效(恒为假),必须用 \ifx
\ifx\flci\flck\global\def\flhit{1}\fi
}%
\ifnum\flhit=1
\PackageError{oucslide}{图示组件的颜色要写在方括号里}{%
命令 \string\flatflow[颜色列表]{条目1, 条目2} 中的颜色是可选参数,%
必须用方括号 [ ],不能用花括号 { }。\MessageBreak
现在「\flci」被当成了条目内容(它看起来是颜色名)。}%
\fi
}%
}
%% ---------------- 辅助:长度 / 计数 / 取色 ----------------
\newlength\flgap
\newlength\flbw
\newcount\flcnt
\def\flcntval{0}
%% 数条目个数,并存成**纯数字宏**(本机 pgfmath 的算式里不能出现计数器)
\newcommand{\flcount}[1]{%
\flcnt=0
\foreach \flzz in {#1}{\global\advance\flcnt by 1}%
\xdef\flcntval{\the\flcnt}%
}
\newcount\flcolcnt
\def\flcoln{0}
\newcommand{\flcolcount}[1]{%
\flcolcnt=0
\foreach \flzz in {#1}{\global\advance\flcolcnt by 1}%
\xdef\flcoln{\the\flcolcnt}%
}
\def\flcur{oucblue}
\def\flmain{oucblue}
%% 第 #2 个块取颜色列表 #1 中的颜色(超出则循环),结果存进 \flcur
\newcommand{\flpick}[2]{%
\pgfmathtruncatemacro{\flidx}{mod(#2-1,\flcoln)+1}%
\foreach \flzz [count=\fljj] in {#1}{%
\ifnum\fljj=\flidx \xdef\flcur{\flzz}\fi
}%
}
%% 取列表第一项作为「主色」(箭头、轴线等用)
\newcommand{\flsetmain}[1]{\flpick{#1}{1}\xdef\flmain{\flcur}}
%% ---------------- 公共样式 ----------------
\tikzset{
flblk/.style={rectangle, rounded corners=2pt, draw=none,
text=white, align=center, inner sep=4pt, font=\small},
flblkl/.style={rectangle, rounded corners=2pt, draw=none,
align=center, inner sep=4pt, font=\footnotesize},
flarr/.style={line width=1pt, -{Stealth[length=1.7mm,width=1.4mm]}}
}
%% ================== 1. 横向箭头流程 ==================
%% \flatflow[颜色列表]{条目1, 条目2, ...}
\NewDocumentCommand \flatflow { O{oucblue} m }{{%
\flcheckitems{#2}\flcount{#2}\flcolcount{#1}\flsetmain{#1}%
\setlength{\flgap}{16pt}%
\pgfmathsetlength{\flbw}{(0.99\textwidth-(\flcntval-1)*\flgap)/\flcntval}%
\begin{tikzpicture}
\foreach \flx [count=\fli] in {#2} {
\flpick{#1}{\fli}%
\pgfmathtruncatemacro{\flprev}{\fli-1}
\ifnum\fli=1
\node[flblk, fill=\flcur, minimum height=20pt,
text width=\dimexpr\flbw-8pt\relax] (fn1) at (0,0) {\flx};
\else
\node[flblk, fill=\flcur, minimum height=20pt,
text width=\dimexpr\flbw-8pt\relax,
right=\flgap of fn\flprev] (fn\fli) {\flx};
\fi
}%
\foreach \flx [count=\fli] in {#2} {
\ifnum\fli>1
\pgfmathtruncatemacro{\flprev}{\fli-1}
\draw[flarr, draw=\flmain!70!black] (fn\flprev.east) -- (fn\fli.west);
\fi
}%
\end{tikzpicture}%
}}
%% ================== 2. 纵向流程 ==================
\NewDocumentCommand \flatstack { O{oucblue} m }{{%
\flcheckitems{#2}\flcount{#2}\flcolcount{#1}\flsetmain{#1}%
\begin{tikzpicture}
\foreach \flx [count=\fli] in {#2} {
\flpick{#1}{\fli}%
\pgfmathtruncatemacro{\flprev}{\fli-1}
\ifnum\fli=1
\node[flblk, fill=\flcur, minimum height=18pt,
minimum width=0.7\textwidth] (fn1) at (0,0) {\flx};
\else
\node[flblk, fill=\flcur, minimum height=18pt,
minimum width=0.7\textwidth, below=8pt of fn\flprev] (fn\fli) {\flx};
\fi
}%
\foreach \flx [count=\fli] in {#2} {
\ifnum\fli>1
\pgfmathtruncatemacro{\flprev}{\fli-1}
\draw[flarr, draw=\flmain!70!black] (fn\flprev.south) -- (fn\fli.north);
\fi
}%
\end{tikzpicture}%
}}
%% ================== 3. 循环图 ==================
\NewDocumentCommand \flatcycle { O{oucblue} m }{{%
\flcheckitems{#2}\flcount{#2}\flcolcount{#1}\flsetmain{#1}%
\begin{tikzpicture}
\foreach \flx [count=\fli] in {#2} {
\flpick{#1}{\fli}%
\pgfmathsetmacro{\flang}{90-360/\flcntval*(\fli-1)}
\node[circle, fill=\flcur, text=white, align=center, inner sep=1pt,
minimum size=32pt, font=\footnotesize] (cn\fli) at (\flang:34pt) {\flx};
}%
\foreach \flx [count=\fli] in {#2} {
\pgfmathtruncatemacro{\flnext}{mod(\fli,\flcntval)+1}
\draw[flarr, draw=\flmain!50!black] (cn\fli) to[bend left=20] (cn\flnext);
}%
\end{tikzpicture}%
}}
%% ================== 4. 中心辐射 ==================
%% \flathub[颜色列表]{中心文字}{环绕1, 环绕2, ...}
\NewDocumentCommand \flathub { O{oucblue} m m }{{%
\flcheckitems{#3}\flcount{#3}\flcolcount{#1}\flsetmain{#1}%
\begin{tikzpicture}
\foreach \flx [count=\fli] in {#3} {
\flpick{#1}{\fli+1}% % 环绕从颜色列表第 2 项起取色
\pgfmathsetmacro{\flang}{90-360/\flcntval*(\fli-1)}
\node[rectangle, rounded corners=2pt, draw=\flcur!30!white, line width=0.6pt,
fill=\flcur!7!white, text=\flcur!55!black, align=center, inner sep=3pt,
font=\footnotesize, minimum height=17pt] (ln\fli) at (\flang:3.1cm) {\flx};
\draw[draw=\flcur!40!white, line width=0.7pt] (0,0) -- (ln\fli);
}%
\flsetmain{#1}%
\node[circle, fill=\flmain, text=white, align=center, inner sep=2pt,
minimum size=40pt, font=\small\bfseries] at (0,0) {#2};
\end{tikzpicture}%
}}
%% ================== 5. 阶梯递进 ==================
\NewDocumentCommand \flatsteps { O{oucblue} m }{{%
\flcheckitems{#2}\flcolcount{#1}%
\begin{tikzpicture}
\foreach \flx [count=\fli] in {#2} {
\flpick{#1}{\fli}%
\pgfmathsetlengthmacro{\flsx}{(\fli-1)*2.6cm}
\pgfmathsetlengthmacro{\flsy}{(\fli-1)*13pt}
\node[flblk, fill=\flcur, minimum height=16pt, minimum width=2.4cm,
anchor=south west] at (\flsx,\flsy) {\flx};
}%
\end{tikzpicture}%
}}
%% ================== 6. 时间轴 ==================
%% \flattime[颜色列表]{年份列表}{事件列表}
\NewDocumentCommand \flattime { O{oucblue} m m }{{%
\flcheckitems{#2}\flcheckitems{#3}\flcount{#2}\flcolcount{#1}\flsetmain{#1}%
\begin{tikzpicture}
\pgfmathsetlengthmacro{\flstep}{0.9\textwidth/\flcntval}%
\draw[line width=1.8pt, draw=\flmain,
-{Stealth[length=2mm,width=1.6mm]}] (0,0) -- (0.9\textwidth,0);
\foreach \fly [count=\fli] in {#2} {
\flpick{#1}{\fli}%
\pgfmathsetlengthmacro{\flxx}{(\fli-0.5)*\flstep}
\node[circle, fill=\flcur, minimum size=6pt, inner sep=0pt] at (\flxx,0) {};
\node[rectangle, fill=\flcur, text=white, inner sep=2.5pt,
font=\footnotesize\bfseries, rounded corners=1pt]
at (\flxx,0.22cm) {\fly};
}%
\foreach \fle [count=\fli] in {#3} {
\flpick{#1}{\fli}%
\pgfmathsetlengthmacro{\flxx}{(\fli-0.5)*\flstep}
\ifodd\fli
\node[flblkl, fill=\flcur!7!white, draw=\flcur!25!white, line width=0.5pt,
text=\flcur!55!black, text width=2.3cm, anchor=south]
at (\flxx,0.45cm) {\fle};
\else
\node[flblkl, fill=\flcur!7!white, draw=\flcur!25!white, line width=0.5pt,
text=\flcur!55!black, text width=2.3cm, anchor=north]
at (\flxx,-0.22cm) {\fle};
\fi
}%
\end{tikzpicture}%
}}
%% ================== 7. 四象限 ==================
%% \flatquad[颜色列表]{左上}{右上}{左下}{右下}
\NewDocumentCommand \flatquad { O{oucblue} m m m m }{{%
\flcolcount{#1}%
\flpick{#1}{1}\xdef\flcA{\flcur}%
\flpick{#1}{2}\xdef\flcB{\flcur}%
\flpick{#1}{3}\xdef\flcC{\flcur}%
\flpick{#1}{4}\xdef\flcD{\flcur}%
\begin{tikzpicture}
\node[flblkl, fill=\flcA!8!white, text=\flcA!55!black, text width=4.5cm,
minimum height=34pt] (q1) at (0,0) {#2};
\node[flblkl, fill=\flcB!14!white, text=\flcB!55!black, text width=4.5cm,
minimum height=34pt, right=3pt of q1] (q2) {#3};
\node[flblkl, fill=\flcC!14!white, text=\flcC!55!black, text width=4.5cm,
minimum height=34pt, below=3pt of q1] (q3) {#4};
\node[flblkl, fill=\flcD!8!white, text=\flcD!55!black, text width=4.5cm,
minimum height=34pt, right=3pt of q3] (q4) {#5};
\end{tikzpicture}%
}}
%% ================== 8. 金字塔 ==================
%% \flatpyramid[颜色列表]{顶层, 第二层, ...} (按书写顺序自上而下)
\NewDocumentCommand \flatpyramid { O{oucblue} m }{{%
\flcheckitems{#2}\flcolcount{#1}%
\begin{tikzpicture}
\foreach \flx [count=\fli] in {#2} {
\flpick{#1}{\fli}%
\pgfmathsetlengthmacro{\flw}{(1.6+(\fli-1)*1.6)*1cm}
\pgfmathsetlengthmacro{\flyy}{-(\fli-1)*15.5pt}
\node[trapezium, trapezium left angle=76, trapezium right angle=104,
draw=none, fill=\flcur, text=white, align=center,
inner sep=1pt, minimum height=15pt, minimum width=\flw]
at (0,\flyy) {\flx};
}%
\end{tikzpicture}%
}}
%% ================== 9. 左右对比 ==================
%% \flatcompare[左色][右色]{左标题}{左内容}{右标题}{右内容}
\NewDocumentCommand \flatcompare { O{oucblue} O{oucamber} m m m m }{{%
\begin{tikzpicture}
\node[flblk, fill=#1, minimum width=4.7cm, minimum height=19pt] (h1) at (0,0) {#3};
\node[flblk, fill=#2, minimum width=4.7cm, minimum height=19pt,
right=8pt of h1] (h2) {#5};
\node[flblkl, fill=#1!7!white, text=#1!55!black, text width=4.5cm,
minimum height=54pt, below=3pt of h1] {#4};
\node[flblkl, fill=#2!7!white, text=#2!55!black, text width=4.5cm,
minimum height=54pt, below=3pt of h2] {#6};
\end{tikzpicture}%
}}