增加便民服务页面

This commit is contained in:
2026-03-04 14:55:15 +08:00
parent 979d5dd26a
commit c885cfd864
4 changed files with 984 additions and 3 deletions
+36 -2
View File
@@ -26,7 +26,7 @@
- 📱 **响应式布局** - 完美适配 PC 端和移动端 - 📱 **响应式布局** - 完美适配 PC 端和移动端
- 🌐 **多语言支持** - 支持中文/英文切换 - 🌐 **多语言支持** - 支持中文/英文切换
-**性能优化** - 轻量级设计,加载快速 -**性能优化** - 轻量级设计,加载快速
- 🎯 **自定义文章类型** - 支持公告通知独立管理 - 🎯 **自定义内容类型** - 支持公告通知、社团新闻、便民服务独立管理
## 🚀 快速开始 ## 🚀 快速开始
@@ -91,6 +91,37 @@ git clone https://github.com/itstudio-2002/ITStudioMainSite.git
- 未设置 `itstudio_weight` 的文章按 `0` 处理。 - 未设置 `itstudio_weight` 的文章按 `0` 处理。
- 当高权重文章不足 4 篇时,页面会自动用最新文章补足。 - 当高权重文章不足 4 篇时,页面会自动用最新文章补足。
#### 5. 维护便民服务目录
主题内置了“便民服务(service)”内容类型,`/services` 页面会自动读取并展示。
每条服务信息来源:
- 图标:文章特色图(Featured Image
- 名称:服务双语字段(中文名称 / 英文名称)
- 简介:服务双语字段(中文简介 / 英文简介)
- 类别:服务分类双语字段(中文名称 / 英文名称)
- 跳转链接:编辑页中的“服务跳转链接”字段
后台新增步骤:
1. 进入 **便民服务 > 新增便民服务**
2. 在“服务双语与链接”填写:
- 中文名称 / 英文名称
- 中文简介 / 英文简介
- 服务跳转链接
3. 设置特色图作为服务图标。
4. 在右侧选择或新建“服务分类”。
5. 发布后,该服务会自动出现在 `/services` 页面中。
分类双语设置:
1. 进入 **便民服务 > 服务分类**
2. 新增或编辑分类时,可填写“中文名称”和“英文名称”。
3. 前台会根据语言切换自动显示对应分类名。
说明:
- 若未设置“服务跳转链接”,前端会回退到该服务文章自身链接。
- 若未设置特色图,前端会使用 `resources/it_logo_2024.svg` 作为默认图标。
- 新增内容类型后若前台路由未生效,请进入 **设置 > 固定链接** 点击一次“保存更改”刷新重写规则。
## 🎨 设计规范 ## 🎨 设计规范
### 主题色彩 ### 主题色彩
@@ -112,7 +143,9 @@ git clone https://github.com/itstudio-2002/ITStudioMainSite.git
ITStudioMainSite/ ITStudioMainSite/
├── assets/ ├── assets/
│ ├── css/ │ ├── css/
│ │ ── main.css # 主样式文件 │ │ ── content.css # 内容页样式
│ │ ├── front-page.css # 首页样式
│ │ └── services-page.css # 便民服务页样式
│ └── js/ │ └── js/
│ ├── theme-toggle.js # 主题切换功能 │ ├── theme-toggle.js # 主题切换功能
│ └── main.js # 主要 JavaScript │ └── main.js # 主要 JavaScript
@@ -127,6 +160,7 @@ ITStudioMainSite/
├── footer.php # 底部模板 ├── footer.php # 底部模板
├── single.php # 单篇文章模板 ├── single.php # 单篇文章模板
├── page.php # 页面模板 ├── page.php # 页面模板
├── page-services.php # 便民服务页面模板
├── archive.php # 归档页面模板 ├── archive.php # 归档页面模板
└── 404.php # 404 错误页面 └── 404.php # 404 错误页面
``` ```
+280
View File
@@ -0,0 +1,280 @@
.services-directory-page {
padding: 36px 0 68px;
}
.services-directory-head {
margin-bottom: 40px;
}
.services-directory-title {
margin: 0;
font-size: clamp(2rem, 1.6vw + 1.5rem, 3rem);
line-height: 1.15;
color: var(--color-primary);
}
.services-directory-layout {
display: grid;
grid-template-columns: 230px minmax(0, 1fr);
gap: 28px;
align-items: start;
}
.services-directory-sidebar {
position: sticky;
top: calc(var(--header-height) + 18px);
}
.services-directory-nav-box {
border: 1px solid var(--border-default);
border-radius: var(--radius-md);
background: color-mix(in srgb, var(--bg-surface) 92%, transparent);
padding: 14px;
}
.services-directory-nav-title {
margin: 0;
font-size: 1rem;
color: var(--text-primary);
}
.services-directory-nav-list {
margin-top: 10px;
display: flex;
flex-direction: column;
gap: 5px;
}
.services-directory-nav-list a {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 8px 10px;
border-radius: 8px;
color: var(--text-secondary);
text-decoration: none;
border: 1px solid transparent;
}
.services-directory-nav-list a:hover {
color: var(--color-primary);
border-color: var(--border-default);
background: color-mix(in srgb, var(--bg-card) 88%, transparent);
}
.services-directory-nav-name {
font-size: 0.94rem;
}
.services-directory-nav-count {
min-width: 26px;
padding: 1px 7px;
border-radius: 999px;
text-align: center;
font-size: 0.74rem;
font-weight: 700;
color: var(--text-secondary);
background: color-mix(in srgb, var(--bg-card) 72%, var(--bg-surface) 28%);
border: 1px solid var(--border-default);
}
.services-directory-content {
min-width: 0;
}
.services-directory-group {
scroll-margin-top: calc(var(--header-height) + 18px);
}
.services-directory-group + .services-directory-group {
margin-top: 28px;
}
.services-directory-group-head {
margin-bottom: 12px;
}
.services-directory-group-head h2 {
margin: 0;
font-size: 1.52rem;
color: var(--text-primary);
}
.services-directory-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 14px;
align-items: start;
}
.services-directory-card {
display: block;
border: 1px solid var(--border-default);
border-radius: 12px;
background: color-mix(in srgb, var(--bg-card) 94%, transparent);
padding: 12px;
min-height: 0;
color: inherit;
text-decoration: none;
transition: transform var(--duration-fast) var(--ease-in-out), border-color var(--duration-fast) var(--ease-in-out), box-shadow var(--duration-fast) var(--ease-in-out);
}
.services-directory-card:hover {
transform: translateY(-2px);
border-color: color-mix(in srgb, var(--color-primary) 52%, var(--border-default) 48%);
box-shadow: var(--shadow-sm);
}
.services-directory-card-head {
display: flex;
align-items: center;
gap: 12px;
}
.services-directory-icon-wrap {
width: 54px;
height: 54px;
border-radius: 12px;
border: none;
background: transparent;
overflow: hidden;
display: block;
flex-shrink: 0;
}
.services-directory-icon {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.services-directory-icon-fallback {
opacity: 1;
}
.services-directory-meta {
min-width: 0;
}
.services-directory-name {
margin: 0;
font-size: 1.16rem;
line-height: 1.28;
color: var(--text-primary);
font-weight: 760;
}
.services-directory-category {
margin-top: 4px;
display: inline-block;
font-size: 0.8rem;
color: var(--text-secondary);
}
.services-directory-desc {
margin: 10px 0 0;
padding-top: 10px;
border-top: 1px solid color-mix(in srgb, var(--border-default) 84%, transparent);
color: var(--text-secondary);
font-size: 0.94rem;
line-height: 1.55;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
.services-directory-empty {
margin-top: 16px;
border: 1px solid var(--border-default);
border-radius: 12px;
padding: 28px;
text-align: center;
background: color-mix(in srgb, var(--bg-card) 95%, transparent);
}
.services-directory-empty h2 {
margin: 0;
font-size: 1.6rem;
color: var(--text-primary);
}
.services-directory-empty p {
margin: 10px 0 0;
color: var(--text-secondary);
}
.services-directory-empty-btn {
margin-top: 16px;
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 148px;
height: 38px;
padding: 0 16px;
border-radius: 10px;
border: 1px solid color-mix(in srgb, var(--color-primary) 65%, var(--border-default) 35%);
color: var(--color-primary);
text-decoration: none;
font-weight: 700;
}
.services-directory-empty-btn:hover {
background: color-mix(in srgb, var(--color-primary) 10%, transparent);
}
@media (max-width: 1280px) {
.services-directory-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 980px) {
.services-directory-head {
margin-bottom: 30px;
}
.services-directory-layout {
grid-template-columns: 1fr;
gap: 16px;
}
.services-directory-sidebar {
position: static;
}
.services-directory-nav-list {
flex-direction: row;
flex-wrap: wrap;
gap: 8px;
}
.services-directory-nav-list li {
flex: 0 0 auto;
}
.services-directory-nav-list a {
padding: 7px 11px;
}
}
@media (max-width: 680px) {
.services-directory-page {
padding: 26px 0 52px;
}
.services-directory-head {
margin-bottom: 24px;
}
.services-directory-grid {
grid-template-columns: 1fr;
gap: 12px;
}
.services-directory-card {
min-height: 0;
}
}
+473 -1
View File
@@ -23,7 +23,7 @@ add_filter('pings_open', '__return_false', 20, 2);
add_filter('comments_array', '__return_empty_array', 10, 2); add_filter('comments_array', '__return_empty_array', 10, 2);
function itstudio_disable_comments_post_types() { function itstudio_disable_comments_post_types() {
$post_types = array('post', 'page', 'announcement', 'news'); $post_types = array('post', 'page', 'announcement', 'news', 'service');
foreach ($post_types as $post_type) { foreach ($post_types as $post_type) {
if (post_type_supports($post_type, 'comments')) { if (post_type_supports($post_type, 'comments')) {
remove_post_type_support($post_type, 'comments'); remove_post_type_support($post_type, 'comments');
@@ -116,6 +116,18 @@ function itstudio_enqueue_scripts() {
wp_enqueue_script('itstudio-about-hero', get_template_directory_uri() . '/assets/js/home-hero.js', array(), '1.0.0', true); wp_enqueue_script('itstudio-about-hero', get_template_directory_uri() . '/assets/js/home-hero.js', array(), '1.0.0', true);
} }
// 仅在便民服务页加载(包含 /services fallback
$is_services = is_page('services') || is_page_template('page-services.php');
if (!$is_services && is_404()) {
global $wp;
$request = isset($wp->request) ? trim($wp->request, '/') : '';
$is_services = ($request === 'services');
}
if ($is_services) {
wp_enqueue_style('itstudio-services-page', get_template_directory_uri() . '/assets/css/services-page.css', array('itstudio-content'), '1.0.0');
}
// Scripts // Scripts
wp_enqueue_script('itstudio-theme-toggle', get_template_directory_uri() . '/assets/js/theme-toggle.js', array(), '1.0.0', true); wp_enqueue_script('itstudio-theme-toggle', get_template_directory_uri() . '/assets/js/theme-toggle.js', array(), '1.0.0', true);
wp_enqueue_script('itstudio-lang-toggle', get_template_directory_uri() . '/assets/js/lang-toggle.js', array(), '1.0.0', true); wp_enqueue_script('itstudio-lang-toggle', get_template_directory_uri() . '/assets/js/lang-toggle.js', array(), '1.0.0', true);
@@ -197,6 +209,47 @@ function itstudio_custom_post_types() {
'show_in_rest' => true, 'show_in_rest' => true,
)); ));
register_taxonomy('service_category', array('service'), array(
'labels' => array(
'name' => __('服务分类', 'itstudio'),
'singular_name' => __('服务分类', 'itstudio'),
),
'hierarchical' => true,
'public' => true,
'show_ui' => true,
'show_admin_column' => false,
'show_in_rest' => true,
'rewrite' => array(
'slug' => 'service-category',
'with_front' => false,
),
));
register_post_type('service', array(
'labels' => array(
'name' => __('便民服务', 'itstudio'),
'singular_name' => __('便民服务', 'itstudio'),
'menu_name' => __('便民服务', 'itstudio'),
'add_new_item' => __('新增便民服务', 'itstudio'),
'edit_item' => __('编辑便民服务', 'itstudio'),
),
'public' => true,
'show_ui' => true,
'show_in_menu' => true,
'show_in_nav_menus' => false,
'exclude_from_search' => true,
'publicly_queryable' => true,
'has_archive' => false,
'rewrite' => array(
'slug' => 'service',
'with_front' => false,
),
'supports' => array('title', 'excerpt', 'thumbnail', 'page-attributes'),
'taxonomies' => array('service_category'),
'menu_icon' => 'dashicons-admin-tools',
'show_in_rest' => true,
));
register_taxonomy_for_object_type('post_tag', 'announcement'); register_taxonomy_for_object_type('post_tag', 'announcement');
register_taxonomy_for_object_type('post_tag', 'news'); register_taxonomy_for_object_type('post_tag', 'news');
} }
@@ -254,6 +307,374 @@ function itstudio_register_acf_fields() {
} }
add_action('acf/init', 'itstudio_register_acf_fields'); add_action('acf/init', 'itstudio_register_acf_fields');
function itstudio_get_service_url_meta_key() {
return '_itstudio_service_url';
}
function itstudio_get_service_title_cn_meta_key() {
return '_itstudio_service_title_cn';
}
function itstudio_get_service_title_en_meta_key() {
return '_itstudio_service_title_en';
}
function itstudio_get_service_excerpt_cn_meta_key() {
return '_itstudio_service_excerpt_cn';
}
function itstudio_get_service_excerpt_en_meta_key() {
return '_itstudio_service_excerpt_en';
}
function itstudio_get_service_category_name_cn_meta_key() {
return 'itstudio_service_category_name_cn';
}
function itstudio_get_service_category_name_en_meta_key() {
return 'itstudio_service_category_name_en';
}
function itstudio_get_service_category_i18n_labels($term) {
if (!$term || is_wp_error($term)) {
return array(
'cn' => '未分类',
'en' => 'Uncategorized',
);
}
$term_id = (int) $term->term_id;
$name_cn = trim((string) get_term_meta($term_id, itstudio_get_service_category_name_cn_meta_key(), true));
$name_en = trim((string) get_term_meta($term_id, itstudio_get_service_category_name_en_meta_key(), true));
if ($name_cn === '') {
$name_cn = (string) $term->name;
}
if ($name_en === '') {
$name_en = $name_cn;
}
return array(
'cn' => $name_cn,
'en' => $name_en,
);
}
function itstudio_get_service_i18n_content($service_id, $excerpt_limit = 90) {
$service_id = (int) $service_id;
if ($service_id <= 0) {
return array(
'title_cn' => '',
'title_en' => '',
'excerpt_cn' => '',
'excerpt_en' => '',
);
}
$title_cn = trim((string) get_post_meta($service_id, itstudio_get_service_title_cn_meta_key(), true));
$title_en = trim((string) get_post_meta($service_id, itstudio_get_service_title_en_meta_key(), true));
$excerpt_cn = trim((string) get_post_meta($service_id, itstudio_get_service_excerpt_cn_meta_key(), true));
$excerpt_en = trim((string) get_post_meta($service_id, itstudio_get_service_excerpt_en_meta_key(), true));
$fallback_title = trim((string) get_the_title($service_id));
if ($fallback_title === '') {
$fallback_title = '未命名服务';
}
$fallback_excerpt = function_exists('itstudio_get_post_excerpt_chars')
? itstudio_get_post_excerpt_chars($service_id, $excerpt_limit)
: wp_html_excerpt(wp_strip_all_tags((string) get_post_field('post_excerpt', $service_id)), $excerpt_limit, '...');
$fallback_excerpt = trim((string) $fallback_excerpt);
if ($fallback_excerpt === '') {
$fallback_excerpt = '暂无简介';
}
if ($title_cn === '') {
$title_cn = $fallback_title;
}
if ($title_en === '') {
$title_en = $title_cn;
}
if ($excerpt_cn === '') {
$excerpt_cn = $fallback_excerpt;
}
if ($excerpt_en === '') {
$excerpt_en = $excerpt_cn;
}
return array(
'title_cn' => $title_cn,
'title_en' => $title_en,
'excerpt_cn' => $excerpt_cn,
'excerpt_en' => $excerpt_en,
);
}
function itstudio_add_service_meta_boxes() {
add_meta_box(
'itstudio_service_link',
__('服务双语与链接', 'itstudio'),
'itstudio_render_service_link_meta_box',
'service',
'normal',
'high'
);
}
add_action('add_meta_boxes', 'itstudio_add_service_meta_boxes');
function itstudio_render_service_link_meta_box($post) {
$service_url = (string) get_post_meta($post->ID, itstudio_get_service_url_meta_key(), true);
$title_cn = (string) get_post_meta($post->ID, itstudio_get_service_title_cn_meta_key(), true);
$title_en = (string) get_post_meta($post->ID, itstudio_get_service_title_en_meta_key(), true);
$excerpt_cn = (string) get_post_meta($post->ID, itstudio_get_service_excerpt_cn_meta_key(), true);
$excerpt_en = (string) get_post_meta($post->ID, itstudio_get_service_excerpt_en_meta_key(), true);
wp_nonce_field('itstudio_save_service_meta', 'itstudio_service_meta_nonce');
?>
<p>
<label for="itstudio_service_title_cn"><strong><?php esc_html_e('中文名称', 'itstudio'); ?></strong></label>
</p>
<p>
<input
type="text"
id="itstudio_service_title_cn"
name="itstudio_service_title_cn"
class="widefat"
placeholder="<?php echo esc_attr((string) get_the_title($post)); ?>"
value="<?php echo esc_attr($title_cn); ?>"
>
</p>
<p>
<label for="itstudio_service_title_en"><strong><?php esc_html_e('英文名称', 'itstudio'); ?></strong></label>
</p>
<p>
<input
type="text"
id="itstudio_service_title_en"
name="itstudio_service_title_en"
class="widefat"
placeholder="Service Name"
value="<?php echo esc_attr($title_en); ?>"
>
</p>
<p>
<label for="itstudio_service_excerpt_cn"><strong><?php esc_html_e('中文简介', 'itstudio'); ?></strong></label>
</p>
<p>
<textarea
id="itstudio_service_excerpt_cn"
name="itstudio_service_excerpt_cn"
class="widefat"
rows="3"
placeholder="<?php echo esc_attr((string) get_the_excerpt($post)); ?>"
><?php echo esc_textarea($excerpt_cn); ?></textarea>
</p>
<p>
<label for="itstudio_service_excerpt_en"><strong><?php esc_html_e('英文简介', 'itstudio'); ?></strong></label>
</p>
<p>
<textarea
id="itstudio_service_excerpt_en"
name="itstudio_service_excerpt_en"
class="widefat"
rows="3"
placeholder="Short English description"
><?php echo esc_textarea($excerpt_en); ?></textarea>
</p>
<p>
<label for="itstudio_service_url"><strong><?php esc_html_e('目标链接', 'itstudio'); ?></strong></label>
</p>
<p>
<input
type="url"
id="itstudio_service_url"
name="itstudio_service_url"
class="widefat"
placeholder="https://example.com"
value="<?php echo esc_attr($service_url); ?>"
>
</p>
<p class="description">
<?php esc_html_e('支持分别填写中英文名称与简介;未填写时会回退到标题/摘要。', 'itstudio'); ?>
</p>
<?php
}
function itstudio_save_service_meta($post_id) {
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
return;
}
if (!isset($_POST['itstudio_service_meta_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['itstudio_service_meta_nonce'])), 'itstudio_save_service_meta')) {
return;
}
if (!current_user_can('edit_post', $post_id)) {
return;
}
if (get_post_type($post_id) !== 'service') {
return;
}
$title_cn = isset($_POST['itstudio_service_title_cn']) ? sanitize_text_field(wp_unslash($_POST['itstudio_service_title_cn'])) : '';
$title_en = isset($_POST['itstudio_service_title_en']) ? sanitize_text_field(wp_unslash($_POST['itstudio_service_title_en'])) : '';
$excerpt_cn = isset($_POST['itstudio_service_excerpt_cn']) ? sanitize_textarea_field(wp_unslash($_POST['itstudio_service_excerpt_cn'])) : '';
$excerpt_en = isset($_POST['itstudio_service_excerpt_en']) ? sanitize_textarea_field(wp_unslash($_POST['itstudio_service_excerpt_en'])) : '';
$meta_updates = array(
itstudio_get_service_title_cn_meta_key() => trim($title_cn),
itstudio_get_service_title_en_meta_key() => trim($title_en),
itstudio_get_service_excerpt_cn_meta_key() => trim($excerpt_cn),
itstudio_get_service_excerpt_en_meta_key() => trim($excerpt_en),
);
foreach ($meta_updates as $meta_key => $value) {
if ($value === '') {
delete_post_meta($post_id, $meta_key);
} else {
update_post_meta($post_id, $meta_key, $value);
}
}
$url_meta_key = itstudio_get_service_url_meta_key();
$raw_url = isset($_POST['itstudio_service_url']) ? trim((string) wp_unslash($_POST['itstudio_service_url'])) : '';
if ($raw_url === '') {
delete_post_meta($post_id, $url_meta_key);
return;
}
if (strpos($raw_url, '/') === 0) {
$raw_url = home_url($raw_url);
}
$service_url = esc_url_raw($raw_url);
if ($service_url === '') {
delete_post_meta($post_id, $url_meta_key);
return;
}
update_post_meta($post_id, $url_meta_key, $service_url);
}
add_action('save_post_service', 'itstudio_save_service_meta');
function itstudio_service_category_add_fields($taxonomy) {
wp_nonce_field('itstudio_save_service_category_meta', 'itstudio_service_category_meta_nonce');
?>
<div class="form-field term-itstudio-service-name-cn-wrap">
<label for="itstudio_service_cat_name_cn"><?php esc_html_e('中文名称', 'itstudio'); ?></label>
<input type="text" name="itstudio_service_cat_name_cn" id="itstudio_service_cat_name_cn" value="">
</div>
<div class="form-field term-itstudio-service-name-en-wrap">
<label for="itstudio_service_cat_name_en"><?php esc_html_e('英文名称', 'itstudio'); ?></label>
<input type="text" name="itstudio_service_cat_name_en" id="itstudio_service_cat_name_en" value="" placeholder="Category Name">
</div>
<?php
}
add_action('service_category_add_form_fields', 'itstudio_service_category_add_fields');
function itstudio_service_category_edit_fields($term) {
$term_id = (int) $term->term_id;
$name_cn = (string) get_term_meta($term_id, itstudio_get_service_category_name_cn_meta_key(), true);
$name_en = (string) get_term_meta($term_id, itstudio_get_service_category_name_en_meta_key(), true);
wp_nonce_field('itstudio_save_service_category_meta', 'itstudio_service_category_meta_nonce');
?>
<tr class="form-field term-itstudio-service-name-cn-wrap">
<th scope="row"><label for="itstudio_service_cat_name_cn"><?php esc_html_e('中文名称', 'itstudio'); ?></label></th>
<td><input type="text" name="itstudio_service_cat_name_cn" id="itstudio_service_cat_name_cn" value="<?php echo esc_attr($name_cn); ?>"></td>
</tr>
<tr class="form-field term-itstudio-service-name-en-wrap">
<th scope="row"><label for="itstudio_service_cat_name_en"><?php esc_html_e('英文名称', 'itstudio'); ?></label></th>
<td><input type="text" name="itstudio_service_cat_name_en" id="itstudio_service_cat_name_en" value="<?php echo esc_attr($name_en); ?>" placeholder="Category Name"></td>
</tr>
<?php
}
add_action('service_category_edit_form_fields', 'itstudio_service_category_edit_fields');
function itstudio_save_service_category_meta($term_id) {
if (!current_user_can('manage_categories')) {
return;
}
if (!isset($_POST['itstudio_service_category_meta_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['itstudio_service_category_meta_nonce'])), 'itstudio_save_service_category_meta')) {
return;
}
$name_cn = isset($_POST['itstudio_service_cat_name_cn']) ? sanitize_text_field(wp_unslash($_POST['itstudio_service_cat_name_cn'])) : '';
$name_en = isset($_POST['itstudio_service_cat_name_en']) ? sanitize_text_field(wp_unslash($_POST['itstudio_service_cat_name_en'])) : '';
$term_meta = array(
itstudio_get_service_category_name_cn_meta_key() => trim($name_cn),
itstudio_get_service_category_name_en_meta_key() => trim($name_en),
);
foreach ($term_meta as $meta_key => $value) {
if ($value === '') {
delete_term_meta($term_id, $meta_key);
} else {
update_term_meta($term_id, $meta_key, $value);
}
}
}
add_action('created_service_category', 'itstudio_save_service_category_meta');
add_action('edited_service_category', 'itstudio_save_service_category_meta');
function itstudio_get_service_target_url($service_id) {
$service_id = (int) $service_id;
if ($service_id <= 0) {
return '';
}
$meta_key = itstudio_get_service_url_meta_key();
$url = (string) get_post_meta($service_id, $meta_key, true);
if ($url !== '') {
return $url;
}
return (string) get_permalink($service_id);
}
function itstudio_service_admin_columns($columns) {
$columns['service_category'] = __('分类', 'itstudio');
$columns['service_url'] = __('跳转链接', 'itstudio');
return $columns;
}
add_filter('manage_service_posts_columns', 'itstudio_service_admin_columns');
function itstudio_service_admin_custom_column($column, $post_id) {
if ($column === 'service_category') {
$terms = get_the_terms($post_id, 'service_category');
if (empty($terms) || is_wp_error($terms)) {
echo '<span style="color:#888;">' . esc_html__('未分类', 'itstudio') . '</span>';
return;
}
$labels = array();
foreach ($terms as $term) {
$i18n = itstudio_get_service_category_i18n_labels($term);
$label = $i18n['cn'];
if ($i18n['en'] !== $i18n['cn']) {
$label .= ' / ' . $i18n['en'];
}
$labels[] = $label;
}
echo esc_html(implode(' | ', $labels));
return;
}
if ($column === 'service_url') {
$url = itstudio_get_service_target_url($post_id);
if ($url === '') {
echo '<span style="color:#888;">-</span>';
return;
}
echo '<a href="' . esc_url($url) . '" target="_blank" rel="noopener noreferrer">' . esc_html($url) . '</a>';
}
}
add_action('manage_service_posts_custom_column', 'itstudio_service_admin_custom_column', 10, 2);
function itstudio_archive_document_title($parts) { function itstudio_archive_document_title($parts) {
if (is_post_type_archive('announcement')) { if (is_post_type_archive('announcement')) {
$parts['title'] = '公告通知'; $parts['title'] = '公告通知';
@@ -369,6 +790,57 @@ function itstudio_news_fallback() {
} }
add_action('template_redirect', 'itstudio_news_fallback'); add_action('template_redirect', 'itstudio_news_fallback');
// Fallback: render /services even if the page isn't created in WP admin.
function itstudio_services_fallback() {
if (!is_404()) {
return;
}
global $wp;
$request = isset($wp->request) ? trim($wp->request, '/') : '';
if ($request !== 'services') {
return;
}
$template = locate_template('page-services.php');
if ($template) {
global $wp_query;
if ($wp_query) {
$wp_query->is_404 = false;
$wp_query->is_page = true;
$wp_query->is_singular = true;
$virtual_post = new WP_Post((object) array(
'ID' => 0,
'post_type' => 'page',
'post_parent' => 0,
'post_title' => __('便民服务', 'itstudio'),
'post_status' => 'publish',
'post_name' => 'services',
'post_content' => '',
));
$wp_query->post = $virtual_post;
$wp_query->posts = array($virtual_post);
$wp_query->queried_object = $virtual_post;
$wp_query->queried_object_id = 0;
$wp_query->post_count = 1;
$wp_query->found_posts = 1;
$wp_query->max_num_pages = 1;
global $post;
$post = $virtual_post;
setup_postdata($post);
}
add_filter('document_title_parts', function ($parts) {
$parts['title'] = __('便民服务', 'itstudio');
return $parts;
});
status_header(200);
nocache_headers();
include $template;
exit;
}
}
add_action('template_redirect', 'itstudio_services_fallback');
function itstudio_get_post_views($post_id) { function itstudio_get_post_views($post_id) {
$post_id = (int) $post_id; $post_id = (int) $post_id;
if ($post_id <= 0) { if ($post_id <= 0) {
+195
View File
@@ -0,0 +1,195 @@
<?php get_header(); ?>
<?php
$default_icon_url = get_template_directory_uri() . '/resources/it_logo_2024.svg';
$services_query = new WP_Query(array(
'post_type' => 'service',
'post_status' => 'publish',
'posts_per_page' => -1,
'orderby' => array(
'menu_order' => 'ASC',
'date' => 'DESC',
),
));
$grouped_services = array();
$uncategorized_items = array();
if ($services_query->have_posts()) {
while ($services_query->have_posts()) {
$services_query->the_post();
$service_id = get_the_ID();
$service_terms = get_the_terms($service_id, 'service_category');
$service_url = function_exists('itstudio_get_service_target_url') ? itstudio_get_service_target_url($service_id) : get_permalink($service_id);
$service_url = is_string($service_url) ? trim($service_url) : '';
if ($service_url === '') {
$service_url = '#';
}
$i18n_content = function_exists('itstudio_get_service_i18n_content')
? itstudio_get_service_i18n_content($service_id, 90)
: array(
'title_cn' => get_the_title($service_id),
'title_en' => get_the_title($service_id),
'excerpt_cn' => function_exists('itstudio_get_post_excerpt_chars')
? itstudio_get_post_excerpt_chars($service_id, 90)
: wp_html_excerpt(wp_strip_all_tags(get_the_excerpt() ?: get_the_content()), 90, '...'),
'excerpt_en' => function_exists('itstudio_get_post_excerpt_chars')
? itstudio_get_post_excerpt_chars($service_id, 90)
: wp_html_excerpt(wp_strip_all_tags(get_the_excerpt() ?: get_the_content()), 90, '...'),
);
$item = array(
'id' => $service_id,
'title_cn' => $i18n_content['title_cn'],
'title_en' => $i18n_content['title_en'],
'url' => $service_url,
'excerpt_cn' => $i18n_content['excerpt_cn'],
'excerpt_en' => $i18n_content['excerpt_en'],
'has_thumb' => has_post_thumbnail($service_id),
'term_name_cn' => '',
'term_name_en' => '',
);
if (!empty($service_terms) && !is_wp_error($service_terms)) {
$primary_term = $service_terms[0];
$term_id = (int) $primary_term->term_id;
$term_labels = function_exists('itstudio_get_service_category_i18n_labels')
? itstudio_get_service_category_i18n_labels($primary_term)
: array('cn' => $primary_term->name, 'en' => $primary_term->name);
$item['term_name_cn'] = $term_labels['cn'];
$item['term_name_en'] = $term_labels['en'];
if (!isset($grouped_services[$term_id])) {
$grouped_services[$term_id] = array(
'term' => $primary_term,
'items' => array(),
);
}
$grouped_services[$term_id]['items'][] = $item;
} else {
$item['term_name_cn'] = '未分类';
$item['term_name_en'] = 'Uncategorized';
$uncategorized_items[] = $item;
}
}
wp_reset_postdata();
}
$sections = array();
if (!empty($grouped_services)) {
$ordered_terms = get_terms(array(
'taxonomy' => 'service_category',
'hide_empty' => true,
));
if (!is_wp_error($ordered_terms) && !empty($ordered_terms)) {
foreach ($ordered_terms as $term) {
$term_id = (int) $term->term_id;
if (!isset($grouped_services[$term_id])) {
continue;
}
$term_labels = function_exists('itstudio_get_service_category_i18n_labels')
? itstudio_get_service_category_i18n_labels($term)
: array('cn' => $term->name, 'en' => $term->name);
$sections[] = array(
'slug' => sanitize_title($term->slug),
'name_cn' => $term_labels['cn'],
'name_en' => $term_labels['en'],
'items' => $grouped_services[$term_id]['items'],
);
}
} else {
foreach ($grouped_services as $group) {
$term_labels = function_exists('itstudio_get_service_category_i18n_labels')
? itstudio_get_service_category_i18n_labels($group['term'])
: array('cn' => $group['term']->name, 'en' => $group['term']->name);
$sections[] = array(
'slug' => sanitize_title($group['term']->slug),
'name_cn' => $term_labels['cn'],
'name_en' => $term_labels['en'],
'items' => $group['items'],
);
}
}
}
if (!empty($uncategorized_items)) {
$sections[] = array(
'slug' => 'uncategorized',
'name_cn' => '未分类',
'name_en' => 'Uncategorized',
'items' => $uncategorized_items,
);
}
?>
<main class="site-main services-directory-page">
<div class="container">
<header class="services-directory-head">
<h1 class="services-directory-title" data-cn="便民服务" data-en="Service">便民服务</h1>
</header>
<?php if (!empty($sections)) : ?>
<div class="services-directory-layout">
<aside class="services-directory-sidebar">
<div class="services-directory-nav-box">
<h2 class="services-directory-nav-title" data-cn="分类" data-en="Categories">分类</h2>
<ul class="services-directory-nav-list">
<?php foreach ($sections as $section) : ?>
<?php $count = count($section['items']); ?>
<li>
<a href="#service-section-<?php echo esc_attr($section['slug']); ?>">
<span class="services-directory-nav-name" data-cn="<?php echo esc_attr($section['name_cn']); ?>" data-en="<?php echo esc_attr($section['name_en']); ?>"><?php echo esc_html($section['name_cn']); ?></span>
<span class="services-directory-nav-count"><?php echo esc_html($count); ?></span>
</a>
</li>
<?php endforeach; ?>
</ul>
</div>
</aside>
<div class="services-directory-content">
<?php foreach ($sections as $section) : ?>
<section class="services-directory-group" id="service-section-<?php echo esc_attr($section['slug']); ?>">
<header class="services-directory-group-head">
<h2 data-cn="<?php echo esc_attr($section['name_cn']); ?>" data-en="<?php echo esc_attr($section['name_en']); ?>"><?php echo esc_html($section['name_cn']); ?></h2>
</header>
<div class="services-directory-grid">
<?php foreach ($section['items'] as $item) : ?>
<a class="services-directory-card" href="<?php echo esc_url($item['url']); ?>" target="_blank" rel="noopener noreferrer">
<div class="services-directory-card-head">
<div class="services-directory-icon-wrap">
<?php if ($item['has_thumb']) : ?>
<?php echo get_the_post_thumbnail($item['id'], 'medium', array('class' => 'services-directory-icon')); ?>
<?php else : ?>
<img class="services-directory-icon services-directory-icon-fallback" src="<?php echo esc_url($default_icon_url); ?>" alt="<?php echo esc_attr($item['title_cn']); ?>">
<?php endif; ?>
</div>
<div class="services-directory-meta">
<h3 class="services-directory-name" data-cn="<?php echo esc_attr($item['title_cn']); ?>" data-en="<?php echo esc_attr($item['title_en']); ?>"><?php echo esc_html($item['title_cn']); ?></h3>
<span class="services-directory-category" data-cn="<?php echo esc_attr($item['term_name_cn']); ?>" data-en="<?php echo esc_attr($item['term_name_en']); ?>"><?php echo esc_html($item['term_name_cn']); ?></span>
</div>
</div>
<p class="services-directory-desc" data-cn="<?php echo esc_attr($item['excerpt_cn']); ?>" data-en="<?php echo esc_attr($item['excerpt_en']); ?>"><?php echo esc_html($item['excerpt_cn']); ?></p>
</a>
<?php endforeach; ?>
</div>
</section>
<?php endforeach; ?>
</div>
</div>
<?php else : ?>
<section class="services-directory-empty">
<h2 data-cn="暂无便民服务" data-en="No services yet">暂无便民服务</h2>
<p data-cn="请先在后台“便民服务”中添加服务条目。" data-en="Please add services in wp-admin first.">请先在后台“便民服务”中添加服务条目。</p>
<?php if (current_user_can('edit_posts')) : ?>
<a class="services-directory-empty-btn" href="<?php echo esc_url(admin_url('post-new.php?post_type=service')); ?>" target="_blank" rel="noopener noreferrer" data-cn="前往新增服务" data-en="Create a service">前往新增服务</a>
<?php endif; ?>
</section>
<?php endif; ?>
</div>
</main>
<?php get_footer(); ?>