Files
wordpress-theme-aksrt/inc/classes/shop/class-aksrt-product-variants-meta-box.php
T
kate522 83f0467084 feat(admin): native plate moderators, product variants, discount term fields
Add WP-native ops UI without SPA: plate moderator list/application review
meta box, product SKU variants editor meta box, and shop_discount taxonomy
add/edit form fields. No admin build artifacts included.
2026-07-15 02:19:06 +08:00

189 lines
8.2 KiB
PHP

<?php
/**
* Native SKU variants meta box for aksrt_product.
*
* @package AKSRT
*/
defined('ABSPATH') || exit;
class AKSRT_Product_Variants_Meta_Box {
private static $instance = null;
public static function get_instance(): self {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
public function init(): void {
if (!is_admin()) {
return;
}
add_action('add_meta_boxes', [$this, 'register_meta_box']);
add_action('save_post_aksrt_product', [$this, 'save'], 30, 2);
}
public function register_meta_box(): void {
add_meta_box(
'aksrt_product_variants',
__('SKU 变体', 'aksrt'),
[$this, 'render'],
'aksrt_product',
'normal',
'default'
);
}
public function render(WP_Post $post): void {
wp_nonce_field('aksrt_product_variants_save', 'aksrt_product_variants_nonce');
$variants = [];
if (class_exists('AKSRT_Shop')) {
$variants = AKSRT_Shop::get_instance()->get_variants((int) $post->ID);
}
if (!$variants) {
$variants = [[
'sku' => '',
'attributes' => [],
'price' => 0,
'stock' => 0,
'image' => '',
]];
}
?>
<style>
.aksrt-variants-table { width:100%; border-collapse:collapse; }
.aksrt-variants-table th, .aksrt-variants-table td { border:1px solid #dcdcde; padding:6px; vertical-align:top; }
.aksrt-variants-table input { width:100%; }
.aksrt-variants-table .col-sku { width:14%; }
.aksrt-variants-table .col-attrs { width:28%; }
.aksrt-variants-table .col-price, .aksrt-variants-table .col-stock { width:12%; }
.aksrt-variants-table .col-image { width:28%; }
.aksrt-variants-hint { color:#646970; font-size:12px; margin:8px 0; }
</style>
<p class="aksrt-variants-hint">
<?php esc_html_e('属性格式:名称:值,多项用分号分隔,例如 颜色:红;尺寸:L。保存时会整体覆盖该商品变体列表。', 'aksrt'); ?>
</p>
<table class="aksrt-variants-table" id="aksrt-variants-table">
<thead>
<tr>
<th class="col-sku"><?php esc_html_e('SKU', 'aksrt'); ?></th>
<th class="col-attrs"><?php esc_html_e('属性', 'aksrt'); ?></th>
<th class="col-price"><?php esc_html_e('价格', 'aksrt'); ?></th>
<th class="col-stock"><?php esc_html_e('库存', 'aksrt'); ?></th>
<th class="col-image"><?php esc_html_e('图片 URL', 'aksrt'); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($variants as $i => $row) :
$attrs = $row['attributes'] ?? [];
$attrs_text = '';
if (is_array($attrs)) {
$parts = [];
foreach ($attrs as $k => $v) {
if (is_int($k) && is_array($v)) {
$parts[] = ($v['name'] ?? $v['key'] ?? '') . ':' . ($v['value'] ?? '');
} else {
$parts[] = $k . ':' . $v;
}
}
$attrs_text = implode(';', array_filter($parts));
} elseif (is_string($attrs)) {
$attrs_text = $attrs;
}
?>
<tr>
<td><input type="text" name="aksrt_variant_sku[]" value="<?php echo esc_attr((string) ($row['sku'] ?? '')); ?>"></td>
<td><input type="text" name="aksrt_variant_attrs[]" value="<?php echo esc_attr($attrs_text); ?>" placeholder="颜色:红;尺寸:L"></td>
<td><input type="number" step="0.01" min="0" name="aksrt_variant_price[]" value="<?php echo esc_attr((string) ($row['price'] ?? 0)); ?>"></td>
<td><input type="number" step="1" min="0" name="aksrt_variant_stock[]" value="<?php echo esc_attr((string) ($row['stock'] ?? 0)); ?>"></td>
<td><input type="url" name="aksrt_variant_image[]" value="<?php echo esc_attr((string) ($row['image'] ?? '')); ?>"></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<p>
<button type="button" class="button" id="aksrt-add-variant-row"><?php esc_html_e('添加变体行', 'aksrt'); ?></button>
</p>
<script>
(function(){
var btn = document.getElementById('aksrt-add-variant-row');
var tbody = document.querySelector('#aksrt-variants-table tbody');
if (!btn || !tbody) return;
btn.addEventListener('click', function(){
var tr = document.createElement('tr');
tr.innerHTML = '<td><input type="text" name="aksrt_variant_sku[]" value=""></td>' +
'<td><input type="text" name="aksrt_variant_attrs[]" value="" placeholder="颜色:红;尺寸:L"></td>' +
'<td><input type="number" step="0.01" min="0" name="aksrt_variant_price[]" value="0"></td>' +
'<td><input type="number" step="1" min="0" name="aksrt_variant_stock[]" value="0"></td>' +
'<td><input type="url" name="aksrt_variant_image[]" value=""></td>';
tbody.appendChild(tr);
});
})();
</script>
<?php
}
public function save(int $post_id, WP_Post $post): void {
if (!isset($_POST['aksrt_product_variants_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash((string) $_POST['aksrt_product_variants_nonce'])), 'aksrt_product_variants_save')) {
return;
}
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
return;
}
if ($post->post_type !== 'aksrt_product' || !current_user_can('edit_post', $post_id)) {
return;
}
if (!class_exists('AKSRT_Shop')) {
return;
}
$skus = isset($_POST['aksrt_variant_sku']) && is_array($_POST['aksrt_variant_sku']) ? wp_unslash($_POST['aksrt_variant_sku']) : [];
$attrs = isset($_POST['aksrt_variant_attrs']) && is_array($_POST['aksrt_variant_attrs']) ? wp_unslash($_POST['aksrt_variant_attrs']) : [];
$prices = isset($_POST['aksrt_variant_price']) && is_array($_POST['aksrt_variant_price']) ? wp_unslash($_POST['aksrt_variant_price']) : [];
$stocks = isset($_POST['aksrt_variant_stock']) && is_array($_POST['aksrt_variant_stock']) ? wp_unslash($_POST['aksrt_variant_stock']) : [];
$images = isset($_POST['aksrt_variant_image']) && is_array($_POST['aksrt_variant_image']) ? wp_unslash($_POST['aksrt_variant_image']) : [];
$variants = [];
$count = max(count($skus), count($attrs), count($prices), count($stocks), count($images));
for ($i = 0; $i < $count; $i++) {
$sku = sanitize_text_field((string) ($skus[$i] ?? ''));
$attr_raw = sanitize_text_field((string) ($attrs[$i] ?? ''));
$price = max(0, (float) ($prices[$i] ?? 0));
$stock = max(0, (int) ($stocks[$i] ?? 0));
$image = esc_url_raw((string) ($images[$i] ?? ''));
if ($sku === '' && $attr_raw === '' && $price <= 0 && $stock <= 0 && $image === '') {
continue;
}
$attributes = [];
if ($attr_raw !== '') {
foreach (preg_split('/\s*;\s*/', $attr_raw) ?: [] as $pair) {
if ($pair === '' || !str_contains($pair, ':')) {
continue;
}
[$k, $v] = array_map('trim', explode(':', $pair, 2));
if ($k !== '') {
$attributes[$k] = $v;
}
}
}
$variants[] = [
'sku' => $sku,
'attributes' => $attributes,
'price' => $price,
'stock' => $stock,
'image' => $image,
];
}
AKSRT_Shop::get_instance()->save_variants($post_id, $variants);
}
}