Typst 学生模板设计指南

每个功能的实现方法与参数详解。适用于课程作业、笔记、实习报告等学生场景。


目录


第一章:模板架构

核心思想

Typst 模板的本质是一个 函数,它接受参数、设置全局样式、返回正文内容。所有样式通过 setshow 规则控制。

// 最简模板结构
#let my-template(
  title: none,
  author: none,
  body,
) = {
  // 1. 全局样式设置
  set text(font: "Noto Serif SC", size: 11pt)
  set page(paper: "a4", numbering: "1")
  set par(justify: true, leading: 0.8em)

  // 2. 封面(可选)
  if title != none {
    align(center + horizon)[
      #text(2em, weight: "bold")[#title]
      #v(1em)
      #author
    ]
    pagebreak()
  }

  // 3. 正文
  body
}

使用方式

#show: my-template.with(
  title: "我的文档",
  author: "张三",
)

= 第一章
这里是正文内容。

参数设计原则

  1. 必填参数放最后body 是必填的,放在参数列表末尾
  2. 可选参数给默认值title: none 表示可选
  3. 复杂参数用字典:字体、边距等用字典传入
  4. 布尔参数用命名参数show-cover: truetrue 更清晰

第二章:字体系统

中英文混排

Typst 的字体设置支持回退机制,当主字体缺少某个字符时自动使用备选字体:

// 字体字典:按用途分类
#let fonts = (
  serif:    "Source Han Serif SC",   // 中文正文(宋体)
  sans:     "Source Han Sans SC",    // 中文标题(黑体)
  mono:     "JetBrains Mono",        // 等宽字体(代码)
  math:     "New Computer Modern Math", // 数学公式
  math-cjk: "Source Han Serif SC",   // 公式中的中文
)

// 设置全局字体
set text(font: (fonts.serif, "Times New Roman"), size: 11pt)

参数说明

不同元素的字体

// 正文:宋体
set text(font: (fonts.serif, "Times New Roman"))

// 标题:黑体
show heading: set text(font: (fonts.sans, "Arial"))

// 代码:等宽字体
show raw: set text(font: fonts.mono)

// 强调:可以换字体
show emph: set text(font: (fonts.serif, "Times New Roman"), weight: "bold")

字体大小层级

// 建立清晰的大小层级
set text(size: 11pt)                              // 正文 11pt
show heading.where(level: 1): set text(size: 1.6em)  // 一级标题
show heading.where(level: 2): set text(size: 1.3em)  // 二级标题
show heading.where(level: 3): set text(size: 1.1em)  // 三级标题

检查可用字体

typst fonts

第三章:行距与段落

行距(leading)

行距是 同一段落内 行与行之间的距离:

// leading: 行距
set par(leading: 0.8em)   // 行距 = 0.8 × 字号

推荐值

效果 适用场景
0.6em 紧凑 代码、表格
0.8em 标准 正文(推荐)
1.0em 宽松 低年级阅读材料

段距(spacing)

段距是 段落与段落 之间的距离:

// spacing: 段距
set par(spacing: 1em)     // 段距 = 1 × 字号

首行缩进 vs 段间距

这是中文学术排版的核心决策,二选一

// 方案 A:传统首行缩进(2 字符)
set par(first-line-indent: 2em)

// 方案 B:现代段间距(无缩进)
set par(first-line-indent: 0pt, spacing: 1.2em)

用参数让用户选择

#let template(
  indent: "spacing",  // "spacing" 或 "traditional"
  body,
) = {
  set par(
    first-line-indent: if indent == "traditional" { 2em } else { 0pt },
    spacing: if indent == "spacing" { 1.2em } else { 0.8em },
  )
  body
}

两端对齐

// justify: 两端对齐(中文推荐开启)
set par(justify: true)

第四章:页面与元数据

页面设置

set page(
  paper: "a4",                    // 纸张大小
  margin: (                       // 边距
    top: 3cm,
    bottom: 2.5cm,
    left: 2.5cm,
    right: 2.5cm,
  ),
  numbering: "1",                 // 页码格式
  number-align: center,           // 页码位置
)

页码格式选项

格式 输出
"1" 1, 2, 3, ...
"I" I, II, III, ...(罗马数字,适合前言)
"一" 一, 二, 三, ...(中文数字)

