前言

本教程将教你如何在 Hexo Butterfly 主题中创建一个专门的页面(Page)来控制全局 APlayer 播放器。通过这个控制台页面,你可以:

  • 显示当前播放的歌曲信息(封面、标题、艺术家)
  • 显示播放进度条和时间
  • 控制播放/暂停、上一曲、下一曲、静音
  • 显示完整的播放列表并支持点击切换

前置条件

  1. 已安装 Hexo 和 Butterfly 主题
  2. 配置 APlayer 相关插件(hexo-tag-aplayer
    1
    npm install hexo-tag-aplayer --save
  3. _config.butterfly.yml 中已启用 aplayerInject

步骤一:配置全局播放器

首先,我们需要在配置文件中添加一个全局播放器,它将在所有页面中保持存在。

编辑 _config.butterfly.yml

找到 inject 配置项,在 bottom 部分添加全局播放器:

1
2
3
4
inject:
bottom:
# ... 其他脚本 ...
- <div class="aplayer global-aplayer no-destroy" data-console="global" data-id="13544074526" data-server="netease" data-type="playlist" data-order="random" data-fixed="true" data-autoplay="true" data-mini="true" data-preload="metadata"></div>

参数说明:

  • class="aplayer global-aplayer no-destroy"global-aplayerno-destroy 用于标识全局播放器
  • data-console="global":标记为全局控制台播放器
  • data-id:播放列表 ID(网易云音乐)
  • data-server:音乐平台(netease、tencent、kugou、xiami、baidu)
  • data-type:类型(playlist、song、album)
  • data-fixed="true":固定播放器,不会被 PJAX 销毁
  • data-mini="true":迷你模式
  • data-autoplay="true":自动播放(可选)

配置隐藏播放器
在 [Blogroot]\themes\butterfly\source\css\custom.css中(没有这个文件就按照路径自己新建)添加如下内容:

1
2
3
4
5
6
7
8
9
.aplayer.aplayer-fixed.aplayer-narrow .aplayer-body {
left: -66px !important;
/* 默认情况下缩进左侧66px,只留一点箭头部分 */
}

.aplayer.aplayer-fixed.aplayer-narrow .aplayer-body:hover {
left: 0 !important;
/* 鼠标悬停是左侧缩进归零,完全显示按钮 */
}

不要忘了到主题配置文件引入自定义样式,修改[Blogroot]_config.butterfly.yml的inject配置项:

1
2
3
inject:
head:
- <link rel="stylesheet" href="/css/custom.css" media="defer" onload="this.media='all'">

步骤二:创建控制台页面

创建页面

在项目根目录执行:

1
hexo new page music

这会在 source/music/ 目录下创建 index.md 文件。

编辑页面内容

编辑 source/music/index.md,设置 Front-matter:

1
2
3
4
5
6
7
---
title: 八音盒
date: 2025-11-13 23:55:09
comments: false
aside: false
aplayer: false
---

说明:

  • aside: false:隐藏侧边栏,让控制台更突出
  • aplayer: false:不在本页面加载额外的 APlayer 资源(使用全局的)

添加 HTML 结构

在 Front-matter 下方添加控制台的 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
<div class="music-console">
<div class="music-player">
<div class="music-cover">
<img id="music-cover" src="/img/avatar.webp" alt="封面" loading="lazy">
</div>
<div class="music-body">
<div class="music-meta">
<div class="music-title" id="music-title">尚未播放</div>
<div class="music-artist" id="music-artist">从列表里随便点一首吧</div>
</div>
<div class="music-progress">
<span class="music-time" id="music-current">00:00</span>
<div class="music-progress-bar">
<div class="music-progress-fill" id="music-progress-fill"></div>
</div>
<span class="music-time" id="music-duration">00:00</span>
</div>
<div class="music-controls">
<button type="button" class="music-btn" data-action="prev" aria-label="上一曲">
<i class="fas fa-backward"></i>
</button>
<button type="button" class="music-btn is-primary" data-action="toggle" aria-label="播放或暂停">
<i class="fas fa-play"></i>
</button>
<button type="button" class="music-btn" data-action="next" aria-label="下一曲">
<i class="fas fa-forward"></i>
</button>
<button type="button" class="music-btn" data-action="mute" aria-label="静音切换">
<i class="fas fa-volume-up"></i>
</button>
</div>
</div>
</div>
<div class="music-playlist">
<div class="music-list-header">
<span>播放列表</span>
<span id="music-count">0 首</span>
</div>
<div class="music-list-body" id="music-list"></div>
</div>
</div>

步骤三:添加控制脚本

在 HTML 结构下方添加 JavaScript 控制脚本:

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
<script data-pjax>
(() => {
const els = {
list: document.getElementById('music-list'),
count: document.getElementById('music-count'),
cover: document.getElementById('music-cover'),
title: document.getElementById('music-title'),
artist: document.getElementById('music-artist'),
current: document.getElementById('music-current'),
duration: document.getElementById('music-duration'),
progressFill: document.getElementById('music-progress-fill'),
progressBar: document.querySelector('.music-progress-bar'),
controls: document.querySelector('.music-controls'),
playBtn: document.querySelector('[data-action="toggle"]'),
prevBtn: document.querySelector('[data-action="prev"]'),
nextBtn: document.querySelector('[data-action="next"]'),
muteBtn: document.querySelector('[data-action="mute"]')
};

const formatTime = value => {
if (!Number.isFinite(value)) return '00:00';
const minutes = Math.floor(value / 60);
const seconds = Math.floor(value % 60);
return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
};

const renderPlaylist = player => {
if (!els.list || !player) return;

const audios = player.list?.audios || [];
if (els.count) {
els.count.textContent = `${audios.length} 首`;
}

els.list.innerHTML = audios
.map(
(item, idx) => `
<div class="music-item" data-index="${idx}">
<span class="music-item__order">${idx + 1}</span>
<div class="music-item__meta">
<p class="music-item__title">${item.name || '未知标题'}</p>
<p class="music-item__artist">${item.artist || '未知艺术家'}</p>
</div>
</div>
`
)
.join('');

els.list.querySelectorAll('.music-item').forEach((node) => {
node.addEventListener('click', () => player.list.switch(Number(node.dataset.index)));
});

markActive(player);
};

const markActive = player => {
if (!els.list || !player) return;
const active = player.list?.index;
els.list.querySelectorAll('.music-item').forEach((node, idx) => {
node.classList.toggle('is-active', idx === active);
});
};

const updateNowPlaying = player => {
const index = player.list?.index ?? 0;
const audio = player.list?.audios?.[index];
if (!audio) return;

if (els.title) {
els.title.textContent = audio.name || '未知标题';
}

if (els.artist) {
els.artist.textContent = audio.artist || '未知艺术家';
}

if (els.cover) {
const fallback = '/img/loading.webp';
const coverSrc = audio.cover || fallback;
els.cover.src = coverSrc;
if (els.cover.dataset) {
els.cover.dataset.lazySrc = coverSrc;
}
els.cover.alt = audio.name || '封面';
}
};

const updatePlayButton = player => {
if (!els.playBtn || !player?.audio) return;
const icon = els.playBtn.querySelector('i');
if (!icon) return;
const isPaused = player.audio.paused;
icon.classList.toggle('fa-play', isPaused);
icon.classList.toggle('fa-pause', !isPaused);
};

const updateMuteButton = player => {
if (!els.muteBtn || !player?.audio) return;
const icon = els.muteBtn.querySelector('i');
if (!icon) return;
const muted = player.audio.muted || player.audio.volume === 0;
icon.classList.toggle('fa-volume-up', !muted);
icon.classList.toggle('fa-volume-mute', muted);
};

const updateDuration = player => {
if (!els.duration || !player?.audio) return;
const duration = player.audio.duration;
if (Number.isFinite(duration)) {
els.duration.textContent = formatTime(duration);
} else {
els.duration.textContent = '00:00';
}
};

const updateProgress = player => {
if (!player?.audio) return;
const audio = player.audio;
const current = audio.currentTime || 0;
const duration = audio.duration || 0;
const percent = duration ? Math.min(100, (current / duration) * 100) : 0;

if (els.progressFill) {
els.progressFill.style.width = `${percent}%`;
}
if (els.current) {
els.current.textContent = formatTime(current);
}
if (els.duration && duration) {
els.duration.textContent = formatTime(duration);
}
};

const locateGlobalPlayer = () => {
const players = window.aplayers || [];
return players.find((ap) => {
const container = ap?.container;
if (!container) return false;
if (container.classList?.contains('global-aplayer')) return true;
if (container.classList?.contains('no-destroy')) return true;
if (container.dataset?.console === 'global') return true;
const parent = container.closest?.('.aplayer');
return parent?.dataset?.console === 'global';
});
};

const bindControls = player => {
if (!els.controls || els.controls.dataset.bound === 'true') return;
els.controls.dataset.bound = 'true';

const ensurePlayer = action => event => {
event?.preventDefault?.();
if (!player) return;
action(player);
};

if (els.prevBtn) {
els.prevBtn.addEventListener('click', ensurePlayer(ap => ap.skipBack()));
}

if (els.nextBtn) {
els.nextBtn.addEventListener('click', ensurePlayer(ap => ap.skipForward()));
}

if (els.playBtn) {
els.playBtn.addEventListener('click', ensurePlayer(ap => {
if (!ap.audio) return;
ap.audio.paused ? ap.play() : ap.pause();
}));
}

if (els.muteBtn) {
els.muteBtn.addEventListener('click', ensurePlayer(ap => {
if (!ap.audio) return;
ap.audio.muted = !ap.audio.muted;
updateMuteButton(ap);
}));
}

if (els.progressBar && els.progressBar.dataset.bound !== 'true') {
els.progressBar.dataset.bound = 'true';
els.progressBar.addEventListener('click', event => {
if (!player?.audio) return;
const rect = els.progressBar.getBoundingClientRect();
const ratio = Math.max(0, Math.min(1, (event.clientX - rect.left) / rect.width));
const duration = player.audio.duration;
if (Number.isFinite(duration)) {
player.seek(duration * ratio);
}
});
}
};

const initMusicConsole = () => {
const target = locateGlobalPlayer();
if (!target) {
setTimeout(initMusicConsole, 200);
return;
}

if (target._consoleBound) {
renderPlaylist(target);
updateNowPlaying(target);
updatePlayButton(target);
updateMuteButton(target);
updateProgress(target);
return;
}

window.globalAPlayer = target;
target._consoleBound = true;
bindControls(target);

const waitForAudios = () => {
if (target.list?.audios?.length) {
renderPlaylist(target);
updateNowPlaying(target);
updateDuration(target);
updateProgress(target);
updateMuteButton(target);
updatePlayButton(target);
} else {
setTimeout(waitForAudios, 200);
}
};

waitForAudios();

const update = () => {
renderPlaylist(target);
markActive(target);
};
const highlight = () => markActive(target);

target.on('listadd', () => {
update();
updateNowPlaying(target);
updateDuration(target);
});
target.on('listremove', update);
target.on('listswitch', () => {
highlight();
updateNowPlaying(target);
updateDuration(target);
updateProgress(target);
});
target.on('play', () => {
highlight();
updatePlayButton(target);
});
target.on('pause', () => updatePlayButton(target));
target.on('timeupdate', () => updateProgress(target));
target.on('loadeddata', () => {
updateDuration(target);
updateNowPlaying(target);
updateProgress(target);
});

if (target.audio) {
target.audio.addEventListener('volumechange', () => updateMuteButton(target));
}
};

if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initMusicConsole, { once: true });
} else {
initMusicConsole();
}
document.addEventListener('pjax:complete', initMusicConsole);
})();
</script>

