跳转至

开发工具

mkdocs快速搭建帮助文档(以气泡图为例)

插件安装

Bash
1
2
3
pip install mkdocs-material

mkdocs new .
Bash
1
2
3
4
.
├─ docs/
  └─ index.md
└─ mkdocs.yml
graph TD
    Root[项目根目录]
    Root --- Docs[docs/ 文件夹]
    Root --- MkdocsYml[mkdocs.yml 文件]
    Docs --- IndexMd[index.md 文件]

创建文件夹目录

例如E:\Gitee\helperdoc\BubbleDiagram

Bash
mkdocs new .

image-20250923103807473

Besides, further assets may also be put in the overrides directory:

overrides
Bash
    .
    ├─ .icons/                      # Bundled icon sets
    ├─ assets/
      ├─ images/                   # Images and icons
      ├─ javascripts/              # JavaScript files
      └─ stylesheets/              # Style sheets
    ├─ partials/
      ├─ integrations/             # Third-party integrations
        ├─ analytics/             # Analytics integrations
        └─ analytics.html         # Analytics setup
      ├─ languages/                # Translation languages
      ├─ actions.html              # Actions
      ├─ alternate.html            # Site language selector
      ├─ comments.html             # Comment system (empty by default)
      ├─ consent.html              # Consent
      ├─ content.html              # Page content
      ├─ copyright.html            # Copyright and theme information
      ├─ feedback.html             # Was this page helpful?
      ├─ footer.html               # Footer bar
      ├─ header.html               # Header bar
      ├─ icons.html                # Custom icons
      ├─ language.html             # Translation setup
      ├─ logo.html                 # Logo in header and sidebar
      ├─ nav.html                  # Main navigation
      ├─ nav-item.html             # Main navigation item
      ├─ pagination.html           # Pagination (used for blog)
      ├─ palette.html              # Color palette toggle
      ├─ post.html                 # Blog post excerpt
      ├─ progress.html             # Progress indicator
      ├─ search.html               # Search interface
      ├─ social.html               # Social links
      ├─ source.html               # Repository information
      ├─ source-file.html          # Source file information
      ├─ tabs.html                 # Tabs navigation
      ├─ tabs-item.html            # Tabs navigation item
      ├─ tags.html                 # Tags
      ├─ toc.html                  # Table of contents
      ├─ toc-item.html             # Table of contents item
      └─ top.html                  # Back-to-top button
    ├─ 404.html                     # 404 error page
    ├─ base.html                    # Base template
    ├─ blog.html                    # Blog index page
    ├─ blog-archive.html            # Blog archive index page
    ├─ blog-category.html           # Blog category index page
    ├─ blog-post.html               # Blog post page
    └─ main.html                    # Default page

修改mkdocs.yml文件

Success
YAML
  site_name: 我的帮助文档
  site_description: 关于mkdocs-material支持的markdown语法,包括传统语法和扩展语法
  site_author: JerryMa
  site_url: http://127.0.0.1:8000
  theme:
    name: material
    palette:
      # Toggle light mode
      - scheme: default
        primary: Blue Grey
        accent: Pink
        toggle:
          icon: material/toggle-switch
          name: 切换到明亮模式
      # Toggle dark mode
      - scheme: slate
        primary: blue
        accent: amber
        toggle:
          icon: material/toggle-switch-off-outline
          name: 切换到暗黑模式
    features:
      - announce.dismiss
      - content.tabs.link
      - content.tooltips
      - content.code.copy #代码复制
      - content.code.select
      - content.code.annotate   
      - content.footnote.tooltips
      - header.autohide
      - navigation.footer
      - navigation.indexes
      - navigation.instant
      - navigation.instant.prefetch
      - navigation.instant.progress
      - navigation.prune
      - navigation.sections
      - navigation.tabs
      - navigation.tabs.sticky
      - navigation.top # 返回顶部的按钮 在上滑时出现  
      - navigation.tracking
      - search.highlight # 搜索出的文章关键词加入高亮
      - search.share #搜索分享按钮   
      - search.suggest # 搜索输入一些字母时推荐补全整个单词
      - toc.follow
      - toc.integrate
    language: 'zh'
  plugins:
    - offline
    - search:
        lang: 
          - zh
          - en
        separator: '[\s\-\.]+'
    - minify:
        minify_html: true
        minify_js: true
        minify_css: true
        htmlmin_opts:
          remove_comments: true
        css_files:
          - stylesheets/extra.css
    - glightbox:
        touchNavigation: true
        loop: false
        effect: zoom
        slide_effect: slide
        width: 100%
        height: auto
        zoomable: true
        draggable: true
        skip_classes:
          - custom-skip-class-name
        auto_caption: false
        caption_position: bottom
  extra:
    social:
      - icon: fontawesome/brands/github #联系方式图标 : https://fontawesome.com/ 去这里找图标
        link: https://github.com/mazaiguo
        name: JerryMa on Github
      - icon: fontawesome/brands/gitlab
        link: https://gitlab.zwsoft.cn/mazaiguo
      - icon: fontawesome/regular/envelope
        link: mailto:mazaiguo@126.com
        name: Email
    analytics:
      feedback:
        title: 这个页面对您有帮助吗?
        ratings:
          - icon: material/emoticon-happy-outline
            name: 有帮助
            data: 1
            note: >-
              感谢您的反馈!
          - icon: material/emoticon-sad-outline
            name: 可以改进
            data: 0
            note: >-
              感谢您的反馈!请帮助我们改进这个页面,
              <a href="https://github.com/mazaiguo/mazaiguo.github.io/issues/new/?title=[Feedback]+{title}+-+{url}" target="_blank" rel="noopener">告诉我们需要改进的地方</a>。
    generator: false #是否删除页脚显示"使用 MkDocs 材料制造"
  extra_javascript:
    - javascripts/katex.js
    - https://unpkg.com/katex@0/dist/katex.min.js
    - https://unpkg.com/katex@0/dist/contrib/auto-render.min.js
    #- https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/highlight.min.js
    #- javascripts/config.js
  extra_css:
    #- https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/default.min.css
    - stylesheets/extra.css  
    - https://unpkg.com/katex@0/dist/katex.min.css
  markdown_extensions:
    - abbr
    - admonition
    - attr_list
    - def_list
    - footnotes
    - md_in_html
    - meta
    - tables
    - toc:
        permalink: true
        title: 目录
    - pymdownx.arithmatex:
        generic: true
    - pymdownx.betterem:
        smart_enable: all
    - pymdownx.caret
    - pymdownx.details
    - pymdownx.emoji:
        emoji_generator: !!python/name:material.extensions.emoji.to_svg
        emoji_index: !!python/name:material.extensions.emoji.twemoji
    - pymdownx.highlight:
        anchor_linenums: true
        line_spans: __span
        pygments_lang_class: true
        linenums: true
        linenums_style: pymdownx.inline
        auto_title: true # 显示编程语言名称
        use_pygments: true
    - pymdownx.inlinehilite
    - pymdownx.keys
    - pymdownx.magiclink:
        normalize_issue_symbols: true
        repo_url_shorthand: true
        user: mazaiguo
        repo: helpdoc
    - pymdownx.mark
    - pymdownx.smartsymbols
    - pymdownx.snippets:
        check_paths: true
    - pymdownx.superfences:
        custom_fences:
          - name: mermaid
            class: mermaid
            format: !!python/name:pymdownx.superfences.fence_code_format
    - pymdownx.tabbed:
        alternate_style: true
        combine_header_slug: true
        slugify: !!python/object/apply:pymdownx.slugs.slugify
          kwds:
            case: lower
    - pymdownx.tasklist:
        custom_checkbox: true
    - pymdownx.tilde
    - pymdownx.critic
  copyright: Copyright &copy; 2016 - present JerryMa

image-20250923154538869

这几个是MKDOCS内置使用的,我们建立文件夹时不要与这些名字冲突了

增加一些配置文件

katex-docsjavascriptskatexjs

Note
JavaScript
   document$.subscribe(({ body }) => { 
     renderMathInElement(body, {
       delimiters: [
         { left: "$$",  right: "$$",  display: true },
         { left: "$",   right: "$",   display: false },
         { left: "\\(", right: "\\)", display: false },
         { left: "\\[", right: "\\]", display: true }
       ],
     })
   })

docs/javascripts/tablesort.js

Note
JavaScript
1
2
3
4
5
6
document$.subscribe(function() {
  var tables = document.querySelectorAll("article table:not([class])")
  tables.forEach(function(table) {
    new Tablesort(table)
  })
})

[docs\stylesheets\extra.css]