元数据参数

完整的元数据参数设计:

#let template(
  // === 必填 ===
  body,                    // 正文内容

  // === 基本信息 ===
  title: none,             // 文档标题
  subtitle: none,          // 副标题(可选)
  author: none,            // 作者姓名
  student-id: none,        // 学号
  date: datetime.today(),  // 日期

  // === 课程信息 ===
  course: none,            // 课程名称
  instructor: none,        // 教师姓名
  semester: none,          // 学期(如 "2025 春")

  // === 作业专用 ===
  assignment-num: none,    // 作业编号
  deadline: none,          // 截止日期

  // === 控制开关 ===
  show-cover: false,       // 是否显示封面
  show-outline: false,     // 是否显示目录
  outline-depth: 2,        // 目录深度

  // === 排版选项 ===
  indent: "spacing",       // 缩进方式:"spacing" 或 "traditional"
  font-size: 11pt,         // 正文字号
) = { ... }

元数据访问

在模板内部通过参数名直接访问:

#let template(title: none, author: none, body) = {
  // 在页眉中使用元数据
  set page(
    header: {
      if title != none {
        align(center)[#text(0.8em)[#title]]
      }
    }
  )

  // 在封面中使用元数据
  if title != none {
    align(center + horizon)[
      #text(2em, weight: "bold")[#title]
      #v(1em)
      #author
    ]
    pagebreak()
  }

  body
}

第五章:封面

基础封面

#let cover(title, author, course, semester, date) = {
  // 页码不计入封面
  counter(page).update(0)

  align(center + horizon)[
    #text(2em, weight: "bold")[#title]
    #v(1.5em)
    #text(1.2em)[#course]
    #v(1em)
    #text(1em)[
      #author \
      #semester \
      #date
    ]
  ]

  pagebreak()
}

关键函数说明

函数 作用
counter(page).update(0) 重置页码,封面不计入页码
align(center + horizon) 水平居中 + 垂直居中(Typst 独有优势)
v(1.5em) 垂直间距,em 相对于当前字号

信息更丰富的封面

