23.内容拓展:代码运行器功能实现指南 本指南提供了在AnZhiYu主题中集成代码运行器功能的完整实现步骤,包括两级导航菜单、多服务商支持、响应式设计等功能。
步骤1:修改主题配置文件 文件路径: _config.anzhiyu.yml
在配置文件中添加以下内容:
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 code_runner: enable: true title: "代码运行器" button_title: "代码运行器" panel_width: "600px" auto_load_first: false close_on_escape: true remember_selection: true categories: - name: "Trinket" icon: "fas fa-leaf" description: "适合Python/HTML/CSS/JS等基础代码运行,界面简洁易用" instances: - name: "Python 3" url: "https://trinket.io/embed/python3/f417f7026885" description: "Python 3 在线编程环境" - name: "HTML/CSS/JS" url: "https://trinket.io/embed/html/1aac0e8640a7" description: "前端三件套在线编辑器" - name: "Java" url: "https://trinket.io/embed/java/33cfa8ec292c" description: "Java 在线编程环境" - name: "JDoodle" icon: "fas fa-terminal" description: "支持70+种编程语言,功能强大的在线编译器" instances: - name: "C++ Compiler" url: "https://www.jdoodle.com/online-compiler-c++/" description: "C++ 在线编译器" - name: "Java Compiler" url: "https://www.jdoodle.com/online-java-compiler/" description: "Java 在线编译器" - name: "Python 3" url: "https://www.jdoodle.com/python3-programming-online/" description: "Python 3 在线编程" - name: "Go Playground" url: "https://www.jdoodle.com/compile-go-online/" description: "Go 语言在线编程" - name: "CodePen" icon: "fab fa-codepen" description: "前端开发者的在线代码编辑器和社区" instances: - name: "HTML/CSS/JS" url: "https://codepen.io/pen/" description: "CodePen 在线编辑器" - name: "React Playground" url: "https://codepen.io/pen/?template=react" description: "React 在线开发环境"
修改rightside按钮配置:
1 2 3 4 rightside_item_order: enable: true hide: readmode,translate,darkmode,hideAside show: toc,chat,comment,downloadMd,docToc,codeRunner
修改inject配置:
1 2 3 4 5 6 7 8 9 10 inject: head: - '<link rel="stylesheet" href="/css/code-runner.css">' bottom: - '<script src="/js/code-runner.js"></script>'
步骤2:修改rightside.pug文件 文件路径: themes/anzhiyu/layout/includes/rightside.pug
在rightside.pug文件的case语句中添加codeRunner分支:
1 2 3 4 when 'codeRunner' if theme.code_runner && theme.code_runner.enable button#code-runner-btn(type="button" title=theme.code_runner.button_title || "代码运行器") i.fas.fa-code
步骤3:修改layout.pug文件 文件路径: themes/anzhiyu/layout/includes/layout.pug
在rightside.pug之后添加面板HTML结构:
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 //- 代码运行器面板 if theme.code_runner && theme.code_runner.enable #code-runner-panel.code-runner-panel .panel-header .panel-title= theme.code_runner.title || "代码运行器" button.panel-close-btn(type="button" title="关闭") i.fas.fa-times .panel-body //- 左侧导航菜单 nav.panel-nav each category in theme.code_runner.categories .nav-category(data-category=category.name) .category-header( data-description=category.description title=category.description ) if category.icon i(class=category.icon) span.category-name= category.name i.expand-icon.fas.fa-chevron-down ul.instance-list each instance in category.instances li.instance-item a.instance-link( href="javascript:void(0);" data-url=instance.url data-name=instance.name title=instance.description || instance.name )= instance.name //- 右侧内容区 .panel-content .welcome-message .welcome-icon i.fas.fa-code .welcome-text h3 欢迎使用代码运行器 p 请从左侧菜单选择一个编程环境开始编码 .iframe-container iframe#code-runner-iframe( frameborder="0" width="100%" height="100%" sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-modals" ) .loading-indicator .loading-spinner i.fas.fa-spinner.fa-spin .loading-text 正在加载编程环境...
步骤4:修改config.pug文件 文件路径: themes/anzhiyu/layout/includes/head/config.pug
在GLOBAL_CONFIG对象中添加code_runner配置:
1 code_runner: !{theme.code_runner ? JSON.stringify(theme.code_runner) : 'null'}
将此行添加到GLOBAL_CONFIG对象的其他配置项中。
步骤5:创建CSS样式文件 文件路径: themes/anzhiyu/source/css/code-runner.css
创建完整的CSS文件(内容较长,见下一部分)。
步骤6:创建JavaScript功能文件 文件路径: themes/anzhiyu/source/js/code-runner.js
创建完整的JavaScript文件(内容较长,见下一部分)。
完整代码文件 CSS样式文件内容 文件: themes/anzhiyu/source/css/code-runner.css
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 #code-runner-panel { position : fixed; top : 0 ; right : 0 ; width : 600px ; height : 100vh ; background : var (--anzhiyu-card-bg); backdrop-filter : blur (20px ); border-left : var (--style-border-always); box-shadow : var (--anzhiyu-shadow-lightblack); z-index : 1001 ; transform : translateX (100% ); transition : transform 0.3s cubic-bezier (0.4 , 0 , 0.2 , 1 ); display : flex; flex-direction : column; overflow : hidden; } #code-runner-panel .active { transform : translateX (0 ); } .panel-header { display : flex; align-items : center; justify-content : space-between; padding : 1rem 1.5rem ; border-bottom : var (--style-border-always); background : var (--anzhiyu-card-bg); backdrop-filter : blur (20px ); position : relative; z-index : 1 ; } .panel-title { font-size : 1.1rem ; font-weight : 600 ; color : var (--anzhiyu-fontcolor); margin : 0 ; } .panel-close-btn { width : 2rem ; height : 2rem ; border : none; background : transparent; color : var (--anzhiyu-fontcolor); cursor : pointer; border-radius : var (--anzhiyu-border-radius); display : flex; align-items : center; justify-content : center; transition : all 0.3s ease; } .panel-close-btn :hover { background : var (--anzhiyu-secondbg); color : var (--anzhiyu-red); } .panel-body { flex : 1 ; display : flex; overflow : hidden; } .panel-nav { width : 200px ; flex-shrink : 0 ; border-right : var (--style-border-always); padding : 1rem ; overflow-y : auto; background : var (--anzhiyu-card-bg); } .nav-category { margin-bottom : 0.5rem ; } .category-header { display : flex; align-items : center; padding : 0.75rem ; cursor : pointer; border-radius : var (--anzhiyu-border-radius); transition : all 0.3s ease; position : relative; user-select : none; } .category-header :hover { background : var (--anzhiyu-secondbg); } .category-header i :first-child { margin-right : 0.5rem ; color : var (--anzhiyu-main); } .category-name { flex : 1 ; font-weight : 500 ; color : var (--anzhiyu-fontcolor); } .expand-icon { margin-left : 0.5rem ; transition : transform 0.3s ease; color : var (--anzhiyu-fontcolor); opacity : 0.7 ; } .nav-category .expanded .expand-icon { transform : rotate (180deg ); } .instance-list { list-style : none; padding : 0 ; margin : 0 ; max-height : 0 ; overflow : hidden; transition : max-height 0.3s ease; } .nav-category .expanded .instance-list { max-height : 300px ; } .instance-item { margin : 0 ; } .instance-link { display : block; padding : 0.5rem 0.75rem ; margin-left : 1.5rem ; color : var (--anzhiyu-fontcolor); text-decoration : none; border-radius : var (--anzhiyu-border-radius); transition : all 0.3s ease; font-size : 0.9rem ; border-left : 2px solid transparent; } .instance-link :hover { background : var (--anzhiyu-secondbg); color : var (--anzhiyu-main); border-left-color : var (--anzhiyu-main); } .instance-link .active { background : var (--anzhiyu-main); color : var (--anzhiyu-white); font-weight : 500 ; } .panel-content { flex : 1 ; position : relative; display : flex; flex-direction : column; } .welcome-message { flex : 1 ; display : flex; flex-direction : column; align-items : center; justify-content : center; text-align : center; padding : 2rem ; color : var (--anzhiyu-fontcolor); } .welcome-icon i { font-size : 3rem ; color : var (--anzhiyu-main); margin-bottom : 1rem ; } .welcome-text h3 { margin : 0 0 0.5rem 0 ; font-size : 1.2rem ; font-weight : 600 ; } .welcome-text p { margin : 0 ; opacity : 0.7 ; font-size : 0.9rem ; } .iframe-container { flex : 1 ; position : relative; display : none; } .iframe-container .active { display : block; } .iframe-container iframe { width : 100% ; height : 100% ; border : none; background : var (--anzhiyu-white); } .loading-indicator { position : absolute; top : 0 ; left : 0 ; right : 0 ; bottom : 0 ; display : none; flex-direction : column; align-items : center; justify-content : center; background : var (--anzhiyu-card-bg); color : var (--anzhiyu-fontcolor); z-index : 2 ; } .loading-indicator .active { display : flex; } .loading-spinner i { font-size : 2rem ; color : var (--anzhiyu-main); margin-bottom : 1rem ; } .loading-text { font-size : 0.9rem ; opacity : 0.8 ; } body .code-runner-open { overflow : hidden; } @media (max-width : 768px ) { #code-runner-panel { width : 100vw ; transform : translateX (100% ); } #code-runner-panel .active { transform : translateX (0 ); } .panel-nav { width : 150px ; } .panel-header { padding : 0.75rem 1rem ; } .panel-title { font-size : 1rem ; } } @media (max-width : 480px ) { .panel-nav { width : 120px ; } .category-header { padding : 0.5rem ; } .category-name { font-size : 0.85rem ; } .instance-link { font-size : 0.8rem ; padding : 0.4rem 0.5rem ; } } [data-theme="dark" ] #code-runner-panel .iframe-container iframe { background : var (--anzhiyu-card-bg); } #code-runner-btn .active { background-color : var (--anzhiyu-main); color : var (--anzhiyu-white); } .category-header ::after { content : attr (data-description); position : absolute; left : 100% ; top : 50% ; transform : translateY (-50% ); margin-left : 10px ; background : var (--anzhiyu-card-bg); color : var (--anzhiyu-fontcolor); padding : 0.5rem 0.75rem ; border-radius : var (--anzhiyu-border-radius); font-size : 0.8rem ; white-space : nowrap; box-shadow : var (--anzhiyu-shadow-lightblack); opacity : 0 ; visibility : hidden; transition : all 0.3s ease; z-index : 1002 ; border : var (--style-border-always); } .category-header :hover ::after { opacity : 1 ; visibility : visible; }
JavaScript功能文件内容 文件: themes/anzhiyu/source/js/code-runner.js
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 function initCodeRunner ( ) { const codeRunnerPanel = document .getElementById ('code-runner-panel' ); const codeRunnerButton = document .getElementById ('code-runner-btn' ); if (!codeRunnerPanel || !codeRunnerButton) { console .log ('Code Runner: Panel or button not found' ); return ; } console .log ('Code Runner: Initializing...' ); initCodeRunnerPanel (); function initCodeRunnerPanel ( ) { setupInitialState (); setupToggleButton (); setupPanelInteractions (); restoreUserSelection (); } function setupInitialState ( ) { codeRunnerPanel.classList .remove ('active' ); document .body .classList .remove ('code-runner-open' ); const iframeContainer = codeRunnerPanel.querySelector ('.iframe-container' ); const loadingIndicator = codeRunnerPanel.querySelector ('.loading-indicator' ); const welcomeMessage = codeRunnerPanel.querySelector ('.welcome-message' ); const iframe = codeRunnerPanel.querySelector ('#code-runner-iframe' ); if (iframeContainer) iframeContainer.classList .remove ('active' ); if (loadingIndicator) loadingIndicator.classList .remove ('active' ); if (welcomeMessage) welcomeMessage.style .display = 'flex' ; if (iframe) iframe.src = '' ; } function setupToggleButton ( ) { codeRunnerButton.addEventListener ('click' , () => { const isOpen = codeRunnerPanel.classList .contains ('active' ); if (isOpen) { closePanel (); } else { openPanel (); } }); } function openPanel ( ) { codeRunnerPanel.classList .add ('active' ); document .body .classList .add ('code-runner-open' ); codeRunnerButton.classList .add ('active' ); console .log ('Code Runner: Panel opened' ); const config = window .GLOBAL_CONFIG ?.code_runner || {}; if (config.auto_load_first !== false ) { setTimeout (() => { autoLoadFirstInstance (); }, 1000 ); } } function closePanel ( ) { codeRunnerPanel.classList .remove ('active' ); document .body .classList .remove ('code-runner-open' ); codeRunnerButton.classList .remove ('active' ); console .log ('Code Runner: Panel closed' ); } function setupPanelInteractions ( ) { const closeBtn = codeRunnerPanel.querySelector ('.panel-close-btn' ); if (closeBtn) { closeBtn.addEventListener ('click' , closePanel); } const categoryHeaders = codeRunnerPanel.querySelectorAll ('.category-header' ); categoryHeaders.forEach (header => { header.addEventListener ('click' , () => { const category = header.closest ('.nav-category' ); toggleCategory (category); }); }); const instanceLinks = codeRunnerPanel.querySelectorAll ('.instance-link' ); instanceLinks.forEach (link => { link.addEventListener ('click' , (e ) => { e.preventDefault (); selectInstance (link); }); }); const config = window .GLOBAL_CONFIG ?.code_runner || {}; if (config.close_on_escape !== false ) { document .addEventListener ('keydown' , (e ) => { if (e.key === 'Escape' && codeRunnerPanel.classList .contains ('active' )) { closePanel (); } }); } document .addEventListener ('click' , (e ) => { if (codeRunnerPanel.classList .contains ('active' ) && !codeRunnerPanel.contains (e.target ) && !codeRunnerButton.contains (e.target )) { closePanel (); } }); } function toggleCategory (category ) { if (!category) return ; const allCategories = codeRunnerPanel.querySelectorAll ('.nav-category' ); allCategories.forEach (cat => { if (cat !== category) { cat.classList .remove ('expanded' ); } }); category.classList .toggle ('expanded' ); console .log ('Code Runner: Category toggled' , category.dataset .category ); } function selectInstance (link ) { const url = link.dataset .url ; const name = link.dataset .name ; if (!url) { console .warn ('Code Runner: No URL found for instance' , name); return ; } const allLinks = codeRunnerPanel.querySelectorAll ('.instance-link' ); allLinks.forEach (l => l.classList .remove ('active' )); link.classList .add ('active' ); loadIframe (url); console .log ('Code Runner: Instance selected' , name, url); } function loadIframe (url, retryCount = 0 ) { const iframe = codeRunnerPanel.querySelector ('#code-runner-iframe' ); const loadingIndicator = codeRunnerPanel.querySelector ('.loading-indicator' ); const welcomeMessage = codeRunnerPanel.querySelector ('.welcome-message' ); const iframeContainer = codeRunnerPanel.querySelector ('.iframe-container' ); if (!iframe) { console .error ('Code Runner: iframe not found' ); return ; } if (welcomeMessage) welcomeMessage.style .display = 'none' ; if (iframeContainer) iframeContainer.classList .remove ('active' ); if (loadingIndicator) loadingIndicator.classList .add ('active' ); iframe.onload = null ; iframe.onerror = null ; const loadingTimeout = setTimeout (() => { onIframeError (); }, 20000 ); const onIframeLoad = ( ) => { clearTimeout (loadingTimeout); if (loadingIndicator) loadingIndicator.classList .remove ('active' ); if (iframeContainer) iframeContainer.classList .add ('active' ); console .log ('Code Runner: iframe loaded successfully' ); }; const onIframeError = ( ) => { clearTimeout (loadingTimeout); if (retryCount < 2 ) { console .warn (`Code Runner: iframe load failed, retrying... (${retryCount + 1 } /2)` ); setTimeout (() => { loadIframe (url, retryCount + 1 ); }, 2000 ); return ; } if (loadingIndicator) loadingIndicator.classList .remove ('active' ); if (welcomeMessage) { welcomeMessage.style .display = 'flex' ; const welcomeText = welcomeMessage.querySelector ('.welcome-text' ); if (welcomeText) { welcomeText.innerHTML = ` <h3>加载失败</h3> <p>无法加载编程环境,请检查网络连接或尝试其他选项</p> <button onclick="location.reload()" style="margin-top: 10px; padding: 5px 10px; background: var(--anzhiyu-main); color: white; border: none; border-radius: 4px; cursor: pointer;">刷新页面重试</button> ` ; } } console .error ('Code Runner: iframe failed to load after retries' , url); }; iframe.onload = onIframeLoad; iframe.onerror = onIframeError; setTimeout (() => { iframe.src = url; }, 500 ); } function autoLoadFirstInstance ( ) { const firstCategory = codeRunnerPanel.querySelector ('.nav-category' ); const firstInstance = firstCategory?.querySelector ('.instance-link' ); if (firstCategory && firstInstance) { firstCategory.classList .add ('expanded' ); setTimeout (() => { firstInstance.click (); }, 300 ); } } function restoreUserSelection ( ) { const config = window .GLOBAL_CONFIG ?.code_runner || {}; if (config.remember_selection === false ) return ; try { const saved = localStorage .getItem ('code-runner-selection' ); if (!saved) return ; const selection = JSON .parse (saved); if (!selection.category || !selection.instance ) return ; const category = codeRunnerPanel.querySelector (`[data-category="${selection.category} "]` ); if (category) { category.classList .add ('expanded' ); const instance = category.querySelector (`[data-name="${selection.instance} "]` ); if (instance) { instance.classList .add ('active' ); } } } catch (error) { console .warn ('Code Runner: Failed to restore selection' , error); } } } let codeRunnerInitialized = false ;document .addEventListener ('DOMContentLoaded' , () => { if (!codeRunnerInitialized) { initCodeRunner (); codeRunnerInitialized = true ; } }); document .addEventListener ('pjax:complete' , () => { codeRunnerInitialized = false ; setTimeout (() => { if (!codeRunnerInitialized) { initCodeRunner (); codeRunnerInitialized = true ; } }, 500 ); });