diff --git a/assets/js/admin.js b/assets/js/admin.js
index 8574217..9709fd8 100644
--- a/assets/js/admin.js
+++ b/assets/js/admin.js
@@ -1,3 +1,30 @@
(function($){
- // 预留:后台联动逻辑(例如启用订阅后才显示其他字段)。
+ $(function(){
+ var $tbody = $('#yoone-sub-plans-body');
+ var $addBtn = $('#yoone-sub-plan-add');
+
+ if ($tbody.length && $addBtn.length) {
+ $addBtn.on('click', function(){
+ var row = [
+ '
';
+ // 面板在 simple/variable/yoone_bundle 上显示
+ echo '
';
echo '
';
// 启用订阅
@@ -117,6 +119,49 @@ class Yoone_Subscriptions_Admin {
'value' => $cfg['allow_onetime'] ? 'yes' : 'no',
));
+ // 多订阅计划管理
+ echo '
';
+ echo '
' . esc_html__('订阅计划列表', 'yoone-subscriptions') . '
';
+ echo '
' . esc_html__('可以为该产品配置多个订阅计划,前端用户可在购买时选择其一。每个计划可设置周期与每周期价格,或以折扣百分比表示相对产品价的优惠。', 'yoone-subscriptions') . '
';
+
+ $plans = isset($cfg['plans']) && is_array($cfg['plans']) ? $cfg['plans'] : array();
+ echo '
';
+ echo ''
+ . '| ' . esc_html__('计划名称', 'yoone-subscriptions') . ' | '
+ . '' . esc_html__('周期', 'yoone-subscriptions') . ' | '
+ . '' . esc_html__('每周期价格', 'yoone-subscriptions') . ' | '
+ . '' . esc_html__('折扣百分比(可选)', 'yoone-subscriptions') . ' | '
+ . '' . esc_html__('操作', 'yoone-subscriptions') . ' | '
+ . '
';
+ if (! empty($plans)) {
+ foreach ($plans as $idx => $p) {
+ echo '';
+ echo ' | ';
+ echo ' | ';
+ echo ' | ';
+ echo ' | ';
+ echo ' | ';
+ echo '
';
+ }
+ } else {
+ // 初始空行
+ echo ''
+ . ' | '
+ . ' | '
+ . ' | '
+ . ' | '
+ . ' | '
+ . '
';
+ }
+ echo '
';
+ echo '
';
+
echo '
';
}
@@ -156,6 +201,33 @@ class Yoone_Subscriptions_Admin {
update_post_meta($post_id, Yoone_Subscriptions::META_TIER_RULES, $tiers);
}
update_post_meta($post_id, Yoone_Subscriptions::META_ALLOW_ONETIME, $onetime ? '1' : '');
+
+ // 保存多订阅计划
+ $plans = array();
+ if (isset($_POST['yoone_sub_plans']) && is_array($_POST['yoone_sub_plans'])) {
+ $labels = isset($_POST['yoone_sub_plans']['label']) ? (array) $_POST['yoone_sub_plans']['label'] : array();
+ $periods = isset($_POST['yoone_sub_plans']['period']) ? (array) $_POST['yoone_sub_plans']['period'] : array();
+ $prices = isset($_POST['yoone_sub_plans']['price']) ? (array) $_POST['yoone_sub_plans']['price'] : array();
+ $discounts = isset($_POST['yoone_sub_plans']['discount_percent']) ? (array) $_POST['yoone_sub_plans']['discount_percent'] : array();
+ $count = max(count($labels), count($periods), count($prices), count($discounts));
+ for ($i=0; $i<$count; $i++) {
+ $label = isset($labels[$i]) ? sanitize_text_field($labels[$i]) : '';
+ $per = isset($periods[$i]) ? sanitize_text_field($periods[$i]) : 'month';
+ $per = in_array($per, array('month','year'), true) ? $per : 'month';
+ $price = isset($prices[$i]) ? wc_clean($prices[$i]) : '';
+ $price = ($price === '' ? 0.0 : floatval(wc_format_decimal($price, 2)));
+ $disc = isset($discounts[$i]) ? floatval($discounts[$i]) : 0.0;
+ if ($label === '' && $price <= 0 && $disc <= 0) continue; // 全空行跳过
+ $plans[] = array(
+ 'id' => uniqid('plan_'),
+ 'label' => $label ? $label : __('订阅计划', 'yoone-subscriptions'),
+ 'period' => $per,
+ 'price' => $price,
+ 'discount_percent' => max(0.0, $disc),
+ );
+ }
+ }
+ update_post_meta($post_id, Yoone_Subscriptions::META_PLANS, $plans);
}
/**
diff --git a/includes/class-yoone-subscriptions.php b/includes/class-yoone-subscriptions.php
index ce84d49..6285e4c 100644
--- a/includes/class-yoone-subscriptions.php
+++ b/includes/class-yoone-subscriptions.php
@@ -15,6 +15,8 @@ class Yoone_Subscriptions {
const META_ALLOW_ONETIME = '_yoone_sub_allow_onetime'; // bool
const META_MIN_QTY = '_yoone_sub_min_qty'; // int >=1(最小起订量,仅订阅模式下生效)
const META_TIER_RULES = '_yoone_sub_tier_rules'; // string 逗号分隔“数量:折扣%”对,如 10:5,20:10
+ // 新增:多订阅计划(每个计划包含:id、label、period、price、discount_percent 可选)
+ const META_PLANS = '_yoone_sub_plans'; // serialized array
protected static $instance = null;
@@ -47,9 +49,26 @@ class Yoone_Subscriptions {
$onetime = (bool) get_post_meta($pid, self::META_ALLOW_ONETIME, true);
$min_qty = absint(get_post_meta($pid, self::META_MIN_QTY, true));
$tiers = get_post_meta($pid, self::META_TIER_RULES, true);
+ $plans = get_post_meta($pid, self::META_PLANS, true);
$period = in_array($period, array('month','year'), true) ? $period : 'month';
$qty = max(1, $qty);
$price = is_numeric($price) ? floatval($price) : 0.0; // 0 表示按产品原价
+ // 规范化计划数组
+ if (! is_array($plans)) {
+ $plans = array();
+ } else {
+ $norm = array();
+ foreach ($plans as $p) {
+ $norm[] = array(
+ 'id' => isset($p['id']) ? sanitize_key($p['id']) : uniqid('plan_'),
+ 'label' => isset($p['label']) ? sanitize_text_field($p['label']) : __('订阅计划', 'yoone-subscriptions'),
+ 'period' => (isset($p['period']) && in_array($p['period'], array('month','year'), true)) ? $p['period'] : 'month',
+ 'price' => isset($p['price']) && is_numeric($p['price']) ? floatval($p['price']) : 0.0,
+ 'discount_percent' => isset($p['discount_percent']) && is_numeric($p['discount_percent']) ? floatval($p['discount_percent']) : 0.0,
+ );
+ }
+ $plans = $norm;
+ }
return array(
'enabled' => $enabled,
'period' => $period,
@@ -58,6 +77,7 @@ class Yoone_Subscriptions {
'allow_onetime' => $onetime,
'min_qty' => max(1, $min_qty),
'tier_rules' => is_string($tiers) ? $tiers : '',
+ 'plans' => $plans,
);
}
@@ -73,6 +93,7 @@ class Yoone_Subscriptions {
'allow_onetime' => true,
'min_qty' => 1,
'tier_rules' => '',
+ 'plans' => array(),
);
}
diff --git a/includes/frontend/class-yoone-subscriptions-frontend.php b/includes/frontend/class-yoone-subscriptions-frontend.php
index 0fd1e17..43e2110 100644
--- a/includes/frontend/class-yoone-subscriptions-frontend.php
+++ b/includes/frontend/class-yoone-subscriptions-frontend.php
@@ -118,6 +118,11 @@ class Yoone_Subscriptions_Frontend {
$cfg = Yoone_Subscriptions::get_config($product);
if (! $cfg['enabled']) return $cart_item_data;
+ // 若是混装子项目(存在父容器ID),不在子项目上应用订阅,避免重复计价
+ if (isset($cart_item_data['yoone_bundle_parent_id'])) {
+ return $cart_item_data;
+ }
+
$mode = isset($_POST['yoone_sub_purchase_mode']) ? sanitize_text_field($_POST['yoone_sub_purchase_mode']) : '';
$period = isset($_POST['yoone_sub_period']) ? sanitize_text_field($_POST['yoone_sub_period']) : $cfg['period'];
$qty = isset($_POST['yoone_sub_quantity']) ? absint($_POST['yoone_sub_quantity']) : $cfg['qty_default'];
@@ -133,7 +138,7 @@ class Yoone_Subscriptions_Frontend {
'mode' => 'subscribe',
'period' => $period,
'qty' => $qty,
- 'price' => ($cfg['price'] > 0 ? $cfg['price'] : floatval($product->get_price())), // 每周期价格
+ 'price' => ($cfg['price'] > 0 ? $cfg['price'] : floatval($product->get_price())), // 每周期价格;对于混装容器,将在后续根据子项目求和覆盖
);
return $cart_item_data;
}
@@ -176,6 +181,37 @@ class Yoone_Subscriptions_Frontend {
$sub_qty = absint($data['qty']);
$cart_qty = absint(isset($item['quantity']) ? $item['quantity'] : 1);
+ // 若为混装容器,则以其子项目的价格汇总作为每周期基价(若有配置价则优先使用配置价)
+ if (isset($item['yoone_bundle_container_id'])) {
+ $container_id = $item['yoone_bundle_container_id'];
+ // 使用配置价优先,否则求和子项目价
+ $cfg = Yoone_Subscriptions::get_config($product);
+ if (!($cfg['price'] > 0)) {
+ $sum = 0.0;
+ foreach ($cart->get_cart() as $k2 => $child) {
+ if (isset($child['yoone_bundle_parent_id']) && $child['yoone_bundle_parent_id'] === $container_id) {
+ $child_product = isset($child['data']) ? $child['data'] : null;
+ if ($child_product && is_a($child_product, 'WC_Product')) {
+ $child_qty = absint(isset($child['quantity']) ? $child['quantity'] : 1);
+ $sum += floatval($child_product->get_price()) * $child_qty;
+ }
+ }
+ }
+ $price_per_cycle = $sum; // 以子项目合计为每周期基价
+ } else {
+ $price_per_cycle = floatval($cfg['price']);
+ }
+ // 将子项目的价格置为 0,避免重复计费
+ foreach ($cart->get_cart() as $k2 => $child) {
+ if (isset($child['yoone_bundle_parent_id']) && $child['yoone_bundle_parent_id'] === $container_id) {
+ $child_product = isset($child['data']) ? $child['data'] : null;
+ if ($child_product && is_a($child_product, 'WC_Product')) {
+ $child_product->set_price(0);
+ }
+ }
+ }
+ }
+
$line_price = $price_per_cycle * $factor * $sub_qty * $cart_qty;
$product->set_price($line_price);
}