Meteor主题友链页面自研

Meteor主题虽然设计简约现代,但由于缺乏原生的友情链接管理功能,许多博主只能将友情链接勉强添加在网站底部,这不仅影响页面美观,也不便于访客查找和互动;为了解决这一痛点,本博主对主题进行了深度二次开发,专门打造了一个独立的友情链接页面,该页面不仅实现了友链分类展示、图文混排等基本功能,更创新性地开发了前端提交表单,允许其他站长直接在线提交申请,同时配套开发了完善的后台审核系统,管理员可以便捷地查看申请信息、审核状态,并一键通过或拒绝,整个流程实现了自动化闭环管理,大大提升了友链交换的效率和用户体验。

源码下载地址:https://gitcode.com/weixin_72610956/Blog/tree/link

1
2
3
4
default-avatar.jpg
page-links.css
page-links.js
pending-links.php

以下是核心代码部分,这段代码实现了系统的关键功能,

并且这里对友链头像有三个判断的逻辑

  • 有头像 → 正常显示;
  • 无头像 → 立即显示默认头像;
  • 有头像但 3 秒仍未加载完成 → 自动替换成默认头像。
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
<?php
/*
* Template Name: 友情链接
* Description: 极简友链模板(样式/脚本已分离,含评论)
*/

get_header();
?>

<div class="hero-title">
<?php the_title( '<h1>', '</h1>' ); ?>
<p class="hero-sub">欢迎交换友链 · 携手点亮彼此的星空</p>
</div>


<!-- 载入独立样式 -->
<link rel="stylesheet" href="<?php echo get_template_directory_uri(); ?>/self-innovate/page-links.css">

<div class="page-links">

<!-- 搜索框 -->
<div class="link-search">
<input type="text" id="link-search" placeholder="搜索站点名称或描述…">
</div>

<!-- 友链列表 -->
<?php
$cats = get_terms( 'link_category', array( 'hide_empty' => 0 ) );
foreach ( $cats as $cat ) :
$bookmarks = get_bookmarks( array(
'category' => $cat->term_id,
'orderby' => 'rating',
'order' => 'DESC'
) );
if ( empty( $bookmarks ) ) continue;
?>
<div class="link-cat">
<h2><?php echo esc_html( $cat->name ); ?></h2>
<?php if ( ! empty( $cat->description ) ) : ?>
<p class="link-cat-desc"><?php echo esc_html( $cat->description ); ?></p>
<?php endif; ?>
<div class="links">
<?php foreach ( $bookmarks as $link ) : ?>
<a class="link-card" href="<?php echo esc_url( $link->link_url ); ?>" target="_blank" rel="noopener">
<img
src="<?php echo esc_url( $link->link_image ?: '' ); ?>"
data-default="<?php echo esc_url( get_template_directory_uri() . '/self-innovate/default-avatar.jpg' ); ?>"
alt="<?php echo esc_attr( $link->link_name ); ?>"
onerror="this.src=this.dataset.default"
onload="clearTimeout(this.t)"
ontimeout="this.src=this.dataset.default"
/>
<script>
(function(img){
img.t = setTimeout(function(){ img.src = img.dataset.default; }, 3000);
})(document.currentScript.previousElementSibling);
</script>
<div class="info">
<div class="name"><?php echo esc_html( $link->link_name ); ?></div>
<div class="desc"><?php echo esc_html( $link->link_description ); ?></div>
</div>
</a>
<?php endforeach; ?>
</div>
</div>
<?php endforeach; ?>

<!-- 后台内容显示 -->
<?php if ( get_the_content() ) : ?>
<div class="link-intro" style="margin-bottom:30px;"><?php the_content(); ?></div>
<?php endif; ?>


<!-- 友链申请弹窗 -->
<div class="btn-center">
<button id="open-link-modal" class="btn-hero">
<span>申请交换友链</span>
</button>
</div>


<!-- ===== 评论区 ===== -->
<?php
// while ( have_posts() ) : the_post();
if ( comments_open() || get_comments_number() ) : ?>
<section class="link-comments">
<h2 class="comments-title"></h2>
<?php comments_template(); ?>
</section>
<?php
endif;
// endwhile;
?>