#let cover(
  title: none,
  subtitle: none,
  author: none,
  student-id: none,
  course: none,
  instructor: none,
  semester: none,
  date: none,
) = {
  counter(page).update(0)

  align(center + horizon)[
    // 标题
    #text(2.2em, weight: "bold")[#title]
    #if subtitle != none [
      #v(0.5em)
      #text(1.4em)[#subtitle]
    ]

    #v(3em)

    // 信息表格
    #set text(size: 1.1em)
    #table(
      columns: 2,
      stroke: none,
      align: (right, left),
      inset: (x: 0pt, y: 6pt),
      [课程], [*#course*],
      [教师], [*#instructor*],
      [学期], [*#semester*],
      [姓名], [*#author*],
      [学号], [*#student-id*],
      [日期], [*#date*],
    )
  ]

  pagebreak()
}

注意tablealign 参数接受数组,(right, left) 表示第一列右对齐、第二列左对齐。


第六章:目录

基础目录

#let outline(depth: 2) = {
  // 标题居中
  show heading.where(level: 1): align(center)

  // 目录内容
  [ #outline(title: [目录], depth: depth) ]

  pagebreak()
}

自定义目录样式

#let custom-outline(depth: 2) = {
  // 目录标题样式
  show outline.title: set text(size: 1.4em, weight: "bold")

  // 目录项样式
  show outline.entry.where(level: 1): it => {
    set text(weight: "bold")
    it
  }

  // 缩进
  show outline.entry: set block(indent: 1.5em)

  outline(title: [目录], depth: depth)
  pagebreak()
}

目录深度

outline(depth: 1)  // 只显示一级标题
outline(depth: 2)  // 显示到二级标题(推荐)
outline(depth: 3)  // 显示到三级标题

第七章:页眉与页脚

基础页眉

set page(
  header: {
    set text(0.8em, fill: luma(120))
    grid(
      columns: (1fr, auto, 1fr),
      align(left, [课程名称]),
      align(center, [文档标题]),
      align(right, [作者]),
    )
    v(0.3em)
    line(length: 100%, stroke: 0.5pt + luma(150))
  },
)

函数说明

函数 作用
grid(columns: (1fr, auto, 1fr)) 三列布局,1fr 表示弹性宽度
line(length: 100%) 水平线,100% 表示占满宽度

首页隐藏页眉

set page(
  header: context {
    // context 关键字允许访问文档状态
    let page-num = counter(page).get().first()
    if page-num > 1 {
      // 从第二页开始显示页眉
      [页眉内容]
    }
  },
)

关键概念context 允许在显示规则中访问文档状态(如页码、计数器)。

页脚

set page(
  footer: {
    align(center)[
      #counter(page).display("1 / 1", both: true)
    ]
  },
)

页码格式

奇偶页不同页眉

set page(
  header: context {
    let page-num = counter(page).get().first()
    if calc.odd(page-num) {
      // 奇数页:页眉在右侧
      align(right)[奇数页页眉]
    } else {
      // 偶数页:页眉在左侧
      align(left)[偶数页页眉]
    }
  },
)

第八章:多级标题

标题样式

// 一级标题:大号、加粗、有下边距
show heading.where(level: 1): it => {
  set text(size: 1.6em, weight: "bold")
  it
  v(0.5em)
}

// 二级标题:中号、加粗
show heading.where(level: 2): it => {
  set text(size: 1.3em, weight: "bold")
  it
  v(0.3em)
}

// 三级标题:小号、加粗
show heading.where(level: 3): it => {
  set text(size: 1.1em, weight: "bold")
  it
}

标题编号

// 阿拉伯数字:1, 1.1, 1.1.1
set heading(numbering: "1.1.1")

// 中文数字:一、,(一),1.
set heading(numbering: "一、(一)1.")

// 混合格式
set heading(numbering: "第 1 章  1.1  1.1.1")

使用 numbly 包自定义

#import "@preview/numbly:0.1.0": numbly

// {1} 表示一级标题的编号,{2} 表示二级,以此类推
set heading(numbering: numbly(
  "{1:第 [1] 章}  ",
  "{2:[1].[2]  ",
  "{3:[1].[2].[3]  ",
))

无编号标题

// 用 = 创建编号标题
= 有编号的标题

// 用 = 创建无编号标题(在 heading 后加 *)
= 无编号的标题*

标题锚点与引用

= 引言 <intro>@intro 所述,本文将讨论...

关键优势<label> 标签可以附加在任何元素上,通过 @label 引用时自动获取编号。


第九章:列表

无序列表

- 项目一
- 项目二
  - 子项目
  - 子项目
- 项目三

样式设置

set list(
  indent: 1.5em,        // 整体缩进
  body-indent: 0.5em,   // 文字与标记的距离
  marker: ([•], [‣], [–]),  // 不同层级的标记
)

有序列表

+ 第一步
+ 第二步
  + 子步骤
+ 第三步

样式设置

set enum(
  indent: 1.5em,
  body-indent: 0.5em,
  numbering: "1.",           // 编号格式
  // numbering: "(1)",       // 带括号
  // numbering: "a.",        // 字母
  // numbering: "I.",        // 罗马数字
)

任务列表(复选框)

// Typst 原生支持任务列表
- [x] 已完成的任务
- [ ] 未完成的任务
- [ ] 另一个未完成的任务

这是 Word 做不到的,非常适合作业清单、待办事项。

术语列表

/ 术语一: 定义内容
/ 术语二: 定义内容

样式设置

set terms(indent: 1.5em, separator: [: ])

第十章:代码块

行内代码

使用 `print()` 函数输出内容。

样式设置

show raw.where(block: false): box.with(
  fill: luma(240),
  inset: (x: 3pt, y: 0pt),
  outset: (y: 3pt),
  radius: 2pt,
)

代码块

```python
def hello():
    print("Hello, World!")
```

样式设置

show raw.where(block: true): block.with(
  width: 100%,
  fill: luma(245),
  inset: (x: 10pt, y: 8pt),
  radius: 4pt,
)

带行号的代码块

#import "@preview/codelst:2.0.0": sourcecode

#sourcecode(numbers: "left")[
  ```python
  def hello():
      print("Hello, World!")

]


**参数说明**:

| 值 | 效果 |
|---|---|
| `numbers: "left"` | 行号在左侧 |
| `numbers: "right"` | 行号在右侧 |
| `numbers: none` | 无行号 |

### 代码块标题

```typst
#figure(
  sourcecode(numbers: "left")[
    ```python
    def hello():
        print("Hello, World!")
    ```
  ],
  caption: [示例代码],
) <code:hello>

第十一章:表格

简单表格

#set table(
  stroke: 0.5pt,
  align: center + horizon,
  inset: (x: 8pt, y: 6pt),
)

#figure(
  table(
    columns: 3,
    [姓名], [学号], [成绩],
    [张三], [001], [95],
    [李四], [002], [88],
  ),
  caption: [成绩表],
) <tab:score>