关键点说明:

  • data-pjax 属性确保脚本在 PJAX 切换页面时重新执行
  • locateGlobalPlayer() 函数通过多种方式查找全局播放器实例
  • 使用轮询机制等待播放器初始化完成
  • 绑定 APlayer 的各种事件来更新 UI

步骤四:添加样式

themes/butterfly/source/css/custom.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
/* ------------------------
Music Console Page
------------------------ */
.music-console {
display: flex;
flex-direction: column;
gap: 1.5rem;
}

.music-player {
display: flex;
gap: 1.5rem;
padding: 1.5rem;
border-radius: 20px;
background: rgba(255, 255, 255, 0.85);
border: 1px solid rgba(255, 255, 255, 0.65);
backdrop-filter: blur(18px) saturate(160%);
box-shadow: 0 20px 35px -28px rgba(15, 23, 42, 0.7);
align-items: center;
}

[data-theme="dark"] .music-player {
background: rgba(20, 24, 35, 0.85);
border: 1px solid rgba(148, 163, 184, 0.25);
box-shadow: 0 24px 40px -28px rgba(0, 0, 0, 0.85);
}

.music-cover img {
width: 128px;
height: 128px;
border-radius: 22px;
object-fit: cover;
box-shadow: 0 20px 35px -18px rgba(15, 23, 42, 0.55);
transition: transform 0.45s ease, box-shadow 0.45s ease;
}