<!-- 弹窗 -->
<div id="link-modal-overlay" class="modal-fade">
<div class="modal-dialog">
<button class="modal-close" aria-label="关闭">×</button>
<h3 class="modal-title">申请交换友链</h3>
<form id="link-form" class="modal-form">
<input type="text" name="link_name" placeholder="网站名称 *" required>
<input type="url" name="link_url" placeholder="网站地址 *" required>
<input type="email" name="link_owner_email" placeholder="站长邮箱 *" required>
<input type="url" name="link_image" placeholder="头像 / Logo *" required>
<textarea name="link_description" rows="3" placeholder="一句话描述 *" required></textarea>
<button type="submit" class="btn-submit">提交申请</button>
<p id="form-msg" class="form-msg"></p>
</form>
</div>
</div>
</div>

<!-- 载入独立脚本 -->
<script>
var ajax_comment_obj = <?php echo wp_json_encode(array('ajax_url' => admin_url('admin-ajax.php'))); ?>;
</script>
<script src="<?php echo get_template_directory_uri(); ?>/self-innovate/page-links.js" defer></script>

<?php get_footer(); ?>

下面是核心代码的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
/**
* * page-links.css
* * 版本:1.0.0
* * 描述:友情链接页面专用样式
* * 作者:Eucalyptus
* * 创建日期:2025-08-30
* * 修改记录:
* * - 2025-08-30:首版,含友链卡片、搜索、弹窗
* */

/* ========= 1. 页面通用 ========= */
.page-links{
max-width:960px;
margin:0 auto;
padding:40px 20px;
font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif;
}

/* ============ 顶部标题(跟随深浅 + 移动端适配) ============ */
/* 友情链接横幅 */
.hero-title{
padding:3.5rem 1.5rem 2.5rem;
border-radius:0 0 1.5rem 1.5rem;
text-align:center;
}
.hero-title h1{
font-size:2.2rem;
font-weight:700;
letter-spacing:.5px;
position:relative;
}
.hero-title h1::after{
content:'';
display:block;
width:60px;
height:3px;
background:#667eea;
margin:8px auto 0;
border-radius:2px;
}
.hero-sub{
font-size:1rem;
color:#666;
margin-top:.75rem;
}


/* ========= 玻璃态卡片 3.0 ========= */
.links{
display:grid;
grid-template-columns:repeat(3, 1fr); /* 固定 3 列 */
gap:24px;
}

.link-card{
position:relative;
padding:20px;
background:transparent; /* 跟随深浅 */
border:1px solid rgba(var(--color-border,0 0 0),.1);
border-radius:20px;
box-shadow:0 8px 32px rgba(0,0,0,.08); /* 深浅通用阴影 */
transition:.4s cubic-bezier(.175,.885,.32,1.275);
overflow:hidden;
}

:root{
--color-border: 0 0 0; /* 深色模式 */
}
[data-theme="light"]{
--color-border: 255 255 255; /* 浅色模式 */
}

.link-card::before{ /* 光泽渐变 */
content:'';
position:absolute;
inset:0;
background:linear-gradient(135deg,transparent 40%,rgba(255,255,255,.3));
pointer-events:none;
}

.link-card:hover{
transform:translateY(-6px) scale(1.02);
box-shadow:0 12px 48px rgba(102,126,234,.25);
}

/* 头像 */
.link-card img{
width:64px;
height:64px;
border-radius:50%;
border:2px solid #fff;
box-shadow:0 4px 12px rgba(0,0,0,.08);
object-fit:cover;
}

/* 文字区 */
.link-card .info{
margin-left:16px;
}
.link-card .name{
font-size:18px;
font-weight:700;
letter-spacing:.5px;
margin-bottom:6px;
}
.link-card .desc{
font-size:14px;
color:#555;
line-height:1.5;
}