参数说明

参数 说明
columns: 3 3 列(等宽)
columns: (1fr, 2fr, 1fr) 指定每列宽度比例
stroke: 0.5pt 边框线粗细
align: center + horizon 水平居中 + 垂直居中
inset: (x: 8pt, y: 6pt) 单元格内边距

三线表(学术必备)

#let three-line-table(columns: (), header: (), ..body) = {
  table(
    columns: columns,
    stroke: none,
    // 顶线:粗
    table.hline(stroke: 1.5pt),
    // 表头分隔线:细
    table.hline(stroke: 0.75pt, y: 1),
    // 表头内容
    ..header,
    // 表体内容
    ..body,
    // 底线:粗
    table.hline(stroke: 1.5pt),
  )
}

// 使用示例
#figure(
  three-line-table(
    columns: (1fr, 1fr, 1fr),
    header: ([变量], [类型], [说明]),
    [x], [int], [输入],
    [y], [int], [输出],
  ),
  caption: [三线表示例],
) <tab:three-line>

关键点

函数 作用
table.hline(stroke: 1.5pt) 粗线(顶线、底线)
table.hline(stroke: 0.75pt, y: 1) 细线(表头下方),y: 1 表示在第 1 行之后
stroke: none 先去掉默认边框,再手动添加

跨列表格

#table(
  columns: 3,
  stroke: 0.5pt,
  // 跨列:colspan
  table.cell(colspan: 3, align: center)[合并的单元格],
  [A], [B], [C],
  // 跨行:rowspan
  table.cell(rowspan: 2)[合并], [1], [2],
  [], [3], [4],
)

表格对齐

#table(
  columns: 3,
  stroke: 0.5pt,
  // 每列单独设置对齐
  align: (left, center, right),
  [左对齐], [居中], [右对齐],
)

第十二章:图片与题注

基础图片

#figure(
  image("figure.png", width: 80%),
  caption: [实验结果],
) <fig:result>

参数说明

参数 说明
width: 80% 宽度占容器的 80%
width: 300pt 固定宽度
height: 200pt 固定高度(与 width 二选一)
fit: "contain" 保持比例(默认)

图片引用

@fig:result 所示,实验结果表明...

自动编号:图片编号由 figure 自动管理,无需手动设置。

图片并排

#figure(
  grid(
    columns: 2,
    gutter: 1em,
    image("fig1.png", width: 100%),
    image("fig2.png", width: 100%),
  ),
  caption: [并排图片],
) <fig:side-by-side>

题注位置

// 表格题注在上方(学术规范)
show figure.where(kind: table): set figure.caption(position: top)

// 图片题注在下方(默认)
show figure.where(kind: image): set figure.caption(position: bottom)

第十三章:公式

行内公式

质能方程 $E = m c^2$ 是物理学的基础。

块级公式

$ sum_(i=1)^n i = (n(n+1)) / 2 $ <eq:sum>

公式编号

// 全局设置
set math.equation(numbering: "(1)")

// 或者在 show 规则中设置
show math.equation.where(block: true): set math.equation(numbering: "(1)")

公式引用

@eq:sum 所示,等差数列求和公式为...

多行公式对齐

$
  f(x) &= x^2 + 2x + 1 \
       &= (x + 1)^2
$

关键符号

符号 作用
& 对齐点
\\ 换行
= 对齐在等号处

第十四章:Alert 系统

设计思路

Alert 是类似 GitHub Markdown Alerts 的设计,用于标注重要信息。

基础 Alert 函数

