fix(pay): render paid reading as fragments

This commit is contained in:
2026-08-13 18:58:24 +08:00
parent 4cc1f2b2ea
commit f040a680d6
9 changed files with 316 additions and 65 deletions
@@ -119,3 +119,117 @@
opacity: .85;
}
/* ---- Paid fragment purchase card ---- */
.aksrt-paywall {
position: relative;
margin: 24px 0;
}
.aksrt-paywall__card {
position: relative;
z-index: 0;
display: grid;
grid-template-columns: auto minmax(0, 1fr) auto;
align-items: center;
gap: 18px;
padding: 22px 24px;
background: linear-gradient(135deg, var(--surface-1, #fff) 0%, var(--surface-2, #f8fafc) 100%);
border: 1px solid var(--color-line, #e5e7eb);
border-radius: var(--radius-lg, 14px);
box-shadow: 0 10px 28px rgba(15, 23, 42, .08);
}
.aksrt-paywall--fragment .aksrt-paywall__card {
border-style: dashed;
border-color: color-mix(in srgb, var(--color-primary, #f04494) 42%, var(--color-line, #e5e7eb));
}
.aksrt-paywall__icon {
display: grid;
place-items: center;
width: 44px;
height: 44px;
flex: 0 0 auto;
border-radius: 50%;
color: var(--color-primary, #f04494);
background: color-mix(in srgb, var(--color-primary, #f04494) 12%, transparent);
font-size: 20px;
}
.aksrt-paywall__body { min-width: 0; }
.aksrt-paywall__title {
margin: 0 0 4px;
color: var(--color-ink, #15171a);
font-size: 17px;
font-weight: 700;
}
.aksrt-paywall__desc {
margin: 0;
color: var(--color-ink-3, #525866);
font-size: 14px;
line-height: 1.6;
}
.aksrt-paywall__price { margin-top: 8px; }
.aksrt-paywall__price-amount { color: var(--color-error, #ef4444); font-size: 20px; font-weight: 700; }
.aksrt-paywall__price-member { color: var(--color-primary, #f04494); font-size: 16px; font-weight: 700; }
.aksrt-paywall__price-free { color: var(--color-success, #10b981); font-size: 16px; font-weight: 700; }
.aksrt-paywall__price-original { margin-left: 8px; color: var(--color-ink-4, #8a8f98); font-size: 13px; text-decoration: line-through; }
.aksrt-paywall__actions {
position: relative;
z-index: 1;
display: flex;
flex: 0 0 auto;
flex-direction: column;
align-items: center;
gap: 8px;
text-align: center;
}
.aksrt-paywall__btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
min-height: 40px;
white-space: nowrap;
cursor: pointer;
}
.aksrt-paywall__btn:focus-visible,
.aksrt-paywall__login-link:focus-visible {
outline: 3px solid color-mix(in srgb, var(--color-primary, #f04494) 35%, transparent);
outline-offset: 3px;
}
.aksrt-paywall__hint,
.aksrt-paywall__login-link {
margin: 0;
color: var(--color-ink-4, #8a8f98);
font-size: 12px;
line-height: 1.5;
}
.aksrt-paywall__login-link { text-decoration: underline; }
.aksrt-gallery-locked { filter: blur(8px); pointer-events: none; user-select: none; }
.aksrt-paywall-preview { margin: 0 0 16px; padding: 14px 16px; border-left: 3px solid var(--color-primary, #f04494); background: var(--surface-2, #f8fafc); border-radius: 0 10px 10px 0; color: var(--color-ink-2, #374151); line-height: 1.7; }
.aksrt-paywall-preview p:last-child { margin-bottom: 0; }
.aksrt-paid-gallery__overlay .but, .aksrt-paid-video__placeholder .but { margin-top: 10px; }
@media (max-width: 680px) {
.aksrt-paywall__card {
grid-template-columns: auto minmax(0, 1fr);
padding: 18px;
}
.aksrt-paywall__actions {
grid-column: 1 / -1;
align-items: stretch;
width: 100%;
}
.aksrt-paywall__btn { width: 100%; }
}
+31
View File
@@ -363,6 +363,37 @@
background: var(--surface-2, #f8fafc);
}
/* ---- Paid Fragment ---- */
.aksrt-paid-fragment-block {
margin: 16px 0;
border: 1px dashed #ec4899;
border-radius: 12px;
overflow: hidden;
background: #fff7fb;
}
.aksrt-paid-fragment-block .components-notice {
margin: 12px;
}
.aksrt-paid-fragment-block__label {
display: flex;
align-items: center;
gap: 8px;
padding: 9px 14px;
color: #9d174d;
background: #fce7f3;
font-size: 13px;
font-weight: 700;
}
.aksrt-paid-fragment-block__content {
min-height: 72px;
padding: 16px;
background: #fff;
}
/* ── Responsive ── */
@media (max-width: 767px) {
+57
View File
@@ -16,6 +16,7 @@
var el = wp.element.createElement;
var Fragment = wp.element.Fragment;
var RawHTML = wp.element.RawHTML;
var registerBlockTypeRaw = wp.blocks.registerBlockType;
var blockEditor = wp.blockEditor || wp.editor || {};
var InspectorControls = blockEditor.InspectorControls || null;
@@ -28,6 +29,8 @@
var TextControl = wp.components.TextControl;
var ToggleControl = wp.components.ToggleControl;
var RangeControl = wp.components.RangeControl;
var Notice = wp.components.Notice;
var useSelect = window.wp.data && window.wp.data.useSelect;
if (!registerBlockTypeRaw || !el) return;
@@ -750,4 +753,58 @@
});
}
// ================================================================
// 9. Paid Fragment Block
// ================================================================
function usePaidReadingConfigured() {
if (!useSelect) return true;
return useSelect(function (select) {
var editor = select('core/editor');
if (!editor || !editor.getEditedPostAttribute) return true;
var meta = editor.getEditedPostAttribute('meta') || {};
var pay = meta._aksrt_pay || {};
if (pay.pay_type !== 'reading') return false;
return pay.pay_modo === 'points'
? Number(pay.points_price || 0) > 0
: Number(pay.price || 0) > 0;
}, []);
}
registerBlockType('aksrt/paid-fragment', {
title: '付费可见',
icon: 'lock',
category: CATEGORY,
description: '仅购买文章付费阅读后可见的片段。',
attributes: {
content: { type: 'string', default: '' },
},
edit: function (props) {
var configured = usePaidReadingConfigured();
return h('div', { className: 'aksrt-paid-fragment-block' },
!configured && Notice ? h(Notice, { status: 'warning', isDismissible: false },
'请在“付费内容设置”中选择“付费阅读”并设置有效价格;未配置时此片段会公开显示。'
) : null,
h('div', { className: 'aksrt-paid-fragment-block__label' },
h('span', { className: 'dashicons dashicons-lock', 'aria-hidden': true }),
'付费可见片段'
),
h(RichText, {
tagName: 'div',
className: 'aksrt-paid-fragment-block__content',
value: props.attributes.content,
onChange: function (value) { props.setAttributes({ content: value }); },
placeholder: '输入购买后可见的内容…',
})
);
},
save: function (props) {
// Save a single canonical shortcode so hand-written and block-authored
// content use the identical server-side rendering path.
var shortcode = '[hidecontent type="payshow"]' + props.attributes.content + '[/hidecontent]';
return RawHTML ? h(RawHTML, null, shortcode) : shortcode;
},
});
})();