.music-cover img:hover {
transform: translateY(-4px);
box-shadow: 0 30px 40px -24px rgba(15, 23, 42, 0.5);
}

.music-body {
display: flex;
flex-direction: column;
gap: 1rem;
flex: 1 1 auto;
}

.music-meta {
display: flex;
flex-direction: column;
gap: 0.3rem;
}

.music-title {
font-size: 1.45rem;
font-weight: 700;
line-height: 1.4;
color: var(--music-title-color, #1f1f1f);
}

[data-theme="dark"] .music-title {
color: #e5ecff;
}

.music-artist {
font-size: 0.95rem;
color: rgba(31, 31, 31, 0.65);
}

[data-theme="dark"] .music-artist {
color: rgba(226, 232, 240, 0.65);
}

.music-progress {
display: grid;
grid-template-columns: 56px minmax(0, 1fr) 56px;
align-items: center;
gap: 0.75rem;
}

.music-time {
font-variant-numeric: tabular-nums;
font-size: 0.85rem;
color: rgba(31, 31, 31, 0.55);
}

[data-theme="dark"] .music-time {
color: rgba(226, 232, 240, 0.55);
}

.music-progress-bar {
position: relative;
height: 8px;
border-radius: 999px;
background: rgba(15, 23, 42, 0.08);
overflow: hidden;
cursor: pointer;
}

[data-theme="dark"] .music-progress-bar {
background: rgba(255, 255, 255, 0.12);
}

.music-progress-fill {
position: absolute;
inset: 0 auto 0 0;
width: 0%;
background: linear-gradient(135deg, #49b1f5 0%, #6d9dfc 70%, #a487f9 100%);
transition: width 0.18s ease;
}

.music-controls {
display: flex;
align-items: center;
gap: 0.75rem;
flex-wrap: wrap;
}

.music-btn {
width: 44px;
height: 44px;
border-radius: 999px;
border: 1px solid transparent;
background: rgba(73, 177, 245, 0.12);
color: #49b1f5;
display: flex;
align-items: center;
justify-content: center;
font-size: 1rem;
outline: none;
cursor: pointer;
transition: transform 0.2s ease, box-shadow 0.2s ease, background 0.2s ease;
}

.music-btn:hover {
transform: translateY(-2px);
box-shadow: 0 12px 20px -16px rgba(73, 177, 245, 0.8);
background: rgba(73, 177, 245, 0.22);
}

[data-theme="dark"] .music-btn {
background: rgba(94, 234, 212, 0.08);
color: #95e1f8;
}

[data-theme="dark"] .music-btn:hover {
background: rgba(94, 234, 212, 0.18);
box-shadow: 0 10px 20px -18px rgba(94, 234, 212, 0.6);
}

.music-btn.is-primary {
width: 56px;
height: 56px;
font-size: 1.15rem;
font-weight: 600;
background: linear-gradient(135deg, #49b1f5, #7c67ff);
color: #fff;
box-shadow: 0 14px 28px -18px rgba(73, 177, 245, 0.95);
}

.music-btn.is-primary:hover {
box-shadow: 0 18px 32px -18px rgba(73, 177, 245, 0.95);
}

.music-playlist {
border-radius: 20px;
background: rgba(255, 255, 255, 0.75);
border: 1px solid rgba(226, 232, 240, 0.6);
backdrop-filter: blur(18px) saturate(140%);
overflow: hidden;
}

[data-theme="dark"] .music-playlist {
background: rgba(20, 24, 35, 0.8);
border: 1px solid rgba(148, 163, 184, 0.18);
}

.music-list-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1.1rem 1.3rem;
font-weight: 600;
font-size: 0.95rem;
color: rgba(15, 23, 42, 0.75);
border-bottom: 1px solid rgba(226, 232, 240, 0.6);
}

[data-theme="dark"] .music-list-header {
color: rgba(226, 232, 240, 0.75);
border-color: rgba(148, 163, 184, 0.18);
}

.music-list-body {
max-height: 420px;
overflow-y: auto;
padding: 0.4rem 0;
display: flex;
flex-direction: column;
}

.music-item {
display: grid;
grid-template-columns: 3rem minmax(0, 1fr);
align-items: center;
gap: 0.75rem;
padding: 0.6rem 1.1rem;
transition: background 0.2s ease, transform 0.2s ease;
cursor: pointer;
}

.music-item:hover {
transform: translateX(4px);
background: rgba(73, 177, 245, 0.09);
}

.music-item.is-active {
background: linear-gradient(135deg, rgba(73, 177, 245, 0.2), rgba(124, 103, 255, 0.2));
}

[data-theme="dark"] .music-item:hover {
background: rgba(148, 163, 184, 0.12);
}

[data-theme="dark"] .music-item.is-active {
background: linear-gradient(135deg, rgba(73, 177, 245, 0.25), rgba(124, 103, 255, 0.28));
}

.music-item__order {
font-weight: 600;
color: rgba(15, 23, 42, 0.4);
font-variant-numeric: tabular-nums;
}

[data-theme="dark"] .music-item__order {
color: rgba(226, 232, 240, 0.4);
}

.music-item__title {
margin: 0;
font-size: 1rem;
font-weight: 600;
color: rgba(15, 23, 42, 0.92);
}

.music-item__artist {
margin: 0.15rem 0 0;
font-size: 0.85rem;
color: rgba(15, 23, 42, 0.55);
}

[data-theme="dark"] .music-item__title {
color: rgba(226, 232, 240, 0.95);
}

[data-theme="dark"] .music-item__artist {
color: rgba(226, 232, 240, 0.55);
}

.music-list-body::-webkit-scrollbar {
width: 6px;
}

.music-list-body::-webkit-scrollbar-thumb {
border-radius: 999px;
background: rgba(73, 177, 245, 0.4);
}

[data-theme="dark"] .music-list-body::-webkit-scrollbar-thumb {
background: rgba(148, 163, 184, 0.35);
}

@media screen and (max-width: 768px) {
.music-player {
flex-direction: column;
align-items: center;
text-align: center;
}

.music-cover img {
width: 110px;
height: 110px;
}

.music-progress {
grid-template-columns: 52px minmax(0, 1fr) 52px;
}

.music-controls {
justify-content: center;
}

.music-playlist {
border-radius: 16px;
}
}

@media screen and (max-width: 480px) {
.music-btn {
width: 40px;
height: 40px;
}

.music-btn.is-primary {
width: 46px;
height: 46px;
font-size: 1rem;
}

.music-title {
font-size: 1.25rem;
}
}

步骤五:生成和预览

完成以上步骤后,执行以下命令:

1
2
3
hexo cl
hexo g
hexo s

然后访问 /music/ 页面即可看到控制台。

功能说明

控制台功能

  1. 播放器信息显示

    • 封面图片(自动更新)
    • 歌曲标题和艺术家
    • 当前播放时间和总时长
    • 播放进度条(可点击跳转)
  2. 控制按钮

    • 上一曲(skipBack()
    • 播放/暂停(切换图标)
    • 下一曲(skipForward()
    • 静音切换(切换图标)
  3. 播放列表

    • 显示所有歌曲
    • 高亮当前播放的歌曲
    • 点击列表项切换歌曲
    • 显示歌曲总数

工作原理

  1. 全局播放器定位:脚本通过 window.aplayers 数组查找带有 global-aplayerno-destroy 类的播放器实例
  2. 事件绑定:监听 APlayer 的各种事件(playpausetimeupdatelistswitch 等)来更新 UI
  3. PJAX 兼容:使用 data-pjax 属性确保页面切换后脚本重新执行
  4. 异步等待:使用轮询机制等待播放器初始化和歌单加载完成

自定义配置

修改播放列表

_config.butterfly.yml 中修改 data-iddata-server 参数:

1
- <div class="aplayer global-aplayer no-destroy" data-console="global" data-id="你的播放列表ID" data-server="netease" ...></div>

调整样式

所有样式都在 custom.css 中,你可以:

  • 修改颜色主题
  • 调整尺寸和间距
  • 修改圆角和阴影效果
  • 添加动画效果

添加更多功能

可以在脚本中添加:

  • 音量控制滑块
  • 播放模式切换(单曲循环、随机播放等)
  • 歌词显示
  • 播放历史记录

常见问题

Q1: 控制台找不到播放器?

A: 确保:

  1. 全局播放器已正确添加到 inject.bottom
  2. 播放器有 global-aplayerno-destroy
  3. aplayerInject.enable: true 已启用

Q2: 播放列表不显示?

A: 检查:

  1. 播放列表 ID 是否正确
  2. 网络连接是否正常(需要访问音乐 API)
  3. 浏览器控制台是否有错误信息

Q3: 按钮点击无反应?

A: 确认:

  1. 脚本已正确加载(检查浏览器控制台)
  2. 播放器实例已找到(window.globalAPlayer 不为空)
  3. 事件监听器已正确绑定

Q4: PJAX 切换后控制台失效?

A: 确保脚本标签有 data-pjax 属性,并且 Butterfly 的 PJAX 功能已启用。