From 08a6245a6b83f132fc72a5e827c341e8b2901b52 Mon Sep 17 00:00:00 2001 From: tikkhun Date: Fri, 7 Nov 2025 10:41:50 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20Gutenberg=E3=80=81?= =?UTF-8?q?Elementor=20=E5=92=8C=E7=9F=AD=E4=BB=A3=E7=A0=81=E6=94=AF?= =?UTF-8?q?=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 Gutenberg 区块、Elementor 小组件和短代码功能,用于在产品页显示混装选择表单。扩展插件兼容性,支持更多内容构建方式。 - 注册 Gutenberg 区块,支持动态渲染混装产品选择表单 - 添加 Elementor 小组件,提供可视化配置界面 - 实现短代码功能,方便在文章/页面中插入表单 - 更新插件元数据,声明 WooCommerce 依赖 --- .gitignore | 20 ++--- assets/js/blocks/bundle-selector.js | 52 ++++++++++++ includes/blocks/register.php | 78 ++++++++++++++++++ .../class-yoone-pb-elementor-widget.php | 80 +++++++++++++++++++ includes/shortcodes/register.php | 59 ++++++++++++++ yoone-product-bundles.php | 15 ++++ 6 files changed, 294 insertions(+), 10 deletions(-) create mode 100644 assets/js/blocks/bundle-selector.js create mode 100644 includes/blocks/register.php create mode 100644 includes/elementor/class-yoone-pb-elementor-widget.php create mode 100644 includes/shortcodes/register.php diff --git a/.gitignore b/.gitignore index b94d5f6..48e816f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,11 @@ # 忽略 docs 目录中的所有图片与设计源文件(防止误提交到仓库) -docs/**/*.png -docs/**/*.jpg -docs/**/*.jpeg -docs/**/*.gif -docs/**/*.svg -docs/**/*.webp -docs/**/*.psd -docs/**/*.ai -docs/**/*.fig -docs/**/*.sketch \ No newline at end of file +**/*.png +**/*.jpg +**/*.jpeg +**/*.gif +**/*.svg +**/*.webp +**/*.psd +**/*.ai +**/*.fig +**/*.sketch \ No newline at end of file diff --git a/assets/js/blocks/bundle-selector.js b/assets/js/blocks/bundle-selector.js new file mode 100644 index 0000000..161a5c4 --- /dev/null +++ b/assets/js/blocks/bundle-selector.js @@ -0,0 +1,52 @@ +/* + * Gutenberg Block: Yoone Bundle Selector + * 在产品页插入混装产品选择与加购表单。 + */ +(function(wp){ + const { registerBlockType } = wp.blocks; + const { __ } = wp.i18n; + const { InspectorControls } = wp.blockEditor || wp.editor; + const { PanelBody, ToggleControl, TextControl } = wp.components; + + registerBlockType('yoone/bundle-selector', { + title: __('Yoone Bundle Selector', 'yoone-product-bundles'), + icon: 'cart', + category: 'widgets', + attributes: { + useCurrentProduct: { type: 'boolean', default: true }, + productId: { type: 'number', default: 0 }, + }, + description: __('在产品页显示“混装产品列表与加购”表单;可选择当前产品或指定产品ID。', 'yoone-product-bundles'), + edit: (props) => { + const { attributes, setAttributes } = props; + const { useCurrentProduct, productId } = attributes; + return ( + wp.element.createElement('div', { className: 'yoone-pb-block-editor' }, + wp.element.createElement(InspectorControls, null, + wp.element.createElement(PanelBody, { title: __('设置', 'yoone-product-bundles'), initialOpen: true }, + wp.element.createElement(ToggleControl, { + label: __('使用当前产品页面', 'yoone-product-bundles'), + checked: !!useCurrentProduct, + onChange: (val) => setAttributes({ useCurrentProduct: !!val }) + }), + !useCurrentProduct && wp.element.createElement(TextControl, { + label: __('指定产品ID(Mix and Match 类型)', 'yoone-product-bundles'), + type: 'number', + value: productId || 0, + onChange: (val) => setAttributes({ productId: parseInt(val || '0', 10) || 0 }) + }) + ) + ), + wp.element.createElement('div', { className: 'yoone-pb-block-preview' }, + wp.element.createElement('p', null, __('Yoone Bundle Selector(编辑器预览)', 'yoone-product-bundles')), + wp.element.createElement('p', null, useCurrentProduct + ? __('将渲染当前产品的混装选择表单(需为 Mix and Match 类型)。', 'yoone-product-bundles') + : __('将渲染指定产品ID的混装选择表单。', 'yoone-product-bundles') + ) + ) + ) + ); + }, + save: () => null, // 由后端动态渲染 + }); +})(window.wp); \ No newline at end of file diff --git a/includes/blocks/register.php b/includes/blocks/register.php new file mode 100644 index 0000000..b7613bb --- /dev/null +++ b/includes/blocks/register.php @@ -0,0 +1,78 @@ + 'yoone-pb-blocks', + 'render_callback' => array($this, 'render_bundle_selector_block'), + 'attributes' => array( + 'useCurrentProduct' => array('type' => 'boolean', 'default' => true), + 'productId' => array('type' => 'integer', 'default' => 0), + ), + 'supports' => array('anchor' => true), + )); + } + + /** + * 动态渲染区块内容:在产品页或指定产品ID下渲染混装选择表单。 + */ + public function render_bundle_selector_block($attributes, $content) { + // 仅在前端渲染;编辑器中显示占位提示 + if (is_admin() && function_exists('wp_doing_ajax') && ! wp_doing_ajax()) { + return '
' . esc_html__('Yoone Bundle Selector (预览):此区块在产品页前端渲染完整选择表单。', 'yoone-product-bundles') . '
'; + } + + $use_current = ! empty($attributes['useCurrentProduct']); + $product_id = absint(isset($attributes['productId']) ? $attributes['productId'] : 0); + + // 确定要渲染的产品对象 + $product = null; + if ($use_current && is_singular('product')) { + global $post; + if ($post) $product = wc_get_product($post->ID); + } elseif ($product_id > 0) { + $product = wc_get_product($product_id); + } + + // 安全检查:仅对我们定义的混装产品类型渲染表单 + if (! $product || $product->get_type() !== Yoone_Product_Bundles::TYPE) { + return '
' . esc_html__('请选择或切换到一个“Mix and Match (Yoone Bundle)”产品以显示表单。', 'yoone-product-bundles') . '
'; + } + + // 前端资源 + wp_enqueue_style('yoone-pb-frontend'); + wp_enqueue_script('yoone-pb-frontend'); + + // 复用插件模板输出完整表单 + ob_start(); + wc_get_template('global/yoone-bundle-form.php', array(), '', YOONE_PB_PATH . 'templates/'); + return ob_get_clean(); + } +} \ No newline at end of file diff --git a/includes/elementor/class-yoone-pb-elementor-widget.php b/includes/elementor/class-yoone-pb-elementor-widget.php new file mode 100644 index 0000000..74bfde1 --- /dev/null +++ b/includes/elementor/class-yoone-pb-elementor-widget.php @@ -0,0 +1,80 @@ +start_controls_section('section_settings', array('label' => __('设置', 'yoone-product-bundles'))); + + $this->add_control('use_current_product', array( + 'label' => __('使用当前产品页面', 'yoone-product-bundles'), + 'type' => \Elementor\Controls_Manager::SWITCHER, + 'label_on' => __('是', 'yoone-product-bundles'), + 'label_off' => __('否', 'yoone-product-bundles'), + 'return_value' => 'yes', + 'default' => 'yes', + )); + + $this->add_control('product_id', array( + 'label' => __('指定产品ID(Mix and Match 类型)', 'yoone-product-bundles'), + 'type' => \Elementor\Controls_Manager::NUMBER, + 'default' => 0, + 'condition' => array('use_current_product!' => 'yes'), + )); + + $this->end_controls_section(); + } + + protected function render() { + $settings = $this->get_settings_for_display(); + $use_current = isset($settings['use_current_product']) && $settings['use_current_product'] === 'yes'; + $product_id = absint(isset($settings['product_id']) ? $settings['product_id'] : 0); + + // 确定产品对象 + $product = null; + if ($use_current && is_singular('product')) { + global $post; if ($post) $product = wc_get_product($post->ID); + } elseif ($product_id > 0) { + $product = wc_get_product($product_id); + } + + if (! $product || $product->get_type() !== Yoone_Product_Bundles::TYPE) { + echo '
' . esc_html__('请选择或切换到一个“Mix and Match (Yoone Bundle)”产品以显示表单。', 'yoone-product-bundles') . '
'; + return; + } + + // 前端资源 + wp_enqueue_style('yoone-pb-frontend'); + wp_enqueue_script('yoone-pb-frontend'); + + // 渲染模板 + wc_get_template('global/yoone-bundle-form.php', array(), '', YOONE_PB_PATH . 'templates/'); + } + + public static function register() { + if (! class_exists('Elementor\\Plugin')) return; + add_action('elementor/widgets/register', function($widgets_manager) { + $widgets_manager->register(new self()); + }); + } + } + } +} else { + // 定义一个降级的空壳类,至少保证调用 register() 不会报错 + if (! class_exists('Yoone_PB_Elementor_Widget')) { + class Yoone_PB_Elementor_Widget { + public static function register() { /* noop: Elementor 未加载*/ } + } + } +} \ No newline at end of file diff --git a/includes/shortcodes/register.php b/includes/shortcodes/register.php new file mode 100644 index 0000000..4da52dd --- /dev/null +++ b/includes/shortcodes/register.php @@ -0,0 +1,59 @@ + 'yes', // yes|no + 'product_id' => 0, + ), $atts, 'yoone_bundle_selector'); + + $use_current = strtolower($atts['use_current_product']) === 'yes' || $atts['use_current_product'] === '1' || $atts['use_current_product'] === 1; + $product_id = absint($atts['product_id']); + + // 解析产品对象 + $product = null; + if ($use_current && function_exists('is_singular') && is_singular('product')) { + global $post; if ($post) $product = wc_get_product($post->ID); + } elseif ($product_id > 0) { + $product = wc_get_product($product_id); + } + + // 安全检查:仅渲染我们定义的混装产品类型 + if (! $product || ! method_exists($product, 'get_type') || $product->get_type() !== Yoone_Product_Bundles::TYPE) { + return '
' . esc_html__('请选择或传入一个 “Mix and Match (Yoone Bundle)” 产品以显示表单。', 'yoone-product-bundles') . '
'; + } + + // 前端资源 + wp_enqueue_style('yoone-pb-frontend'); + wp_enqueue_script('yoone-pb-frontend'); + + // 输出模板 + ob_start(); + wc_get_template('global/yoone-bundle-form.php', array(), '', YOONE_PB_PATH . 'templates/'); + return ob_get_clean(); + } + } + + // 启动短代码注册 + Yoone_PB_Shortcodes::init(); +} \ No newline at end of file diff --git a/yoone-product-bundles.php b/yoone-product-bundles.php index 78698f0..023c4a0 100644 --- a/yoone-product-bundles.php +++ b/yoone-product-bundles.php @@ -6,6 +6,7 @@ * Version: 0.1.0 * Requires at least: 6.0 * Requires PHP: 7.4 + * Requires Plugins: woocommerce * WC requires at least: 6.0 * WC tested up to: 8.x */ @@ -29,6 +30,9 @@ require_once YOONE_PB_PATH . 'includes/class-yoone-product-bundles.php'; require_once YOONE_PB_PATH . 'includes/class-yoone-product-type-bundle.php'; require_once YOONE_PB_PATH . 'includes/admin/class-yoone-product-bundles-admin.php'; require_once YOONE_PB_PATH . 'includes/frontend/class-yoone-product-bundles-frontend.php'; +require_once YOONE_PB_PATH . 'includes/blocks/register.php'; +require_once YOONE_PB_PATH . 'includes/shortcodes/register.php'; +// 注意:Elementor 小组件文件依赖 Elementor 的类,需在 Elementor 加载后再引入,避免致命错误 // 引导插件 add_action('plugins_loaded', function () { @@ -36,6 +40,17 @@ add_action('plugins_loaded', function () { Yoone_Product_Bundles::instance(); Yoone_Product_Bundles_Admin::instance(); Yoone_Product_Bundles_Frontend::instance(); + // 注册 Gutenberg 区块 + if (function_exists('register_block_type')) { + Yoone_PB_Blocks::instance(); + } + // 注册 Elementor 小组件(在 Elementor 通知 widgets 可注册时再加载并注册) + add_action('elementor/widgets/register', function($widgets_manager){ + require_once YOONE_PB_PATH . 'includes/elementor/class-yoone-pb-elementor-widget.php'; + if (class_exists('Yoone_PB_Elementor_Widget') && class_exists('\\Elementor\\Widget_Base')) { + $widgets_manager->register(new Yoone_PB_Elementor_Widget()); + } + }); }); // 插件版本号