feat(user-center): add dedicated creator workspace

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-06 17:23:31 +08:00
co-authored by Claude Fable 5
parent c5b7deaa59
commit ceff04ce54
7 changed files with 127 additions and 5 deletions
+10
View File
@@ -709,6 +709,16 @@
border-bottom: 0;
}
/* ==================== 独立创作中心工作台 ==================== */
.creator-workspace { display: flex; flex-direction: column; gap: 16px; }
.creator-workspace__hero { display: flex; align-items: center; justify-content: space-between; gap: 20px; padding: 24px 26px; border-radius: var(--radius-lg); color: #fff; background: linear-gradient(135deg, var(--color-primary), #7c3aed); }
.creator-workspace__eyebrow { margin: 0 0 6px; font-size: 12px; opacity: .78; }.creator-workspace__title { margin: 0; font-size: 26px; color: inherit; }.creator-workspace__intro { margin: 7px 0 0; font-size: 13px; opacity: .84; }.creator-workspace__primary { flex-shrink: 0; color: var(--color-primary); background: #fff; border-color: #fff; }
.creator-workspace__stats { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; }.creator-stat-card { display: flex; align-items: center; gap: 12px; padding: 16px; border: 1px solid var(--color-line); border-radius: var(--radius-lg); background: var(--surface-1); }.creator-stat-card__icon { display: inline-flex; align-items: center; justify-content: center; width: 36px; height: 36px; border-radius: 10px; color: var(--color-primary); background: var(--color-primary-soft); }.creator-stat-card strong, .creator-stat-card small { display: block; }.creator-stat-card strong { font-size: 20px; line-height: 1.1; color: var(--color-ink); }.creator-stat-card small { margin-top: 4px; font-size: 12px; color: var(--color-ink-4); }
.creator-workspace__panel { padding: 0; overflow: hidden; }.creator-workspace__toolbar { display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; padding: 20px 22px 14px; border-bottom: 1px solid var(--color-line); }.creator-workspace__toolbar h3 { margin: 0; font-size: 18px; }.creator-workspace__toolbar p { margin: 4px 0 0; font-size: 12px; color: var(--color-ink-4); }.creator-workspace__filters { display: flex; flex-wrap: wrap; gap: 6px; }.creator-filter { padding: 6px 10px; border-radius: var(--radius-pill); color: var(--color-ink-3); font-size: 12px; text-decoration: none; }.creator-filter b { margin-left: 3px; font-weight: 600; color: var(--color-ink-4); }.creator-filter:hover, .creator-filter.is-active { color: var(--color-primary); background: var(--color-primary-soft); }
.creator-post-list { display: flex; flex-direction: column; }.creator-post-row { display: grid; grid-template-columns: 96px minmax(0, 1fr) auto; gap: 16px; align-items: center; padding: 16px 22px; border-bottom: 1px solid var(--color-line); }.creator-post-row:last-child { border-bottom: 0; }.creator-post-row__thumb { display: block; width: 96px; height: 68px; overflow: hidden; border-radius: 8px; background: var(--surface-2); }.creator-post-row__thumb img { width: 100%; height: 100%; object-fit: cover; }.creator-post-row__thumb span { display: grid; place-items: center; height: 100%; color: var(--color-primary); font-size: 22px; }.creator-post-row__heading { display: flex; align-items: center; gap: 8px; min-width: 0; }.creator-post-row__heading h4 { overflow: hidden; margin: 0; font-size: 15px; text-overflow: ellipsis; white-space: nowrap; }.creator-post-row__heading h4 a { color: var(--color-ink); text-decoration: none; }.creator-post-row__body p { margin: 6px 0; overflow: hidden; color: var(--color-ink-4); font-size: 12px; text-overflow: ellipsis; white-space: nowrap; }.creator-post-row__status { flex-shrink: 0; padding: 3px 7px; border-radius: var(--radius-pill); font-size: 11px; color: #2563eb; background: #eff6ff; }.creator-post-row__status--draft { color: #b45309; background: #fffbeb; }.creator-post-row__status--pending { color: #7c3aed; background: #f5f3ff; }.creator-post-row__status--private { color: #64748b; background: #f1f5f9; }.creator-post-row__meta { display: flex; gap: 12px; color: var(--color-ink-4); font-size: 11px; }.creator-post-row__actions { display: flex; gap: 6px; white-space: nowrap; }.creator-post-row__action { padding: 6px 9px; border: 1px solid var(--color-line); border-radius: 6px; color: var(--color-ink-3); background: var(--surface-1); font-size: 12px; text-decoration: none; cursor: pointer; }.creator-post-row__action:hover { color: var(--color-primary); border-color: var(--color-primary); }.creator-post-row__action--danger:hover { color: var(--color-error); border-color: var(--color-error); }.creator-workspace__empty { padding: 48px 20px; text-align: center; color: var(--color-ink-4); }.creator-workspace__empty i { font-size: 32px; color: var(--color-primary); }.creator-workspace__empty h4 { margin: 12px 0 4px; color: var(--color-ink); }.creator-workspace__empty p { margin: 0 0 16px; font-size: 13px; }
@media (max-width: 767px) { .creator-workspace__hero { align-items: flex-start; flex-direction: column; padding: 20px; }.creator-workspace__stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }.creator-workspace__toolbar { align-items: flex-start; flex-direction: column; padding: 16px; }.creator-post-row { grid-template-columns: 72px minmax(0, 1fr); padding: 14px 16px; }.creator-post-row__thumb { width: 72px; height: 58px; }.creator-post-row__actions { grid-column: 2; }.creator-workspace__filters { width: 100%; overflow-x: auto; flex-wrap: nowrap; } }
.dashboard--public .profile-list[data-list="favorites"] .profile-list-item,
.dashboard--public .profile-list[data-list="favorites"] .profile-list-item:hover {
background: transparent;
+1 -1
View File
@@ -717,7 +717,7 @@
.then(function(r) { return r.json(); })
.then(function(res) {
if (res && res.success) {
var item = btn.closest('.profile-list-item');
var item = btn.closest('.profile-list-item, .creator-post-row');
if (item) item.remove();
showToast((res.data && res.data.message) || '文章已经删除');
} else {
+4 -3
View File
@@ -108,7 +108,7 @@ if (!function_exists('aksrt_get_user_center_tab_url')) {
}
$url = remove_query_arg([
'tab', 'posts_page', 'comments_page', 'favorites_page', 'credits_page',
'post_status', 'saved', 'profile_error',
'post_status', 'creator_page', 'creator_status', 'saved', 'profile_error',
'order_tab', 'otab', 'order_status', 'order_page', 'order_type', 'section',
], aksrt_get_user_center_url());
@@ -148,6 +148,7 @@ if (!function_exists('aksrt_capture_user_center_view')) {
'level' => 'level',
'profile' => 'form',
'posts' => 'posts',
'creator' => 'creator',
'comments' => 'comments',
'favorites' => 'favorites',
'credits' => 'credits',
@@ -179,7 +180,7 @@ if (!function_exists('aksrt_capture_user_center_panel')) {
* Capture a built-in or registered user-center panel body.
*/
function aksrt_capture_user_center_panel(string $tab_key, array $tab, array $context): string {
$built_in_tabs = ['profile', 'level', 'posts', 'comments', 'favorites', 'credits', 'vip', 'orders', 'auth', 'revenue', 'settings'];
$built_in_tabs = ['profile', 'level', 'posts', 'creator', 'comments', 'favorites', 'credits', 'vip', 'orders', 'auth', 'revenue', 'settings'];
if (in_array($tab_key, $built_in_tabs, true)) {
return aksrt_capture_user_center_view($tab_key, $context);
}
@@ -479,7 +480,7 @@ if (!function_exists('aksrt_get_user_center_render_context')) {
$post_status_filter = 'publish';
}
$user_status_counts = ['publish' => 0, 'draft' => 0, 'pending' => 0, 'private' => 0];
if ('posts' === $active_tab) {
if ('posts' === $active_tab || 'creator' === $active_tab) {
$status = aksrt_user_center_post_status_counts($user_id);
$user_status_counts = $status['counts'];
$post_count = $status['total'];
@@ -0,0 +1,65 @@
<?php
/**
* User center creator workspace.
*
* @package AKSRT
*/
defined('ABSPATH') || exit;
$context = is_array($args['context'] ?? null) ? $args['context'] : [];
extract($context, EXTR_SKIP); // phpcs:ignore WordPress.PHP.DontExtract.extract_extract
$can_submit = class_exists('\\AKSRT\\Core\\FrontSubmit')
&& \AKSRT\Core\FrontSubmit::get_instance()->can_submit((int) $user_id);
$counts = isset($user_status_counts) && is_array($user_status_counts) ? $user_status_counts : [];
$total = array_sum(array_map('intval', $counts));
$creator_filter = isset($_GET['creator_status']) ? sanitize_key(wp_unslash($_GET['creator_status'])) : 'all';
if (!in_array($creator_filter, ['all', 'publish', 'draft', 'pending', 'private'], true)) {
$creator_filter = 'all';
}
$creator_url = aksrt_get_user_center_tab_url('creator');
$filters = [
'all' => __('全部文章', 'aksrt'),
'publish' => __('已发布', 'aksrt'),
'draft' => __('草稿', 'aksrt'),
'pending' => __('待审核', 'aksrt'),
'private' => __('私密', 'aksrt'),
];
?>
<div class="creator-workspace">
<header class="creator-workspace__hero">
<div>
<p class="creator-workspace__eyebrow"><?php esc_html_e('内容工作台', 'aksrt'); ?></p>
<h2 class="creator-workspace__title"><?php esc_html_e('创作中心', 'aksrt'); ?></h2>
<p class="creator-workspace__intro"><?php esc_html_e('管理你的内容,查看发布进度,持续分享有价值的文章。', 'aksrt'); ?></p>
</div>
<?php if ($can_submit && !empty($new_post_url)) : ?>
<a class="creator-workspace__primary btn-primary" href="<?php echo esc_url($new_post_url); ?>">
<i class="fas fa-plus" aria-hidden="true"></i><?php esc_html_e('写新文章', 'aksrt'); ?>
</a>
<?php endif; ?>
</header>
<section class="creator-workspace__stats" aria-label="<?php esc_attr_e('创作数据', 'aksrt'); ?>">
<div class="creator-stat-card creator-stat-card--total"><span class="creator-stat-card__icon"><i class="fas fa-layer-group" aria-hidden="true"></i></span><span><strong><?php echo esc_html((string) $total); ?></strong><small><?php esc_html_e('全部文章', 'aksrt'); ?></small></span></div>
<div class="creator-stat-card"><span class="creator-stat-card__icon"><i class="fas fa-check" aria-hidden="true"></i></span><span><strong><?php echo esc_html((string) ($counts['publish'] ?? 0)); ?></strong><small><?php esc_html_e('已发布', 'aksrt'); ?></small></span></div>
<div class="creator-stat-card"><span class="creator-stat-card__icon"><i class="fas fa-pen" aria-hidden="true"></i></span><span><strong><?php echo esc_html((string) ($counts['draft'] ?? 0)); ?></strong><small><?php esc_html_e('草稿', 'aksrt'); ?></small></span></div>
<div class="creator-stat-card"><span class="creator-stat-card__icon"><i class="fas fa-clock" aria-hidden="true"></i></span><span><strong><?php echo esc_html((string) ($counts['pending'] ?? 0)); ?></strong><small><?php esc_html_e('待审核', 'aksrt'); ?></small></span></div>
</section>
<section class="creator-workspace__panel widget-card">
<div class="creator-workspace__toolbar">
<div><h3><?php esc_html_e('内容管理', 'aksrt'); ?></h3><p><?php esc_html_e('按状态查看并管理你的文章', 'aksrt'); ?></p></div>
<nav class="creator-workspace__filters" aria-label="<?php esc_attr_e('文章筛选', 'aksrt'); ?>">
<?php foreach ($filters as $key => $label) : $url = add_query_arg(['tab' => 'creator', 'creator_status' => $key], remove_query_arg(['creator_page'])); ?>
<a class="creator-filter<?php echo $creator_filter === $key ? ' is-active' : ''; ?>" href="<?php echo esc_url($url); ?>" aria-current="<?php echo $creator_filter === $key ? 'page' : 'false'; ?>"><?php echo esc_html($label); ?> <b><?php echo esc_html((string) ('all' === $key ? $total : ($counts[$key] ?? 0))); ?></b></a>
<?php endforeach; ?>
</nav>
</div>
<?php
$creator_args = ['status' => $creator_filter];
get_template_part('templates/parts/user-center/creator-posts', null, ['context' => $context, 'creator_args' => $creator_args]);
?>
</section>
</div>
@@ -7,7 +7,7 @@
defined('ABSPATH') || exit;
$creation_url = isset($dashboard_tabs['posts']) ? aksrt_get_user_center_tab_url('posts') : '';
$creation_url = isset($dashboard_tabs['creator']) ? aksrt_get_user_center_tab_url('creator') : '';
$revenue_url = (string) ($revenue_url ?? '');
if ($creation_url === '' && $revenue_url === '') {
+12
View File
@@ -133,6 +133,12 @@ class Center {
'priority' => 20,
'type' => 'panel',
],
'creator' => [
'label' => __('创作中心', 'aksrt'),
'icon' => 'feather-alt',
'priority' => 18,
'type' => 'panel',
],
'comments' => [
'label' => __('我的评论', 'aksrt'),
'icon' => 'comment',
@@ -210,6 +216,12 @@ class Center {
// Follow configured order; always auto-include enabled surfaces.
$order = $configured;
// Creator center is a dedicated workspace, separate from the posts archive.
if (!in_array('creator', $order, true)) {
$insert_at = array_search('profile', $order, true);
array_splice($order, $insert_at === false ? 0 : (int) $insert_at + 1, 0, ['creator']);
}
// Growth level is a dedicated user-center surface, separate from spendable points.
if ($level_on && !in_array('level', $order, true)) {
$insert_at = array_search('profile', $order, true);
@@ -0,0 +1,34 @@
<?php
defined('ABSPATH') || exit;
$context = is_array($args['context'] ?? null) ? $args['context'] : [];
extract($context, EXTR_SKIP); // phpcs:ignore WordPress.PHP.DontExtract.extract_extract
$creator_args = is_array($args['creator_args'] ?? null) ? $args['creator_args'] : [];
$status = (string) ($creator_args['status'] ?? 'all');
$statuses = $status === 'all' ? ['publish', 'draft', 'pending', 'private'] : [$status];
$page = max(1, (int) ($_GET['creator_page'] ?? 1));
$q = new WP_Query(['author' => (int) $user_id, 'post_type' => 'post', 'post_status' => $statuses, 'posts_per_page' => 10, 'paged' => $page, 'no_found_rows' => false]);
$front_submit = class_exists('\\AKSRT\\Core\\FrontSubmit') ? \AKSRT\Core\FrontSubmit::get_instance() : null;
$status_labels = ['publish' => __('已发布', 'aksrt'), 'draft' => __('草稿', 'aksrt'), 'pending' => __('待审核', 'aksrt'), 'private' => __('私密', 'aksrt')];
?>
<?php if ($q->have_posts()) : ?>
<div class="creator-post-list">
<?php while ($q->have_posts()) : $q->the_post();
$post_id = get_the_ID();
$post_status = get_post_status();
$edit_url = $front_submit && $front_submit->can_edit_post($post_id, (int) $user_id) ? aksrt_get_new_post_url($post_id) : '';
$can_delete = $front_submit && $front_submit->can_delete_post($post_id, (int) $user_id);
$price = (float) get_post_meta($post_id, '_aksrt_pay_price', true);
?>
<article class="creator-post-row" data-post-id="<?php echo esc_attr((string) $post_id); ?>">
<a class="creator-post-row__thumb" href="<?php echo esc_url(get_permalink($post_id)); ?>" aria-label="<?php echo esc_attr(get_the_title()); ?>">
<?php if (has_post_thumbnail($post_id)) : the_post_thumbnail('medium'); else : ?><span><i class="fas fa-feather-alt" aria-hidden="true"></i></span><?php endif; ?>
</a>
<div class="creator-post-row__body"><div class="creator-post-row__heading"><h4><a href="<?php echo esc_url(get_permalink($post_id)); ?>"><?php echo esc_html(get_the_title()); ?></a></h4><span class="creator-post-row__status creator-post-row__status--<?php echo esc_attr($post_status); ?>"><?php echo esc_html($status_labels[$post_status] ?? $post_status); ?></span></div><p><?php echo esc_html(wp_trim_words(get_the_excerpt(), 20, '…')); ?></p><div class="creator-post-row__meta"><time datetime="<?php echo esc_attr(get_the_date('c')); ?>"><?php echo esc_html(get_the_date()); ?></time><span><?php echo $price > 0 ? esc_html__('付费内容', 'aksrt') : esc_html__('免费内容', 'aksrt'); ?></span></div></div>
<div class="creator-post-row__actions"><?php if ($edit_url) : ?><a href="<?php echo esc_url($edit_url); ?>" class="creator-post-row__action"><?php esc_html_e('编辑', 'aksrt'); ?></a><?php endif; ?><a href="<?php echo esc_url(get_permalink($post_id)); ?>" class="creator-post-row__action"><?php esc_html_e('查看', 'aksrt'); ?></a><?php if ($can_delete) : ?><button type="button" class="creator-post-row__action creator-post-row__action--danger" data-action="delete-post" data-post-id="<?php echo esc_attr((string) $post_id); ?>" data-nonce="<?php echo esc_attr(wp_create_nonce('aksrt_submit')); ?>"><?php esc_html_e('删除', 'aksrt'); ?></button><?php endif; ?></div>
</article>
<?php endwhile; ?>
</div>
<?php aksrt_render_profile_pagination((int) $q->max_num_pages, $page, 'creator_page'); wp_reset_postdata(); ?>
<?php else : ?>
<div class="creator-workspace__empty"><i class="fas fa-feather-alt" aria-hidden="true"></i><h4><?php esc_html_e('还没有文章', 'aksrt'); ?></h4><p><?php esc_html_e('开始创作你的第一篇文章吧。', 'aksrt'); ?></p><?php if (!empty($new_post_url)) : ?><a class="btn-primary btn-sm" href="<?php echo esc_url($new_post_url); ?>"><?php esc_html_e('写新文章', 'aksrt'); ?></a><?php endif; ?></div>
<?php endif; ?>