/* 右上角角标(纯 CSS) */
.link-card{
position: relative; /* 为伪元素定位 */
}
.link-card::after{
content: "友链"; /* 角标文字 */
position: absolute;
top: 8px;
right: 8px;
padding: 2px 6px;
font-size: 11px;
font-weight: 600;
color: #fff;
background: linear-gradient(135deg,#ea66d4,#ac5196);
border-radius: 4px;
opacity: 0;
transition: opacity .3s;
pointer-events: none; /* 不影响点击 */
}
.link-card:hover::after{
opacity: 1;
}

/* ===== 分类标题 3.0 ===== */
.link-cat{
position:relative;
margin-bottom:40px;
}
.link-cat h2{
display:inline-flex;
align-items:center;
gap:8px;
padding:8px 18px;
font-size:20px;
margin-bottom: 15px;
font-weight:700;
color:#fff;
background:linear-gradient(135deg,#667eea,#764ba2);
border-radius:20px;
box-shadow:0 4px 12px rgba(102,126,234,.25);
letter-spacing:.5px;
}

/* 分类描述 */
.link-cat-desc{
margin: -8px 0 24px 18px; /* 负值贴紧标题,左侧与标题文字对齐 */
font-size: 0.9rem;
color: #555;
letter-spacing: .4px;
line-height: 1.5;
max-width: 540px;
}

/* ========= 移动端适配 ========= */
@media (max-width: 768px) {
.links {
grid-template-columns: 1fr; /* 1 列 */
gap: 20px;
}

.link-card {
padding: 16px;
}

.link-card img {
width: 48px;
height: 48px;
}

.link-card .name {
font-size: 16px;
}

.link-card .desc {
font-size: 13px;
}
}
/* ========= 4. 搜索框 ========= */
.link-search{
display:flex;
margin-bottom:40px;
}

.link-search input{
width:100%;
max-width:320px;
padding:12px 40px 12px 16px;
font-size:15px;
color:#333;
background:#fff;
border:2px solid transparent;
border-radius:30px;
box-shadow:0 4px 12px rgba(102,126,234,.15);
transition:border-color .3s, box-shadow .3s;
outline:none;
background-image:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="%23667eea" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>');
background-repeat:no-repeat;
background-position:right 14px center;
background-size:20px 20px;
}

/* 悬浮/聚焦高亮 */
.link-search input:focus{
border-color:#667eea;
box-shadow:0 0 0 4px rgba(102,126,234,.25);
}

/* placeholder 样式 */
.link-search input::placeholder{
color:#7f8c8d;
font-size:14px;
}

/* ========= 5. 按钮 & 弹窗 ========= */
/* 5.1 触发按钮居中容器 */
.btn-center{
display:flex;
justify-content:center;
margin:30px 0;
}

/* 5.2 主按钮样式 */
.btn-hero{
position:relative;
padding:12px 32px;
font-size:16px;
font-weight:600;
color:#fff;
background:linear-gradient(135deg,#667eea 0%, #764ba2 100%);
border:none;
border-radius:30px;
cursor:pointer;
box-shadow:0 6px 20px rgba(102,126,234,.4);
transition:all .3s;
overflow:hidden;
}
.btn-hero:hover{
transform:translateY(-3px) scale(1.03);
box-shadow:0 10px 30px rgba(102,126,234,.55);
}

/* 5.3 弹窗遮罩 */
.modal-fade{
position:fixed;
inset:0;
display:flex;
align-items:center;
justify-content:center;
background:rgba(0,0,0,.55);
backdrop-filter:blur(4px);
opacity:0;
visibility:hidden;
transition:.35s;
z-index:9999;
}
.modal-fade.show{
opacity:1;
visibility:visible;
}

/* 5.4 弹窗主体 */
.modal-dialog{
width:92%;
max-width:420px;
background:#fff;
border-radius:16px;
padding:32px 36px 36px;
position:relative;
transform:translateY(-30px) scale(.95);
transition:transform .35s;
}
.modal-fade.show .modal-dialog{
transform:none;
}

/* 5.5 关闭按钮 */
.modal-close{
position:absolute;
top:14px;
right:18px;
font-size:26px;
background:none;
border:none;
color:#999;
cursor:pointer;
}

/* 5.6 表单元素 */
.modal-form{
display:flex;
flex-direction:column;
gap:14px;
}
.modal-form input,
.modal-form textarea{
padding:12px 14px;
border:1px solid #e1e5e9;
border-radius:8px;
}
.btn-submit{
margin-top:8px;
padding:12px;
color:#fff;
border:none;
border-radius:30px;
background:linear-gradient(135deg,#667eea 0%,#764ba2 100%);
cursor:pointer;
}

/* 移动端补丁,溢出锁死 */
.page-links{
overflow-x: hidden; /* 关键:禁止横向滚动 */
}

以下是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
/* ========= 实时搜索 ========= */
document.getElementById('link-search').addEventListener('input', function () {
const kw = this.value.toLowerCase();
document.querySelectorAll('.link-card').forEach(card => {
card.style.display = card.textContent.toLowerCase().includes(kw) ? 'flex' : 'none';
});
});

/* ========= 弹窗控制 ========= */
const overlay = document.getElementById('link-modal-overlay');
document.getElementById('open-link-modal').addEventListener('click', () => overlay.classList.add('show'));
overlay.addEventListener('click', e => {
if (e.target === overlay || e.target.classList.contains('modal-close')) overlay.classList.remove('show');
});

/* ========= Ajax 提交友链 ========= */
document.getElementById('link-form').addEventListener('submit', function (e) {
e.preventDefault();
const data = new FormData(this);
data.append('action', 'submit_link_apply');
fetch(ajax_comment_obj.ajax_url, { method: 'POST', body: data })
.then(r => r.json())
.then(res => {
document.getElementById('form-msg').textContent = res.data;
if (res.success) {
this.reset();
setTimeout(() => overlay.classList.remove('show'), 1500);
}
});
});

最后是后端处理前端请求并为link_manager新增待审核列的代码,审核逻辑基于链接是否在前端隐藏, 关闭私密功能即可前端展示

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
<?php
/**
* 待审核友链提示
* 包含:后台列表新增“待审核”列 + 红色气泡菜单提示 + Ajax 接收
*/

/* --------------------------------------------------
* 1. 后台列表新增“待审核”列,纯文本,系统默认样式
* -------------------------------------------------- */
/* 新增“待审核”列(键名 review) */
add_filter( 'manage_link-manager_columns', function ( $cols ) {
$cols['review'] = '待审核';
return $cols;
} );

/* 单元格输出红色气泡 */
add_action( 'manage_link_custom_column', function ( $col, $link_id ) {
if ( $col === 'review' ) {
echo get_bookmark_field( 'link_visible', $link_id ) === 'N'
? '<span style="display:inline-block;background:#e60026;color:#fff;font-size:11px;padding:2px 6px;border-radius:10px;margin-left:4px;">待审</span>'
: '';
}
}, 10, 2 );


/* --------------------------------------------------
* 2. 后台“链接”菜单右上角红色数字气泡
* -------------------------------------------------- */
add_action( 'admin_menu', function () {
$count = 0;
foreach ( get_bookmarks( [ 'hide_invisible' => 0 ] ) as $link ) {
if ( $link->link_visible === 'N' ) $count++;
}
if ( $count ) {
global $menu;
foreach ( $menu as &$item ) {
if ( $item[2] === 'link-manager.php' ) {
$item[0] .= ' <span class="awaiting-mod"><span class="pending-count">' . $count . '</span></span>';
break;
}
}
}
} );

/* --------------------------------------------------
* 3. Ajax 接收端(无邮件)
* -------------------------------------------------- */
add_action( 'wp_ajax_nopriv_submit_link_apply', 'handle_link_apply' );
add_action( 'wp_ajax_submit_link_apply', 'handle_link_apply' );
function handle_link_apply() {
$name = sanitize_text_field( $_POST['link_name'] ?? '' );
$url = esc_url_raw ( $_POST['link_url'] ?? '' );
$email = sanitize_email ( $_POST['link_owner_email'] ?? '' );
$desc = sanitize_text_field( $_POST['link_description'] ?? '' );
$img = esc_url_raw ( $_POST['link_image'] ?? '' );

if ( empty( $name ) || empty( $url ) || ! is_email( $email ) ) {
wp_send_json_error( '请完整填写必填项' );
}

wp_insert_link( [
'link_name' => $name,
'link_url' => $url,
'link_description' => $desc,
'link_image' => $img,
'link_owner_email' => $email,
'link_visible' => 'N',
] );

wp_send_json_success( '已收到申请,审核后显示,感谢!' );
}

上传代码至Meteor主题目录完成部署,最后在functions.php中启用link_manager并调用pending-links.php处理后台任务。

WordPress的functions.php文件中添加指定代码可实现特定功能,需注意代码正确性和备份以防出错。

1
2
3
4
5
/* 开启后台链接 */
add_filter( 'pre_option_link_manager_enabled', '__return_true' );

/* 后台新增友链提示功能 */
require_once get_theme_file_path( '/self-innovate/pending-links.php' );

展示地址:https://blog.mingliang.net.cn/%e5%8f%8b%e9%93%be/