Warning
CSS
    :root > * {
      --md-code-hl-string-color: #0ff1ce;
      --md-code-hl-number-color: #ae81ff;
      --md-code-hl-special-color: #a846b9;
      --md-code-hl-function-color: #66d9ef;
      --md-code-hl-constant-color: #f92672;
      --md-code-hl-keyword-color: #f92672;
      --md-code-hl-string-color: #e6db74;
      --md-code-hl-name-color: #feffff;
      --md-code-hl-operator-color: #f92672;
      --md-code-hl-punctuation-color: #ffffff;
      --md-code-hl-comment-color: #757575;
      --md-code-hl-generic-color: #af82fc;
      --md-code-hl-variable-color: #f92672;
      --md-code-fg-color: #ffffff;
      --md-code-bg-color: #282c34;
      --md-code-hl-color: #ffff7f;
      --md-default-fg-color--light: #75715f;
    }
    .md-typeset p > code {
      background-color: #ffffff;
      color: #eb245c;
    }
    .md-typeset li code {
      background-color: #ffffff;
      color: #eb245c;
    }
    /*代码块头部图标 start*/
    .highlight span.filename pre:before {
      content: "";
      display: block;
      background: url(../assets/images/codeHeader.png);
      height: 30px;
      background-size: 40px;
      background-repeat: no-repeat;
      background-color: #212121;
      background-position: 10px 10px;
    }
    /*代码块头部图标 end*/

    .highlighttable .code pre > code {
      color: #c0c3c1;
      font-family: "Inconsolata", consolas, "PingFang SC", "Microsoft YaHei",
        monospace;
      background-color: #212121;
      font-size: 15px;
      white-space: pre;
      line-height: 1.5;
      -moz-tab-size: 4;
      -o-tab-size: 4;
      tab-size: 4;
    }
    .highlight span.filename {
      color: white;
    }

    /* 基础容器样式:确保导航项布局正常 */
    .md-nav__list {
      list-style: none;
      padding: 0;
      margin: 0;
      width: 280px; /* 适配侧边导航宽度,可按需调整 */
    }
    .md-nav__item {
      margin: 4px 0;
    }
    .md-nav__link {
      display: block;
      padding: 8px 12px;
      border-radius: 6px;
      text-decoration: none;
      color: #333; /* 默认文本色 */
      transition: all 0.3s ease; /* 统一过渡动画,确保流畅性 */
    }

    /* 核心:.md-ellipsis 交互特效 */
    .md-ellipsis {
      position: relative;
      z-index: 1;
      transition: color 0.3s ease;
    }
    /* 鼠标悬浮(hover)效果:文本变色 + 底部渐变下划线 */
    .md-nav__link:hover .md-ellipsis {
      color: #165dff; /* 悬浮文本主色(可替换为品牌色) */
    }
    .md-nav__link:hover .md-ellipsis::after {
      content: "";
      position: absolute;
      left: 0;
      bottom: -2px;
      width: 100%;
      height: 2px;
      background: linear-gradient(90deg, #165dff, #4080ff); /* 渐变下划线 */
      border-radius: 1px;
      transform: scaleX(1);
      transform-origin: left center;
      transition: transform 0.3s ease;
    }
    /* 初始状态:下划线收缩至0,hover时展开 */
    .md-ellipsis::after {
      content: "";
      position: absolute;
      left: 0;
      bottom: -2px;
      width: 100%;
      height: 2px;
      background: linear-gradient(90deg, #165dff, #4080ff);
      border-radius: 1px;
      transform: scaleX(0);
      transform-origin: left center;
      transition: transform 0.3s ease;
    }

    /* 鼠标点击(active)效果:文本加深 + 背景压暗 */
    .md-nav__link:active .md-ellipsis {
      color: #0e42d2; /* 点击文本加深色 */
      font-weight: 500; /* 点击时文本轻微加粗 */
    }
    .md-nav__link:active {
      background-color: rgba(22, 93, 255, 0.1); /* 点击背景色(淡蓝压暗) */
      transform: translateY(1px); /* 轻微下沉,模拟物理按压感 */
      transition: transform 0.1s ease, background-color 0.1s ease;
    }

    /* 激活状态(.md-nav__link--active):区分当前选中项 */
    .md-nav__link--active .md-ellipsis {
      color: #165dff;
      font-weight: 500;
    }
    .md-nav__link--active .md-ellipsis::after {
      transform: scaleX(1); /* 激活项默认显示下划线 */
    }
    .md-nav__link--active {
      background-color: rgba(22, 93, 255, 0.05); /* 激活项背景色 */
    }

    /* 标签云样式 */
    .tag-cloud {
      margin: 1rem 0;
      line-height: 2;
    }

    .tag-cloud .tag {
      display: inline-block;
      padding: 0.25rem 0.5rem;
      margin: 0.125rem;
      background-color: var(--md-primary-fg-color--light);
      color: var(--md-primary-bg-color);
      border-radius: 0.25rem;
      text-decoration: none;
      font-size: 0.875rem;
      font-weight: 500;
      transition: all 0.2s ease;
    }

    .tag-cloud .tag:hover {
      background-color: var(--md-primary-fg-color);
      transform: translateY(-1px);
      box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
    }

    /* 暗色主题下的标签样式 */
    [data-md-color-scheme="slate"] .tag-cloud .tag {
      background-color: var(--md-accent-fg-color);
      color: var(--md-default-bg-color);
    }

    [data-md-color-scheme="slate"] .tag-cloud .tag:hover {
      background-color: var(--md-accent-fg-color--transparent);
    }

    /* 博客卡片样式 */
    .blog-card {
      background: var(--md-default-bg-color);
      border: 1px solid var(--md-default-fg-color--lightest);
      border-radius: 0.5rem;
      padding: 1.5rem;
      margin: 1rem 0;
      transition: all 0.2s ease;
    }

    .blog-card:hover {
      box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
      transform: translateY(-2px);
    }

    /* 统计数字样式 */
    .stats-grid {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
      gap: 1rem;
      margin: 2rem 0;
    }

    .stat-item {
      text-align: center;
      padding: 1rem;
      background: var(--md-default-bg-color);
      border: 1px solid var(--md-default-fg-color--lightest);
      border-radius: 0.5rem;
    }

    .stat-number {
      font-size: 2rem;
      font-weight: bold;
      color: var(--md-primary-fg-color);
      display: block;
    }

    .stat-label {
      font-size: 0.875rem;
      color: var(--md-default-fg-color--light);
      margin-top: 0.5rem;
    }

    /* 分类页面样式 */
    .category-section {
      margin: 2rem 0;
    }

    .category-title {
      color: var(--md-primary-fg-color);
      border-bottom: 2px solid var(--md-primary-fg-color--light);
      padding-bottom: 0.5rem;
      margin-bottom: 1rem;
    }

    .category-list {
      list-style: none;
      padding: 0;
    }

    .category-list li {
      margin: 0.5rem 0;
      padding-left: 1rem;
      border-left: 3px solid var(--md-accent-fg-color);
    }

    .category-list a {
      text-decoration: none;
      color: var(--md-default-fg-color);
      font-weight: 500;
    }

    .category-list a:hover {
      color: var(--md-primary-fg-color);
    }

    /* 响应式设计 */
    @media screen and (max-width: 768px) {
      .tag-cloud .tag {
        font-size: 0.75rem;
        padding: 0.2rem 0.4rem;
      }

      .stats-grid {
        grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
      }

      .stat-number {
        font-size: 1.5rem;
      }
    }

    /* 代码块优化 */
    .highlight pre {
      border-radius: 0.5rem;
      box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
    }

    /* 文章元信息样式 */
    .article-meta {
      display: flex;
      flex-wrap: wrap;
      gap: 1rem;
      margin: 1rem 0;
      padding: 1rem;
      background: var(--md-code-bg-color);
      border-radius: 0.5rem;
      font-size: 0.875rem;
      color: var(--md-default-fg-color--light);
    }

    .article-meta .meta-item {
      display: flex;
      align-items: center;
      gap: 0.25rem;
    }

    .article-meta .meta-icon {
      width: 1rem;
      height: 1rem;
      opacity: 0.7;
    }

    /* 博客文章列表样式优化 */
    .md-content .md-typeset .md-post {
      margin-bottom: 2rem;
      padding-bottom: 2rem;
      border-bottom: 1px solid var(--md-default-fg-color--lightest);
    }

    .md-content .md-typeset .md-post:last-child {
      border-bottom: none;
    }

    /* 提高阅读体验 */
    .md-typeset h1,
    .md-typeset h2,
    .md-typeset h3 {
      margin-top: 2rem;
      margin-bottom: 1rem;
    }

    .md-typeset h1:first-child,
    .md-typeset h2:first-child,
    .md-typeset h3:first-child {
      margin-top: 0;
    }

[docs\assets\images\codeHeader.png]

codeHeader

帮助文档生成样式

image-20250923160659893

image-20250923160714067

mkdocs_help_doc

MkDocs博客文件命名规则

MkDocs博客文件命名规则

❌ 避免的命名方式

  1. 以点号开头的文件名

  2. ❌ .Net封装ObjectARX自定义实体类型.md

  3. ❌ .gitignore.md

  4. 包含特殊字符的文件名

  5. file@name.md

  6. ❌ file#name.md

  7. ❌ file<>name.md

  8. 过长的文件名

  9. ❌ 超过255字符的文件名

✅ 推荐的命名方式

  1. 使用标准字符

  2. ✅ Net封装ObjectARX自定义实体类型.md

  3. ✅ dotnet-objectarx-custom-entity.md

  4. 使用连字符分隔

  5. ✅ c-sharp-json-processing.md

  6. ✅ wpf-custom-drawer-menu.md

  7. 使用下划线分隔

  8. ✅ objectarx_net_learning_part2.md

mkdocs快速搭建博客

安装依赖

requirement.txt

Text Only
# MkDocs 核心
mkdocs>=1.5.0
mkdocs-material>=9.4.0

# 博客功能(包含在 mkdocs-material 中)
# mkdocs-blog-plugin  # 不需要单独安装

# 功能增强插件
mkdocs-minify-plugin>=0.7.0
mkdocs-glightbox>=0.3.4

# Git 相关插件(可选,需要系统安装 Git)
# mkdocs-git-revision-date-localized-plugin>=1.2.0

# 其他可选插件
mkdocs-awesome-pages-plugin
# mkdocs-redirects
# mkdocs-rss-plugin

yaml文件配置

mkdoc.yml

mkdoc.yml
YAML
site_name: 我的帮助文档
site_description: 关于mkdocs-material支持的markdown语法,包括传统语法和扩展语法
site_author: JerryMa
site_url: http://127.0.0.1:8000

repo_name: 'mkdocsblog'
repo_url: 'https://github.com/mazaiguo/mkdocsblog'
theme:
  name: material
  palette:
    # Toggle light mode
    - scheme: default
      primary: Blue Grey
      accent: Pink
      toggle:
        icon: material/toggle-switch
        name: 切换到明亮模式
    # Toggle dark mode
    - scheme: slate
      primary: blue
      accent: amber
      toggle:
        icon: material/toggle-switch-off-outline
        name: 切换到暗黑模式
  features:
    - announce.dismiss
    - content.tabs.link
    - content.tooltips
    - content.code.copy #代码复制
    - content.code.select
    - content.code.annotate   
    - content.footnote.tooltips
    - header.autohide
    - navigation.footer
    - navigation.indexes
    - navigation.instant
    - navigation.instant.prefetch
    - navigation.instant.progress
    - navigation.prune
    - navigation.sections
    - navigation.tabs
    - navigation.tabs.sticky
    - navigation.top # 返回顶部的按钮 在上滑时出现  
    - navigation.tracking
    - search.highlight # 搜索出的文章关键词加入高亮
    - search.share #搜索分享按钮   
    - search.suggest # 搜索输入一些字母时推荐补全整个单词
    - toc.follow
    - toc.integrate
  language: 'zh'
plugins:
  - macros
  - blog:
      blog_dir: blog
      post_dir: "{blog}/posts"
      post_date_format: full
      post_url_format: "{date}/{slug}"
      pagination_per_page: 10
      pagination_url_format: "page/{page}"
      authors_file: "{blog}/.authors.yml"
      blog_toc: true
      categories_toc: true
      archive: true
      archive_name: 归档
      archive_date_format: "YYYY年MM月"
      archive_url_format: "archive/{date}"
      archive_toc: true
      archive_file: "archive/index.md"
      categories: true
      categories_name: 分类
      categories_url_format: "category/{slug}"
      categories_slugify: !!python/object/apply:pymdownx.slugs.slugify
        kwds:
          case: lower
  - offline
  - tags:
      tags_hierarchy: true
      tags_slugify_format: "tag:{slug}"
      tags_slugify: !!python/object/apply:pymdownx.slugs.slugify
        kwds:
          case: lower
  - search:
      lang: 
        - zh
        - en
      separator: '[\s\-\.]+'
  - minify:
      minify_html: true
      minify_js: true
      minify_css: true
      htmlmin_opts:
        remove_comments: true
      css_files:
        - stylesheets/extra.css
  - glightbox:
      touchNavigation: true
      loop: false
      effect: zoom
      slide_effect: slide
      width: 100%
      height: auto
      zoomable: true
      draggable: true
      skip_classes:
        - custom-skip-class-name
      auto_caption: false
      caption_position: bottom
  # 注释掉 git 插件,因为需要系统安装 Git
  # - git-revision-date-localized:
  #     enable_creation_date: true
  #     type: timeago
  #     locale: zh
  #     fallback_to_build_date: false
  #     exclude:
  #       - index.md
  #       - tags.md
  #       - blog/index.md
extra:
  social:
    - icon: fontawesome/brands/github #联系方式图标 : https://fontawesome.com/ 去这里找图标
      link: https://github.com/mazaiguo
      name: JerryMa on Github
    - icon: fontawesome/brands/gitlab
      link: https://gitlab.zwsoft.cn/mazaiguo
    - icon: fontawesome/regular/envelope
      link: mailto:mazaiguo@126.com
      name: Email
  analytics:
    feedback:
      title: 这个页面对您有帮助吗?
      ratings:
        - icon: material/emoticon-happy-outline
          name: 有帮助
          data: 1
          note: >-
            感谢您的反馈!
        - icon: material/emoticon-sad-outline
          name: 可以改进
          data: 0
          note: >-
            感谢您的反馈!请帮助我们改进这个页面,
            <a href="https://github.com/mazaiguo/mazaiguo.github.io/issues/new/?title=[Feedback]+{title}+-+{url}" target="_blank" rel="noopener">告诉我们需要改进的地方</a>。
  tags:
    HTML5: html
    JavaScript: js
    CSS: css
    Python: python
    AutoCAD: autocad
    C++: cpp
    "Csharp": csharp
    ".NET": dotnet
  generator: false #是否删除页脚显示"使用 MkDocs 材料制造"
#extra_javascript:
  #- https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/highlight.min.js
  #- javascripts/config.js
extra_css:
  #- https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/default.min.css
  - stylesheets/extra.css  
markdown_extensions:
  - abbr
  - admonition
  - attr_list
  - def_list
  - footnotes
  - md_in_html
  - meta
  - toc:
      permalink: true
      title: 目录
  - pymdownx.arithmatex:
      generic: true
  - pymdownx.betterem:
      smart_enable: all
  - pymdownx.caret
  - pymdownx.details
  - pymdownx.emoji:
      emoji_generator: !!python/name:material.extensions.emoji.to_svg
      emoji_index: !!python/name:material.extensions.emoji.twemoji
  - pymdownx.highlight:
      anchor_linenums: true
      line_spans: __span
      pygments_lang_class: true
      linenums: true
      linenums_style: pymdownx.inline
      auto_title: true # 显示编程语言名称
      use_pygments: true
  - pymdownx.inlinehilite
  - pymdownx.keys
  - pymdownx.magiclink:
      normalize_issue_symbols: true
      repo_url_shorthand: true
      user: mazaiguo
      repo: helpdoc
  - pymdownx.mark
  - pymdownx.smartsymbols
  - pymdownx.snippets:
      check_paths: true
  - pymdownx.superfences:
      custom_fences:
        - name: mermaid
          class: mermaid
          format: !!python/name:pymdownx.superfences.fence_code_format
  - pymdownx.tabbed:
      alternate_style: true
      combine_header_slug: true
      slugify: !!python/object/apply:pymdownx.slugs.slugify
        kwds:
          case: lower
  - pymdownx.tasklist:
      custom_checkbox: true
  - pymdownx.tilde
  - pymdownx.critic
copyright: Copyright &copy; 2016 - present [JerryMa](https://github.com/mazaiguo)
nav:
  - 首页: index.md
  - 博客:
     - blog/index.md
  - 归档: archive/index.md
  - 分类: blog/category.md
  - 标签: tags.md
  - 关于: 
     - 关于本站: about.md

增加latex

Bash
1
2
3
$$
\cos x=\sum_{k=0}^{\infty}\frac{(-1)^k}{(2k)!}x^{2k}
$$
\[ \cos x=\sum_{k=0}^{\infty}\frac{(-1)^k}{(2k)!}x^{2k} \]
Bash
1
2
3
The homomorphism $f$ is injective if and only if its kernel is only the
singleton set $e_G$, because otherwise $\exists a,b\in G$ with $a\neq b$ such
that $f(a)=f(b)$.

The homomorphism \(f\) is injective if and only if its kernel is only the singleton set \(e_G\), because otherwise \(\exists a,b\in G\) with \(a\neq b\) such that \(f(a)=f(b)\).

contents tab

  • Sed sagittis eleifend rutrum
  • Donec vitae suscipit est
  • Nulla tempor lobortis orci
  1. Sed sagittis eleifend rutrum
  2. Donec vitae suscipit est
  3. Nulla tempor lobortis orci
C
1
2
3
4
5
6
#include <stdio.h>

int main(void) {
  printf("Hello world!\n");
  return 0;
}
C++
1
2
3
4
5
6
#include <iostream>

int main(void) {
  std::cout << "Hello world!" << std::endl;
  return 0;
}

Example

Example:

Markdown
1
2
3
* Sed sagittis eleifend rutrum
* Donec vitae suscipit est
* Nulla tempor lobortis orci

Result:

  • Sed sagittis eleifend rutrum
  • Donec vitae suscipit est
  • Nulla tempor lobortis orci

Example:

Markdown
1
2
3
1. Sed sagittis eleifend rutrum
2. Donec vitae suscipit est
3. Nulla tempor lobortis orci

Result:

  1. Sed sagittis eleifend rutrum
  2. Donec vitae suscipit est
  3. Nulla tempor lobortis orci

Admonition

Outer Note

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla et euismod nulla. Curabitur feugiat, tortor non consequat finibus, justo purus auctor massa, nec semper lorem quam in massa.

Inner Note

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla et euismod nulla. Curabitur feugiat, tortor non consequat finibus, justo purus auctor massa, nec semper lorem quam in massa.

Note

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla et euismod nulla. Curabitur feugiat, tortor non consequat finibus, justo purus auctor massa, nec semper lorem quam in massa.

Info

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla et euismod
nulla. Curabitur feugiat, tortor non consequat finibus, justo purus auctor
massa, nec semper lorem quam in massa.

Abstract

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla et euismod
nulla. Curabitur feugiat, tortor non consequat finibus, justo purus auctor
massa, nec semper lorem quam in massa.

Info

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla et euismod
nulla. Curabitur feugiat, tortor non consequat finibus, justo purus auctor
massa, nec semper lorem quam in massa.

Tip

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla et euismod
nulla. Curabitur feugiat, tortor non consequat finibus, justo purus auctor
massa, nec semper lorem quam in massa.

Success

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla et euismod
nulla. Curabitur feugiat, tortor non consequat finibus, justo purus auctor
massa, nec semper lorem quam in massa.

Question

这是个问题

Warning

这是个警告

Failure

这是失败的提示

Danger

危险错误的提示

Error

错误的提示

Bug

bug的提示

Quote

quote的提示

特殊数据处理

archive/index.md

tags.md

不识别[TAGS]、[ARCHIVE],用main.py中定义的自定义宏来处理

main.py

main.py
Python
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
"""
MkDocs macros for auto-generating archive content
"""
import os
import re
import yaml
from pathlib import Path
from datetime import datetime, date  # 顶部导入
from collections import defaultdict
import urllib.parse

def generate_url_slug(title):
    """根据实际URL格式生成URL片段"""
    # 1. 转为小写
    slug = title.lower()

    # 2. 特殊处理:如果以点号开头,删除点号并在后面添加分隔符
    if slug.startswith('.'):
        slug = slug[1:]  # 删除开头的点号
        # 在英文和中文之间添加分隔符
        slug = re.sub(r'^([a-z]+)(?=[\u4e00-\u9fff])', r'\1-', slug)

    # 3. 处理其他点号 - 直接删除(保持原有行为)
    slug = slug.replace('.', '')

    # 4. 处理括号(删除括号,保留内容)
    slug = re.sub(r'[()()]', '', slug)

    # 5. 处理连续的+号(C++等)
    slug = re.sub(r'\+{2,}', '', slug)

    # 6. 只对没有自然分隔符的长字符串添加中英文分隔符
    # 检查是否需要添加分隔符(没有空格或连字符的长字符串)
    # if ' ' not in slug and '-' not in slug and len(slug) > 10:
    #     # 在中英文交界处添加分隔符
    #     slug = re.sub(r'(?<=[a-z0-9])(?=[\u4e00-\u9fff])', '-', slug)
    #     slug = re.sub(r'(?<=[\u4e00-\u9fff])(?=[a-z0-9])', '-', slug)

    # 7. 将多个空格合并为一个,然后转为连字符
    slug = re.sub(r'\s+', '-', slug)

    # 8. 清理多余的连字符
    slug = re.sub(r'-+', '-', slug)
    slug = slug.strip('-')

    # 9. URL编码
    return urllib.parse.quote(slug)

def define_env(env):
    """
    Define macros for MkDocs  
    """

    @env.macro  
    def auto_archive():
        """
        Automatically generate archive content from blog posts
        从md文件中完全自动获取所有信息,无硬编码
        """
        try:
            blog_posts_dir = Path("docs/blog/posts")
            if not blog_posts_dir.exists():
                return "## ❌ 错误\n\n无法找到博客文章目录\n\n"

            posts = []
            all_categories = set()  # 收集所有出现的分类
            debug_info = []  # 添加调试信息

            # 遍历所有文章文件
            for md_file in blog_posts_dir.rglob("*.md"):
                debug_info.append(f"处理文件: {md_file}")
                try:
                    with open(md_file, 'r', encoding='utf-8-sig') as f:
                        content = f.read()
                    debug_info.append(f"成功读取: {md_file.name}")

                    # 提取front matter
                    if content.startswith('---'):
                        parts = content.split('---', 2)
                        if len(parts) >= 3:
                            front_matter_text = parts[1].strip()
                            try:
                                # 解析YAML front matter
                                front_matter = yaml.safe_load(front_matter_text)
                                if not front_matter:
                                    continue

                                title = front_matter.get('title', md_file.stem)
                                date_val = front_matter.get('date', '2025-09-18')
                                categories = front_matter.get('categories', [])

                                # 确定主分类 - 完全从front matter获取
                                if isinstance(categories, list) and categories:
                                    main_category = categories[0]
                                    # 收集所有分类
                                    for cat in categories:
                                        all_categories.add(str(cat))
                                else:
                                    # 如果没有分类,跳过这篇文章或使用未分类
                                    main_category = "未分类"
                                    all_categories.add("未分类")

                                # 生成相对路径
                                relative_path = os.path.relpath(md_file, Path("docs/archive")).replace('\\', '/')

                                posts.append({
                                    'title': title,
                                    'date': str(date_val),
                                    'category': str(main_category),
                                    'path': relative_path,
                                    'all_categories': categories if isinstance(categories, list) else [main_category]
                                })

                            except yaml.YAMLError as e:
                                # YAML解析失败,尝试提取基本信息
                                posts.append({
                                    'title': md_file.stem,
                                    'date': '2025-09-18', 
                                    'category': "解析失败",
                                    'path': os.path.relpath(md_file, Path("docs/archive")).replace('\\', '/'),
                                    'all_categories': ["解析失败"]
                                })
                                all_categories.add("解析失败")

                except Exception as e:
                    # 文件读取失败
                    continue

            # 添加调试输出
            debug_text = "\n".join(debug_info[:10])  # 显示前10行调试信息

            if not posts:
                return f"## 📝 调试信息\n\n找到 {len(posts)} 篇文章\n\n调试:\n```\n{debug_text}\n```\n\n分类: {list(all_categories)}\n\n"

            # 生成干净的结果,不包含调试信息

            # 动态生成分类名称映射
            category_display_names = {}
            for category in all_categories:
                cat_lower = category.lower()
                if cat_lower == 'cpp' or 'c++' in cat_lower:
                    category_display_names[category] = 'CPP开发'
                elif cat_lower == 'python' or 'python' in cat_lower:
                    category_display_names[category] = 'Python开发'
                elif 'autocad' in cat_lower or 'cad' in cat_lower:
                    category_display_names[category] = 'AutoCAD开发'
                elif cat_lower == 'csharp' or 'c#' in cat_lower or '.net' in cat_lower:
                    category_display_names[category] = 'C#/.NET开发'
                elif '开发工具' in category or '工具' in category:
                    category_display_names[category] = '开发工具'
                elif '未分类' in category:
                    category_display_names[category] = '未分类'
                elif '解析失败' in category:
                    category_display_names[category] = '解析失败'
                else:
                    # 默认添加"开发"后缀,除非已经包含
                    if '开发' not in category:
                        category_display_names[category] = f'{category}开发'
                    else:
                        category_display_names[category] = category

            # 按日期分组
            posts.sort(key=lambda x: x['date'], reverse=True)
            date_groups = defaultdict(list)

            for post in posts:
                try:
                    if isinstance(post['date'], str):
                        date_obj = datetime.strptime(post['date'], '%Y-%m-%d')
                    else:
                        date_obj = post['date']
                    month_key = date_obj.strftime('%Y年%m月')
                    date_groups[month_key].append(post)
                except:
                    # 日期解析失败,使用默认
                    date_groups['2025年09月'].append(post)

            # 生成归档内容
            result = []

            for month in sorted(date_groups.keys(), reverse=True):
                month_posts = date_groups[month]
                result.append(f"## 🗓️ {month}")
                result.append("")

                # 按分类分组
                category_groups = defaultdict(list)
                for post in month_posts:
                    category_groups[post['category']].append(post)

                # 按分类显示
                for category in sorted(category_groups.keys()):
                    display_name = category_display_names.get(category, category)
                    result.append(f"### {display_name}")

                    for post in category_groups[category]:
                        result.append(f"- [{post['title']}]({post['path']}) - {post['date']}")

                    result.append("")

            # 在最后添加统计信息
            result.append("---")
            result.append("")
            result.append("## 📊 统计信息")
            result.append("")
            result.append(f"- **总文章数**: {len(posts)}篇")
            result.append(f"- **分类数量**: {len(all_categories)}个")
            result.append("- **分类列表**: " + "、".join(sorted(all_categories)))

            return '\n'.join(result)

        except Exception as e:
            return f"## ❌ 生成错误\n\n生成归档时出错: {str(e)}\n\n请检查md文件格式或front matter语法。"

    @env.macro
    def auto_category():
        """
        Automatically generate category content from blog posts
        从md文件中完全自动获取分类信息,无硬编码
        """
        try:
            blog_posts_dir = Path("docs/blog/posts")
            if not blog_posts_dir.exists():
                return "## ❌ 错误\n\n无法找到博客文章目录\n\n"

            # 收集分类信息
            category_info = defaultdict(list)
            all_categories = set()
            nBlogCount = 0
            # 遍历所有文章文件
            for md_file in blog_posts_dir.rglob("*.md"):
                try:
                    with open(md_file, 'r', encoding='utf-8-sig') as f:
                        content = f.read()

                    nBlogCount += 1
                    # 提取front matter
                    if content.startswith('---'):
                        parts = content.split('---', 2)
                        if len(parts) >= 3:
                            front_matter_text = parts[1].strip()
                            try:
                                front_matter = yaml.safe_load(front_matter_text)
                                if not front_matter:
                                    continue

                                title = front_matter.get('title', md_file.stem)
                                categories = front_matter.get('categories', [])
                                date_val = front_matter.get('date', '2025-09-18')

                                # 处理分类
                                if isinstance(categories, list) and categories:
                                    for category in categories:
                                        cat_str = str(category).strip().lower()  # 归一化
                                        all_categories.add(cat_str)
                                        category_info[cat_str].append((title, date_val, md_file.stem))

                            except yaml.YAMLError:
                                continue

                except Exception:
                    continue

            # 生成分类页面内容  
            result = []
            result.append(f"## 🔍 找到{len(all_categories)}个分类")
            result.append("")

            # 编程语言部分
            result.append("## 🖥️ 编程语言")
            result.append("")

            for category in sorted(all_categories):
                cat_lower = category.lower()
                if cat_lower in ['cpp', 'python', 'csharp'] or 'c++' in cat_lower:
                    count = len(category_info[category])

                    if cat_lower == 'cpp' or 'c++' in cat_lower:
                        display_name = 'CPP'
                        icon = '🖥️'
                    elif cat_lower == 'python':
                        display_name = 'Python'
                        icon = '🐍'
                    else:
                        display_name = category
                        icon = '💻'

                    # 添加分类标题
                    result.append(f"### {icon} [{display_name}](category/{cat_lower}.html)")
                    result.append(f"- **文章数量**: {count}篇")

                    latest = []
                    print(f"category={category}, items={category_info[category]}")
                    for t, d, stem in sorted(category_info[category], key=lambda x: x[1], reverse=True)[:3]:
                        url = f"{generate_url_slug(t)}.html"  # 使用title转小写再编码
                        dt = None
                        try:
                            if isinstance(d, datetime):
                                dt = d
                            elif isinstance(d, date):
                                dt = datetime.combine(d, datetime.min.time())
                            elif isinstance(d, str) and len(d) == 10:
                                dt = datetime.strptime(d, "%Y-%m-%d")
                        except Exception as ex:
                            pass
                        if dt:
                            url = f"{dt.year}/{dt.month:02d}/{dt.day:02d}/{generate_url_slug(t)}.html"
                        latest.append(f"[{t}]({url})")
                    result.append(f"- **最新文章**: {', '.join(latest)}")
                    result.append("")
                else:
                    icon = '💻'
                    result.append(f"### {icon} [{cat_lower}](category/{cat_lower}.html)")
                    result.append(f"- **文章数量**: {len(category_info[category])}篇")
                    # 修正这里,生成带链接的最新文章
                    latest = []
                    print(f"category={category}, items={category_info[category]}")
                    for t, d, stem in sorted(category_info[category], key=lambda x: x[1], reverse=True)[:3]:
                        url = f"{generate_url_slug(t)}.html"  # 使用title转小写再编码
                        dt = None
                        try:
                            if isinstance(d, datetime):
                                dt = d
                            elif isinstance(d, date):
                                dt = datetime.combine(d, datetime.min.time())
                            elif isinstance(d, str) and len(d) == 10:
                                dt = datetime.strptime(d, "%Y-%m-%d")
                        except Exception as ex:
                            pass
                        if dt:
                            url = f"{dt.year}/{dt.month:02d}/{dt.day:02d}/{generate_url_slug(t)}.html"
                        latest.append(f"[{t}]({url})")
                    result.append(f"- **最新文章**: {', '.join(latest)}")
                    result.append("")

            # 开发框架和工具部分
            result.append("## 🔧 开发框架与工具")
            result.append("")

            for category in sorted(all_categories):
                cat_lower = category.lower()
                if 'autocad' in cat_lower or 'cad' in cat_lower or '工具' in cat_lower:
                    count = len(category_info[category])

                    # 添加分类标题
                    if 'autocad' in cat_lower or 'cad' in cat_lower:
                        icon = '🏗️'
                        display_name = 'AutoCAD/CAD开发'
                    else:
                        icon = '🔧'
                        display_name = category

                    result.append(f"### {icon} [{display_name}](category/{cat_lower}.html)")
                    result.append(f"- **文章数量**: {count}篇")

                    latest = []
                    print(f"category={category}, items={category_info[category]}")
                    for t, d, stem in sorted(category_info[category], key=lambda x: x[1], reverse=True)[:3]:
                        url = f"{generate_url_slug(t)}.html"  # 使用title转小写再编码
                        dt = None
                        try:
                            if isinstance(d, datetime):
                                dt = d
                            elif isinstance(d, date):
                                dt = datetime.combine(d, datetime.min.time())
                            elif isinstance(d, str) and len(d) == 10:
                                dt = datetime.strptime(d, "%Y-%m-%d")
                        except Exception as ex:
                            pass
                        if dt:
                            url = f"{dt.year}/{dt.month:02d}/{dt.day:02d}/{generate_url_slug(t)}.html"
                        latest.append(f"[{t}]({url})")
                    result.append(f"- **最新文章**: {', '.join(latest)}")
                    result.append("")

            # 统计信息
            result.append("---")
            result.append("")
            result.append("## 📊 分类统计")
            result.append("")
            total_articles = sum(len(articles) for articles in category_info.values())
            result.append(f"- **总分类数**: {len(all_categories)}个")
            result.append(f"- **总文章数**:  {nBlogCount}篇")

            return '\n'.join(result)

        except Exception as e:
            return f"## ❌ 生成错误\n\n{str(e)}\n\n"

    @env.macro
    def auto_tag():
        """
        Automatically generate tag content from blog posts
        从md文件中完全自动获取标签信息,无硬编码
        """

        try:
            all_tags = set()
            tag_info = defaultdict(list)
            blog_posts_dir = Path("docs/blog/posts")
            if not blog_posts_dir.exists():
                return "## ❌ 错误\n\n无法找到博客文章目录\n\n"
            # 遍历所有文章文件
            for md_file in blog_posts_dir.rglob("*.md"):
                try:
                    with open(md_file, 'r', encoding='utf-8-sig') as f:
                        content = f.read()

                    # 提取front matter
                    if content.startswith('---'):
                        parts = content.split('---', 2)
                        if len(parts) >= 3:
                            front_matter_text = parts[1].strip()
                            try:        
                                front_matter = yaml.safe_load(front_matter_text)
                                if not front_matter:
                                    continue

                                tags = front_matter.get('tags', [])

                                # 处理标签
                                if isinstance(tags, list) and tags:
                                    for tag in tags:
                                        tag_str = str(tag)
                                        all_tags.add(tag_str)
                                        tag_info[tag_str].append(md_file.stem)

                            except yaml.YAMLError:
                                continue

                except Exception:
                    continue

            if not tag_info:
                return f"## 📝 调试信息\n\n找到 {len(all_tags)} 个标签,{len(tag_info)} 个有文章的标签\n\n所有标签: {list(all_tags)}\n\n"

            # 生成标签页面内容
            result = []
            result.append(f"## 🔍 找到{len(all_tags)}个标签")
            result.append("")

            for tag in sorted(all_tags):
                count = len(tag_info[tag])
                result.append(f"### [{tag}](tag/{tag}.html)") # 修改为tag/{tag}.html
                result.append(f"- **文章数量**: {count}篇")
                result.append(f"- **最新文章**: {', '.join(tag_info[tag][:3])}")
                result.append("")   
            return '\n'.join(result)

        except Exception as e:
            return f"## ❌ 生成错误\n\n{str(e)}\n\n"

    @env.macro
    def auto_home_category():
        """
        Automatically generate category content from blog posts
        从md文件中完全自动获取分类信息,无硬编码
        """
        try:
            blog_posts_dir = Path("docs/blog/posts")
            if not blog_posts_dir.exists():
                return "## ❌ 错误\n\n无法找到博客文章目录\n\n"

            # 收集分类信息
            category_info = defaultdict(list)
            all_categories = set()
            nBlogCount = 0
            # 遍历所有文章文件
            for md_file in blog_posts_dir.rglob("*.md"):
                try:
                    with open(md_file, 'r', encoding='utf-8-sig') as f:
                        content = f.read()

                    nBlogCount += 1
                    # 提取front matter
                    if content.startswith('---'):
                        parts = content.split('---', 2)
                        if len(parts) >= 3:
                            front_matter_text = parts[1].strip()
                            try:
                                front_matter = yaml.safe_load(front_matter_text)
                                if not front_matter:
                                    continue

                                title = front_matter.get('title', md_file.stem)
                                categories = front_matter.get('categories', [])
                                date_val = front_matter.get('date', '2025-09-18')

                                # 处理分类
                                if isinstance(categories, list) and categories:
                                    for category in categories:
                                        cat_str = str(category).strip().lower()  # 归一化
                                        all_categories.add(cat_str)
                                        category_info[cat_str].append((title, date_val, md_file.stem))

                            except yaml.YAMLError:
                                continue

                except Exception:
                    continue

            # if not category_info:
            #     return f"## 📝 调试信息\n\n找到 {len(all_categories)} 个分类,{len(category_info)} 个有文章的分类\n\n所有分类: {list(all_categories)}\n\n"

            # 生成分类页面内容  
            result = []
            result.append(f"## 🔍 找到{len(all_categories)}个分类")
            result.append("")

            # 编程语言部分
            result.append("## 🖥️ 编程语言")
            result.append("")

            for category in sorted(all_categories):
                cat_lower = category.lower()
                if 'windows' in cat_lower or 'window' in cat_lower:
                    count = len(category_info[category])

                    if cat_lower == 'window' or 'windows' in cat_lower:
                        display_name = 'windows程序'
                        icon = '🔨'
                    else:
                        display_name = category
                        icon = '💻'

                    # 添加分类标题
                    result.append(f"### {icon} [{display_name}](blog/category/{cat_lower}.html)")
                    result.append(f"- **文章数量**: {count}篇")

                    latest = []
                    print(f"category={category}, items={category_info[category]}")
                    for t, d, stem in sorted(category_info[category], key=lambda x: x[1], reverse=True)[:]:
                        url = f"blog/{generate_url_slug(t)}.html"  # 使用title转小写再编码
                        dt = None
                        try:
                            if isinstance(d, datetime):
                                dt = d
                            elif isinstance(d, date):
                                dt = datetime.combine(d, datetime.min.time())
                            elif isinstance(d, str) and len(d) == 10:
                                dt = datetime.strptime(d, "%Y-%m-%d")
                        except Exception as ex:
                            pass
                        if dt:
                            url = f"blog/{dt.year}/{dt.month:02d}/{dt.day:02d}/{generate_url_slug(t)}.html"
                        latest.append(f"<li>[{t}]({url})</li>")
                    result.append(f"- **最新文章**: {' '.join(latest)}")
                    result.append("")
                else:
                    icon = '🛠️'
                    result.append(f"### {icon} [{cat_lower}](blog/category/{cat_lower}.html)")
                    result.append(f"- **文章数量**: {len(category_info[category])}篇")
                    # 修正这里,生成带链接的最新文章
                    latest = []
                    print(f"category={category}, items={category_info[category]}")
                    for t, d, stem in sorted(category_info[category], key=lambda x: x[1], reverse=True)[:]:
                        url = f"blog/{generate_url_slug(t)}.html"  # 使用title转小写再编码
                        dt = None
                        try:
                            if isinstance(d, datetime):
                                dt = d
                            elif isinstance(d, date):
                                dt = datetime.combine(d, datetime.min.time())
                            elif isinstance(d, str) and len(d) == 10:
                                dt = datetime.strptime(d, "%Y-%m-%d")
                        except Exception as ex:
                            pass
                        if dt:
                            url = f"blog/{dt.year}/{dt.month:02d}/{dt.day:02d}/{generate_url_slug(t)}.html"
                        latest.append(f"<li>[{t}]({url})</li>")
                    result.append(f"- **最新文章**: {' '.join(latest)}")
                    result.append("")

            # 开发框架和工具部分
            result.append("## 🏗️ 开发框架与工具")
            result.append("")

            for category in sorted(all_categories):
                cat_lower = category.lower()
                if 'autocad' in cat_lower or 'cad' in cat_lower or '工具' in cat_lower:
                    count = len(category_info[category])

                    # 添加分类标题
                    if 'autocad' in cat_lower or 'cad' in cat_lower:
                        icon = '🏗️'
                        display_name = 'AutoCAD/CAD开发'
                    else:
                        icon = '✏️'
                        display_name = category

                    result.append(f"### {icon} [{display_name}](blog/category/{cat_lower}.html)")
                    result.append(f"- **文章数量**: {count}篇")

                    latest = []
                    print(f"category={category}, items={category_info[category]}")
                    for t, d, stem in sorted(category_info[category], key=lambda x: x[1], reverse=True)[:]:
                        url = f"blog/{generate_url_slug(t)}.html"  # 使用title转小写再编码
                        dt = None
                        try:
                            if isinstance(d, datetime):
                                dt = d
                            elif isinstance(d, date):
                                dt = datetime.combine(d, datetime.min.time())
                            elif isinstance(d, str) and len(d) == 10:
                                dt = datetime.strptime(d, "%Y-%m-%d")
                        except Exception as ex:
                            pass
                        if dt:
                            url = f"blog/{dt.year}/{dt.month:02d}/{dt.day:02d}/{generate_url_slug(t)}.html"
                        latest.append(f"<li>[{t}]({url})</li>")
                    result.append(f"- **最新文章**: {''.join(latest)}")
                    result.append("")

            # 统计信息
            result.append("---")
            result.append("")
            result.append("## 📊 分类统计")
            result.append("")
            total_articles = sum(len(articles) for articles in category_info.values())
            result.append(f"- **总分类数**: {len(all_categories)}个")
            result.append(f"- **总文章数**:  {nBlogCount}篇")

            return '\n'.join(result)

        except Exception as e:
            return f"## ❌ 生成错误\n\n{str(e)}\n\n" 

发布到github中

使用GitHub Actions

使用GitHub Actions可以自动部署网站。在库的根目录下新建一个GitHub Actions workflow,比如:.github/workflows/ci.yml,并粘贴入以下内容:

Material for MkDocs

Text Only
name: ci
on:
  push:
    branches:
      - master
      - main
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-python@v2
        with:
          python-version: 3.x
      - run: pip install mkdocs-material
      - run: mkdocs gh-deploy --force

此时,当一个新的提交推送到mastermain时,我们的静态网站的内容将自动生成并完成部署。可以尝试推送一个提交来查看GitHub Actions的工作状况。

添加相关权限:

image-20250919104024286

image-20250919104720037

Vs自定义项目模板

image-20250415192756364

先配置好项目相关文件

导出项目相关文件

image-20250415192839962

导出为项目

image-20250415193017914

image-20250415193036748

image-20250415193112362

取消,需要对压缩文件进行处理

处理压缩包

image-20250415193410753

修改数据

img

修改完成后将压缩包放置到Visual Studio 2022\Templates\ProjectTemplates

Text Only
Visual Studio 2022\Templates\ProjectTemplates

image-20250415193604984

Text Only
MyTemplate.vstemplate
<VSTemplate Version="3.0.0" xmlns="http://schemas.microsoft.com/developer/vstemplate/2005" Type="Project">
  <TemplateData>
    <Name>ZwObjectZrxNet</Name>
    <Description>ObjectZRX+WPF模板</Description>
    <ProjectType>CSharp</ProjectType>
    <ProjectSubType>
    </ProjectSubType>
    <SortOrder>1000</SortOrder>
    <CreateNewFolder>true</CreateNewFolder>
    <DefaultName>ZwObjectZrxNet</DefaultName>
    <ProvideDefaultName>true</ProvideDefaultName>
    <LocationField>Enabled</LocationField>
    <EnableLocationBrowseButton>true</EnableLocationBrowseButton>
    <Icon>__TemplateIcon.ico</Icon>
    <PreviewImage>__PreviewImage.ico</PreviewImage>
    <LanguageTag>CSharp</LanguageTag>
    <PlatformTag>Windows</PlatformTag>
    <ProjectTypeTag>Library</ProjectTypeTag>
  </TemplateData>
  <TemplateContent>
    <Project TargetFileName="$safeprojectname$.csproj" File="$safeprojectname$.csproj" ReplaceParameters="true">
      <ProjectItem ReplaceParameters="true" TargetFileName="app.config">app.config</ProjectItem>
      <ProjectItem ReplaceParameters="true" TargetFileName="Commands.cs">Commands.cs</ProjectItem>
      <Folder Name="Controls" TargetFolderName="Controls" />
      <Folder Name="Converters" TargetFolderName="Converters">
        <ProjectItem ReplaceParameters="true" TargetFileName="OptionToBooleanConverter.cs">OptionToBooleanConverter.cs</ProjectItem>
      </Folder>
      <Folder Name="Models" TargetFolderName="Models" />
      <ProjectItem ReplaceParameters="true" TargetFileName="packages.config">packages.config</ProjectItem>
      <ProjectItem ReplaceParameters="true" TargetFileName="PlugInApplication.cs">PlugInApplication.cs</ProjectItem>
      <Folder Name="Properties" TargetFolderName="Properties">
        <ProjectItem ReplaceParameters="true" TargetFileName="AssemblyInfo.cs">AssemblyInfo.cs</ProjectItem>
      </Folder>
      <Folder Name="Styles" TargetFolderName="Styles">
        <ProjectItem ReplaceParameters="true" TargetFileName="Styles.xaml">Styles.xaml</ProjectItem>
      </Folder>
      <Folder Name="ViewModel" TargetFolderName="ViewModel">
        <ProjectItem ReplaceParameters="true" TargetFileName="WpfDemoViewModel.cs">WpfDemoViewModel.cs</ProjectItem>
      </Folder>
      <Folder Name="Views" TargetFolderName="Views">
        <ProjectItem ReplaceParameters="true" TargetFileName="WpfDemo.xaml">WpfDemo.xaml</ProjectItem>
        <ProjectItem ReplaceParameters="true" TargetFileName="WpfDemo.xaml.cs">WpfDemo.xaml.cs</ProjectItem>
      </Folder>
    </Project>
  </TemplateContent>
</VSTemplate>

Csharp

Text Only
$safeprojectname$.csproj
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
    <ProjectGuid>{$guid1$}</ProjectGuid>
    <OutputType>Library</OutputType>
    <AppDesignerFolder>Properties</AppDesignerFolder>
    <RootNamespace>$safeprojectname$</RootNamespace>
    <AssemblyName>$safeprojectname$</AssemblyName>
    <TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
    <FileAlignment>512</FileAlignment>
    <Deterministic>true</Deterministic>
    <ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
    <NuGetPackageImportStamp>
    </NuGetPackageImportStamp>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <DebugSymbols>true</DebugSymbols>
    <DebugType>full</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>bin\Debug\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    <DebugType>pdbonly</DebugType>
    <Optimize>true</Optimize>
    <OutputPath>bin\Release\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
    <DebugSymbols>true</DebugSymbols>
    <OutputPath>..\..\Out\Debug\bin\x64\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <DebugType>full</DebugType>
    <PlatformTarget>x64</PlatformTarget>
    <LangVersion>7.3</LangVersion>
    <ErrorReport>prompt</ErrorReport>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
    <OutputPath>..\..\Out\Release\bin\x64\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <Optimize>true</Optimize>
    <DebugType>pdbonly</DebugType>
    <PlatformTarget>x64</PlatformTarget>
    <LangVersion>7.3</LangVersion>
    <ErrorReport>prompt</ErrorReport>
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="CommunityToolkit.Mvvm, Version=8.4.0.0, Culture=neutral, PublicKeyToken=4aff67a105548ee2, processorArchitecture=MSIL">
      <HintPath>..\packages\CommunityToolkit.Mvvm.8.4.0\lib\netstandard2.0\CommunityToolkit.Mvvm.dll</HintPath>
    </Reference>
    <Reference Include="MaterialDesignColors, Version=5.2.1.0, Culture=neutral, PublicKeyToken=df2a72020bd7962a, processorArchitecture=MSIL">
      <HintPath>..\packages\MaterialDesignColors.5.2.1\lib\net462\MaterialDesignColors.dll</HintPath>
    </Reference>
    <Reference Include="MaterialDesignThemes.Wpf, Version=5.2.1.0, Culture=neutral, PublicKeyToken=df2a72020bd7962a, processorArchitecture=MSIL">
      <HintPath>..\packages\MaterialDesignThemes.5.2.1\lib\net462\MaterialDesignThemes.Wpf.dll</HintPath>
    </Reference>
    <Reference Include="Microsoft.Bcl.AsyncInterfaces, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
      <HintPath>..\packages\Microsoft.Bcl.AsyncInterfaces.8.0.0\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll</HintPath>
    </Reference>
    <Reference Include="Microsoft.Xaml.Behaviors, Version=1.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
      <HintPath>..\packages\Microsoft.Xaml.Behaviors.Wpf.1.1.39\lib\net45\Microsoft.Xaml.Behaviors.dll</HintPath>
    </Reference>
    <Reference Include="PresentationCore" />
    <Reference Include="PresentationFramework" />
    <Reference Include="System.Buffers, Version=4.0.4.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
      <HintPath>..\packages\System.Buffers.4.6.0\lib\net462\System.Buffers.dll</HintPath>
    </Reference>
    <Reference Include="System.ComponentModel.Annotations, Version=4.2.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
      <HintPath>..\packages\System.ComponentModel.Annotations.5.0.0\lib\net461\System.ComponentModel.Annotations.dll</HintPath>
    </Reference>
    <Reference Include="System.ComponentModel.DataAnnotations" />
    <Reference Include="System.Drawing" />
    <Reference Include="System.Memory, Version=4.0.2.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
      <HintPath>..\packages\System.Memory.4.6.0\lib\net462\System.Memory.dll</HintPath>
    </Reference>
    <Reference Include="System.Numerics" />
    <Reference Include="System.Numerics.Vectors, Version=4.1.5.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
      <HintPath>..\packages\System.Numerics.Vectors.4.6.0\lib\net462\System.Numerics.Vectors.dll</HintPath>
    </Reference>
    <Reference Include="System.Runtime.CompilerServices.Unsafe, Version=6.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
      <HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.6.1.0\lib\net462\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
    </Reference>
    <Reference Include="System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
      <HintPath>..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll</HintPath>
    </Reference>
    <Reference Include="System.Windows.Forms" />
    <Reference Include="System.Xaml" />
    <Reference Include="WindowsBase" />
    <Reference Include="WindowsFormsIntegration" />
    <Reference Include="ZcCui">
      <HintPath>$(ZrxSdk2025)\inc\ZcCui.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="ZcWindows">
      <HintPath>$(ZrxSdk2025)\inc\ZcWindows.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="ZdWindows">
      <HintPath>$(ZrxSdk2025)\inc\ZdWindows.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="ZwDatabaseMgd">
      <HintPath>$(ZrxSdk2025)\inc\ZwDatabaseMgd.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="ZwDatabaseMgdBrep">
      <HintPath>$(ZrxSdk2025)\inc\ZwDatabaseMgdBrep.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="ZwManaged">
      <HintPath>$(ZrxSdk2025)\inc\ZwManaged.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="System" />
    <Reference Include="System.Core" />
    <Reference Include="System.Xml.Linq" />
    <Reference Include="System.Data.DataSetExtensions" />
    <Reference Include="Microsoft.CSharp" />
    <Reference Include="System.Data" />
    <Reference Include="System.Net.Http" />
    <Reference Include="System.Xml" />
  </ItemGroup>
  <ItemGroup>
    <Compile Include="Commands.cs" />
    <Compile Include="Converters\OptionToBooleanConverter.cs" />
    <Compile Include="PlugInApplication.cs" />
    <Compile Include="Properties\AssemblyInfo.cs" />
    <Compile Include="ViewModel\WpfDemoViewModel.cs" />
    <Compile Include="Views\WpfDemo.xaml.cs">
      <DependentUpon>WpfDemo.xaml</DependentUpon>
    </Compile>
  </ItemGroup>
  <ItemGroup>
    <Folder Include="Controls\" />
    <Folder Include="Models\" />
  </ItemGroup>
  <ItemGroup>
    <Page Include="Styles\Styles.xaml">
      <SubType>Designer</SubType>
      <Generator>MSBuild:Compile</Generator>
    </Page>
    <Page Include="Views\WpfDemo.xaml">
      <SubType>Designer</SubType>
      <Generator>MSBuild:Compile</Generator>
    </Page>
  </ItemGroup>
  <ItemGroup>
    <None Include="app.config" />
    <None Include="packages.config" />
  </ItemGroup>
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
  <Import Project="..\packages\MaterialDesignThemes.5.2.1\build\MaterialDesignThemes.targets" Condition="Exists('..\packages\MaterialDesignThemes.5.2.1\build\MaterialDesignThemes.targets')" />
  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
    <PropertyGroup>
      <ErrorText>这台计算机上缺少此项目引用的 NuGet 程序包。使用“NuGet 程序包还原”可下载这些程序包。有关更多信息,请参见 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。</ErrorText>
    </PropertyGroup>
    <Error Condition="!Exists('..\packages\MaterialDesignThemes.5.2.1\build\MaterialDesignThemes.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MaterialDesignThemes.5.2.1\build\MaterialDesignThemes.targets'))" />
    <Error Condition="!Exists('..\packages\CommunityToolkit.Mvvm.8.4.0\build\CommunityToolkit.Mvvm.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\CommunityToolkit.Mvvm.8.4.0\build\CommunityToolkit.Mvvm.targets'))" />
  </Target>
  <Import Project="..\packages\CommunityToolkit.Mvvm.8.4.0\build\CommunityToolkit.Mvvm.targets" Condition="Exists('..\packages\CommunityToolkit.Mvvm.8.4.0\build\CommunityToolkit.Mvvm.targets')" />
</Project>

Csharp

image-20250416092345724

模版参数

激活IDM

第一步:https://www.internetdownloadmanager.com/官网下载IDM

第二步:安装idm并运行idm,在Windows搜索栏中输入:“powershell”右键以管理员运行。

第三步:在power shell窗口中,右键粘贴代码:

irm https://massgrave.dev/ias | iex

回车键运行,按数字键2,再按数字键9 等待片刻,关闭所有窗口再次启动你的IDM下载器,就完成激活了。

image-20240923114031157 Github

Sublime Text4 4169 安装激活

下载地址

https://download.sublimetext.com/sublime_text_build_4169_x64_setup.exe

激活

默认安装路径:C:\Program Files\Sublime Text

安装之后,使用sublime text 打开安装目录下的sublime_text.exe文件。

Ctrl + F 搜到到

Bash
80 7805 000f
94c1

更改为

Bash
c6 4005 0148
85c9

然后另存到其他路径,然后关闭sublime text,将原sublime_text.exe进行替换即可。

关闭更新

打开Sublime,在最上方菜单栏点击Preferences(中文“首选项”),然后点击(中文“设置-特定语法”)

在花括号中输入以下语句: "ignored_packages": [], "update_check":false, 然后,Ctrl+S保存 注:切记是在英文模式下!!!

image-20240719162525183

打开Sublime,在最上方菜单栏点击Preferences(中文“首选项”),然后点击Settings(中文“设置”)

在花括号中输入以下语句: "update_check":false, 注:切记是在英文模式下!!!

image-20240719162626571

☞ How to install free evaluation for Sublime Text:

Bash
1. Package Control  Install Package  Theme - Monokai Pro
2. Command Palette  Monokai Pro: select theme

ConvertToUTF8

Bash
Package Control  Install Package  ConvertToUTF8

BracketHighlighter

Bash
Package Control  Install Package  BracketHighlighter

Chinese

Bash
Package Control  Install Package  Chinese

使用 XAML 格式化工具:XAML Styler

1. XAML 的问题#

刚入门 WPF/UWP 之类的 XAML 平台,首先会接触到 XAML 这一新事物。初学 XAML 时对它的印象可以归纳为一个词:一坨

随着我在 XAML 平台上工作的时间越来越长,我对 XAML 的了解就越来越深入,从语法、约束、扩展性等方方面面,我明白到 XAML 是桌面开发平台的一个最佳解决方案。这时候我已经对 XAML 有了改观,我重新用一个词归纳了我对它的印象:一大坨

没错,这时候我已经是一个成熟的 XAML 工人了,经过我熟练的双手产生了一坨又一坨 XAML,它们成长相遇结合繁衍,变成了一大坨又一大坨 XAML。

明明 XAML 这么一大坨已经够艰难了,偏偏对于它的格式化微软爸爸也没给个好的方案。对我来说,XAML 格式化主要的难题是下面几个:

  • 如果所有属性都写在同一行,它太宽了很难看到后面的属性
  • 如果每个属性单独一行,它又太长了很难看清楚它的结构
  • 属性之间没有排序,重要属性的属性找起来很困难
  • 团队没有统一的标准,不小心格式化一下代码的话全部都会变,CodeReview 烦死个人

如果不想得过且过忍受上述这些问题的话,可以试试用 XAML Styler 这个工具,它正好解决了我最想解决的问题。

2. 安装 XAML Styler#

XAML Styler 是一个 VisualStudio插件(也可用于其它 IDE),这是它在 Github 上的地址:

https://github.com/Xavalon/XamlStyler

在这里你可以找到具体的文档,而这篇文章我只介绍我关心的其中几个属性,不一定满足到你。

在 VisualStudio 的管理扩展窗口中,输入 XamlStyle 搜索,点击“下载”然后关闭 VisualStudio 即可完成安装。

img

安装完成后重启 Visual Studio,可以在“选项”窗口中看到它的配置:

img

之后,每次在 XAML 编辑器中执行保存都会自动进行格式化操作。你也可以在 XAML 编辑器的右键菜单选择 Format XAML 或使用快捷键进行格式化。

img

3. 格式化#

XAML 的格式主要有两种方式:所有属性放一行和每个属性单独一行。

如果选择所有属性放一行的时候,XAML 结构清晰,结构严谨,段落分明,而且文件也很短。

可是万一很多属性问题就出来了,一行 XAML 会变得很长。而且看看下面两个 ContentPresenter,同样都有 Margin 属性、HorizontalAlignment 属性,VerticalAlignment 属性,RecognizesAccessKey 属性,SnapsToDevicePixels 顺序ing,但你能看到第二个 ContentPresenter 后面偷偷塞了个 Margin 吗:

XML
Copy<ContentPresenter Margin="{TemplateBinding Padding}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
<ContentPresenter Margin="{TemplateBinding Padding}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" Margin="40"/>

如果在 VisualStudio 中“文本编辑器->XAML->格式化->间距->特性间距”这个选项中选择了“将各个属性分别放置”:

img

格式化文档后上面的 XAML 就会变成这样:

XML
Copy<ContentPresenter Margin="{TemplateBinding Padding}"
                  HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                  VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                  RecognizesAccessKey="True"
                  SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
<ContentPresenter Margin="{TemplateBinding Padding}"
                  HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                  VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                  RecognizesAccessKey="True"
                  SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
                  Margin="40" />

每个属性单独一行不仅不会看漏属性,而且编辑器本身也不会有横向和纵向两种方向的移动,只有从上到下的移动,这就舒服多了。

可是大部分情况下每个属性分行放置会破坏原本清晰的 XAML 层次结构,例如下面这种本来好好的 XAML:

XML
1
2
3
4
5
6
Copy<Setter Property="FontWeight" Value="Normal" />
<Setter Property="UseSystemFocusVisuals" Value="True" />
<Setter Property="FocusVisualMargin" Value="-3" />
<Setter Property="Height" Value="50" />
<Setter Property="Width" Value="50" />
<Setter Property="Maximum" Value="1" />

变成这样:

XML
Copy<Setter Property="FontWeight"
        Value="Normal" />
<Setter Property="UseSystemFocusVisuals"
        Value="True" />
<Setter Property="FocusVisualMargin"
        Value="-3" />
<Setter Property="Height"
        Value="50" />
<Setter Property="Width"
        Value="50" />
<Setter Property="Maximum"
        Value="1" />

这种风格优雅得像诗歌 我偶尔称为豆瓣风 一行变两行 两行变四行 本来 一页看得完 的代码 变成 两页才看得完 也是够 麻烦的。

XAML Styler 很好地解决了这个问题,它通过 “Attribute tolerance” 属性控制每一行的容许的最多的属性数量,如果一个元素的属性数量少于设定值,那就放在一行,如果超过就所有属性单独一行。通常我将这个属性设置为 2,再配合 “Keep first attribute on same line = true” 的设置,可以做到下面这种格式化效果:

XML
Copy<SolidColorBrush x:Key="NormalTextColor" Color="#2E2F33" />
<SolidColorBrush x:Key="PrimaryColor" Color="#FFED5B8C" />
<SolidColorBrush x:Key="LineColor" Color="#E1E1E1" />
<SolidColorBrush x:Key="TransparentBackground" Color="Transparent" />

<ControlTemplate x:Key="CompletedTemplate" TargetType="ContentControl">
    <Grid x:Name="CompletedElement" Margin="-2">
        <control:DropShadowPanel HorizontalContentAlignment="Stretch"
                                 VerticalContentAlignment="Stretch"
                                 BlurRadius="8"
                                 OffsetX="0"
                                 OffsetY="0"
                                 Color="#FFED5B8C">
            <Ellipse x:Name="CompletedRectangle" Fill="{StaticResource PrimaryColor}" />
        </control:DropShadowPanel>
    </Grid>
</ControlTemplate>

这样就可以兼顾两种格式化的优点。

4. 排序#

如果元素有多个属性,要找到它的主要属性(通常是 Name 和 Grid.Row)需要颇费一番功夫。XAML Styler 根据一个可设定的规则自动将元素的各个属性排序,这个规则如下:

JSON
Copy"AttributeOrderingRuleGroups": [
    "x:Class",
    "xmlns, xmlns:x",
    "xmlns:*",
    "x:Key, Key, x:Name, Name, x:Uid, Uid, Title",
    "Grid.Row, Grid.RowSpan, Grid.Column, Grid.ColumnSpan, Canvas.Left, Canvas.Top, Canvas.Right, Canvas.Bottom",
    "Width, Height, MinWidth, MinHeight, MaxWidth, MaxHeight",
    "Margin, Padding, HorizontalAlignment, VerticalAlignment, HorizontalContentAlignment, VerticalContentAlignment, Panel.ZIndex",
    "*:*, *",
    "PageSource, PageIndex, Offset, Color, TargetName, Property, Value, StartPoint, EndPoint",
    "mc:Ignorable, d:IsDataSource, d:LayoutOverrides, d:IsStaticText",
    "Storyboard.*, From, To, Duration"
],

排序结果大致如下:

XML
1
2
3
4
5
6
7
8
9
Copy<Button x:Name="Show"
        Grid.Row="1"
        Padding="40,20"
        HorizontalAlignment="Center"
        VerticalAlignment="Center"
        Background="#00aef1"
        Content="Show"
        Foreground="White"
        Style="{StaticResource BubbleButtonStyle}" />

另外,我不喜欢它自动将 VisualStateManager 排序到后面,虽然这个排序合理,但不符合我的习惯,所以要将 “Record visual state manager” 设置为 None。

5. 统一标准#

最后,就算自己做好了格式化,团队中的其它成员使用了不同的格式化标准也会引起很多问题。针对这个问题 Xaml Styler 也提供了解决方案。

在项目的根目录创建一个名为“Settings.XamlStyler”的文件,内容参考这个网址:https://github.com/Xavalon/XamlStyler/wiki/External-Configurations 中的 Default Configuration。有了这个配置文件,XAML Styler 就会根据它而不是全局配置进行格式化,作为项目的统一格式化标准。