#let alert(
  title: none,           // 标题(可选)
  icon: "💡",            // 图标
  color: rgb("#0066cc"), // 主题色
  fill: rgb("#e6f3ff"),  // 背景色
  body,                  // 内容
) = {
  block(
    width: 100%,
    fill: fill,
    stroke: color,
    radius: 6pt,
    inset: 12pt,
    [
      #if title != none [
        #text(weight: "bold", fill: color)[#icon #title]
        #v(4pt)
      ]
      #body
    ],
  )
}

参数详解

参数 说明
block(width: 100%) 占满容器宽度
fill 背景填充色
stroke 边框颜色
radius 圆角半径
inset 内边距(内容与边框的距离)

预定义 Alert 类型

// Note:蓝色,普通笔记
#let note(title: none, body) = {
  alert(
    title: title,
    icon: "📝",
    color: rgb("#0066cc"),
    fill: rgb("#e6f3ff"),
    body,
  )
}

// Tip:绿色,建议
#let tip(title: none, body) = {
  alert(
    title: title,
    icon: "💡",
    color: rgb("#007a3d"),
    fill: rgb("#e6f9e6"),
    body,
  )
}

// Warning:黄色,警告
#let warning(title: none, body) = {
  alert(
    title: title,
    icon: "⚠️",
    color: rgb("#856404"),
    fill: rgb("#fff3cd"),
    body,
  )
}

// Important:紫色,重要信息
#let important(title: none, body) = {
  alert(
    title: title,
    icon: "",
    color: rgb("#6f42c1"),
    fill: rgb("#f3e8ff"),
    body,
  )
}

// Caution:红色,危险操作
#let caution(title: none, body) = {
  alert(
    title: title,
    icon: "🚨",
    color: rgb("#dc3545"),
    fill: rgb("#ffe6e6"),
    body,
  )
}

使用示例

#note[这是普通笔记内容]

#tip(title: "建议")[优先使用方案 A]

#warning(title: "注意")[此操作不可逆]

#important(title: "重要")[请仔细阅读]

#caution(title: "警告")[删除前请备份]

自定义 Alert 样式

// 左边框样式(替代全边框)
#let alert-left(title: none, icon: "💡", color: rgb("#0066cc"), body) = {
  block(
    width: 100%,
    fill: color.lighten(90%),
    radius: (left: 4pt, right: 0pt),
    inset: (left: 12pt, right: 8pt, y: 10pt),
    stroke: (left: 4pt + color, rest: none),
    [
      #if title != none [
        #text(weight: "bold", fill: color)[#icon #title]
        #v(4pt)
      ]
      #body
    ],
  )
}

第十五章:交叉引用系统

标签(Label)

// 为任何元素添加标签
= 引言 <intro>
$ E = m c^2 $ <eq:einstein>
#figure(image("fig.png"), caption: [图]) <fig:example>
#table(columns: 2, [A], [B]) <tab:example>

引用(Ref)

// 引用时自动获取编号@intro 所述...
公式 @eq:einstein 表明...
如 @fig:example 所示...
见 @tab:example

自定义引用格式

// 修改引用样式
show ref: it => {
  let target = it.target
  // 根据引用类型添加前缀
  if it.element.func() == heading {
    text(fill: blue)[(第 #it 页)]
  } else if it.element.func() == figure {
    text(fill: blue)[图 #it]
  } else {
    it
  }
}

页码引用

// 引用页码@page(ref) 页

第十六章:脚注

基础脚注

这是一个脚注示例。#footnote[这是脚注内容。]

脚注样式

// 修改脚注标记样式
show footnote: set text(size: 0.8em)

// 修改脚注内容样式
show footnote.entry: set text(size: 0.9em)

手动脚注标记

// 自定义脚注标记
#footnote(numbering: "*")[特殊脚注]

第十七章:完整模板代码

以下是整合所有功能的完整模板:

// ============================================================
// student-template.typ - 学生模板
// ============================================================

// 字体配置
#let fonts = (
  serif: "Source Han Serif SC",
  sans: "Source Han Sans SC",
  mono: "JetBrains Mono",
  math: "New Computer Modern Math",
  math-cjk: "Source Han Serif SC",
)

// Alert 函数
#let alert(title: none, icon: "💡", color: rgb("#0066cc"), fill: rgb("#e6f3ff"), body) = {
  block(
    width: 100%,
    fill: fill,
    stroke: color,
    radius: 6pt,
    inset: 12pt,
    [
      #if title != none [
        #text(weight: "bold", fill: color)[#icon #title]
        #v(4pt)
      ]
      #body
    ],
  )
}

