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());
+ }
+ });
});
// 插件版本号