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.
189 lines
8.2 KiB
PHP
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);
|
|
}
|
|
}
|