// 预定义 Alert 类型
#let note(title: none, body) = alert(title: title, icon: "📝", color: rgb("#0066cc"), fill: rgb("#e6f3ff"), body)
#let tip(title: none, body) = alert(title: title, icon: "💡", color: rgb("#007a3d"), fill: rgb("#e6f9e6"), body)
#let warning(title: none, body) = alert(title: title, icon: "⚠️", color: rgb("#856404"), fill: rgb("#fff3cd"), body)

// 三线表
#let three-line-table(columns: (), header: (), ..body) = {
  table(
    columns: columns,
    stroke: none,
    table.hline(stroke: 1.5pt),
    table.hline(stroke: 0.75pt, y: 1),
    ..header,
    ..body,
    table.hline(stroke: 1.5pt),
  )
}

// 封面
#let cover(
  title: none,
  subtitle: none,
  author: none,
  student-id: none,
  course: none,
  instructor: none,
  semester: none,
  date: none,
) = {
  counter(page).update(0)
  align(center + horizon)[
    #text(2.2em, weight: "bold")[#title]
    #if subtitle != none [
      #v(0.5em)
      #text(1.4em)[#subtitle]
    ]
    #v(3em)
    #set text(size: 1.1em)
    #table(
      columns: 2,
      stroke: none,
      align: (right, left),
      inset: (x: 0pt, y: 6pt),
      [课程], [*#course*],
      [教师], [*#instructor*],
      [学期], [*#semester*],
      [姓名], [*#author*],
      [学号], [*#student-id*],
      [日期], [*#date*],
    )
  ]
  pagebreak()
}

// 目录
#let outline-page(depth: 2) = {
  show heading.where(level: 1): align(center)
  [ #outline(title: [目录], depth: depth) ]
  pagebreak()
}

// 主模板函数
#let student-template(
  // 基本信息
  title: none,
  subtitle: none,
  author: none,
  student-id: none,
  date: datetime.today(),

  // 课程信息
  course: none,
  instructor: none,
  semester: none,

  // 作业专用
  assignment-num: none,
  deadline: none,

  // 控制开关
  show-cover: false,
  show-outline: false,
  outline-depth: 2,

  // 排版选项
  indent: "spacing",
  font-size: 11pt,

  body,
) = {
  // === 全局样式 ===
  set text(font: (fonts.serif, "Times New Roman"), size: font-size)
  set page(
    paper: "a4",
    margin: (top: 3cm, bottom: 2.5cm, left: 2.5cm, right: 2.5cm),
    numbering: "1",
    number-align: center,
    header: context if counter(page).get().first() > 1 {
      set text(0.8em, fill: luma(120))
      grid(
        columns: (1fr, auto, 1fr),
        align(left, [#course]),
        align(center, [#title]),
        align(right, [#author]),
      )
      v(0.3em)
      line(length: 100%, stroke: 0.5pt + luma(150))
    },
  )
  set par(
    justify: true,
    leading: 0.8em,
    spacing: 0.8em,
    first-line-indent: if indent == "traditional" { 2em } else { 0pt },
  )

  // === 标题样式 ===
  show heading.where(level: 1): it => { set text(size: 1.6em, weight: "bold"); it; v(0.5em) }
  show heading.where(level: 2): it => { set text(size: 1.3em, weight: "bold"); it; v(0.3em) }
  show heading.where(level: 3): it => { set text(size: 1.1em, weight: "bold"); it }
  set heading(numbering: "1.1.1")

  // === 列表样式 ===
  set list(indent: 1.5em, body-indent: 0.5em)
  set enum(indent: 1.5em, body-indent: 0.5em)

  // === 代码块样式 ===
  show raw.where(block: false): box.with(fill: luma(240), inset: (x: 3pt, y: 0pt), outset: (y: 3pt), radius: 2pt)
  show raw.where(block: true): block.with(width: 100%, fill: luma(245), inset: (x: 10pt, y: 8pt), radius: 4pt)

  // === 表格样式 ===
  show table: set table(stroke: 0.5pt, align: center + horizon, inset: (x: 8pt, y: 6pt))

  // === 图片题注 ===
  show figure.where(kind: table): set figure.caption(position: top)

  // === 公式编号 ===
  set math.equation(numbering: "(1)")

  // === 封面 ===
  if show-cover {
    cover(
      title: title,
      subtitle: subtitle,
      author: author,
      student-id: student-id,
      course: course,
      instructor: instructor,
      semester: semester,
      date: date,
    )
  }

  // === 目录 ===
  if show-outline {
    outline-page(depth: outline-depth)
  }

  // === 页码重置 ===
  counter(page).update(1)

  // === 正文 ===
  body
}

第十八章:最佳实践

1. 参数命名

// 好:语义清晰
#let template(
  show-cover: false,
  indent: "spacing",
  font-size: 11pt,
)

// 差:含义模糊
#let template(
  cover: false,
  mode: "a",
  size: 11,
)

2. 默认值设计

// 所有参数都应该有合理的默认值
#let template(
  title: none,           // 可选:不显示标题
  author: none,          // 可选:不显示作者
  font-size: 11pt,       // 有默认值
  indent: "spacing",     // 有默认值
  body,                  // 必填:无默认值
)

3. 函数文档

/// 创建学生文档模板。
/// - title (string): 文档标题。
/// - author (string): 作者姓名。
/// - body (content): 正文内容。
/// -> content
#let template(title: none, author: none, body) = { ... }

4. 避免全局状态

// 好:通过参数传递
#let template(font-size: 11pt, body) = {
  set text(size: font-size)
  body
}

// 差:直接修改全局状态
#let template(body) = {
  set text(size: 11pt)  // 硬编码
  body
}

5. 组件化设计

// 把复杂组件提取为独立函数
#let definition(title: none, body) = {
  block(
    width: 100%,
    fill: rgb("#f0f8ff"),
    stroke: rgb("#4682b4"),
    radius: 4pt,
    inset: 10pt,
    [
      #if title != none [
        #text(weight: "bold", fill: rgb("#4682b4"))[定义:#title]
        #v(4pt)
      ]
      #body
    ],
  )
}

6. 测试模板

# 编译模板
typst compile student-template.typ student-template.pdf

# 清理编译缓存
typst clean

# 查看可用字体
typst fonts

附录:Typst vs Word

Typst 的优势

  1. 确定性输出:同样的源码永远得到同样的 PDF
  2. 版本控制友好:纯文本,适合 Git
  3. 自动交叉引用@label 自动更新编号
  4. 函数化模板:一个 show 规则搞定全文样式
  5. 布局原语gridstackcolumns 精确控制
  6. 原生数学公式:不需要插件
  7. 代码高亮:原生支持
  8. 编译速度快:比 LaTeX 快得多

Word 的优势

  1. 所见即所得:适合非技术用户
  2. 协作编辑:多人实时编辑
  3. 插件生态:丰富的插件支持
  4. 学习成本低:大多数人已经会用

选择建议


总结

本文档涵盖了 Typst 学生模板的所有设计要点:

  1. 模板架构:函数化设计,参数清晰
  2. 字体系统:中英文混排,层级分明
  3. 行距与段落:行距、段距、首行缩进
  4. 页面与元数据:页面设置、参数设计
  5. 封面:简洁美观,信息完整
  6. 目录:自动生成,支持深度控制
  7. 页眉页脚:灵活配置,首页隐藏
  8. 多级标题:样式统一,自动编号
  9. 列表:有序、无序、任务列表
  10. 代码块:语法高亮,行号支持
  11. 表格:简单表格、三线表
  12. 图片与题注:自动编号,交叉引用
  13. 公式:自动编号,多行对齐
  14. Alert 系统:多种类型,样式丰富
  15. 交叉引用:标签系统,自动更新
  16. 脚注:灵活配置

掌握这些设计要点,你就能创建一个功能完整、样式美观的 Typst 学生模板。

本站内容除特别声明外,均遵循 CC BY-NC-SA 4.0 协议