refactor(admin): 统一形状、表情和媒体的卡片式管理界面

重构后台管理界面,将形状、表情和媒体的管理统一为卡片式布局:
1. 移除单独的权重输入区域,将权重输入集成到每个卡片中
2. 使用中性灰色背景提升预览区域的可读性
3. 添加类型选择下拉菜单,动态切换不同内容类型的添加面板
4. 确保所有形状脚本依赖正确加载
5. 优化现有卡片初始化时的预览渲染逻辑
This commit is contained in:
tikkhun 2025-12-12 09:17:20 +08:00
parent ec507d3aa9
commit 00471c205f
2 changed files with 188 additions and 312 deletions

View File

@ -11,9 +11,9 @@
} }
} }
// 条件判断 如果没有媒体按钮则不执行 // 条件判断 如果没有媒体按钮则不执行
var addButton = document.getElementById('yooneSnowAddMedia'); var addMediaUnifiedButton = document.getElementById('yooneSnowAddMediaUnified');
var listContainer = document.getElementById('yooneSnowMediaList'); var listContainer = null;
var emojiListContainer = document.getElementById('yooneSnowEmojiList'); var emojiListContainer = null;
var emojiInput = document.getElementById('yooneSnowAddEmojiInput'); var emojiInput = document.getElementById('yooneSnowAddEmojiInput');
var emojiAddButton = document.getElementById('yooneSnowAddEmoji'); var emojiAddButton = document.getElementById('yooneSnowAddEmoji');
var emojiSuggestBox = document.getElementById('yooneSnowEmojiSuggest'); var emojiSuggestBox = document.getElementById('yooneSnowEmojiSuggest');
@ -21,13 +21,16 @@
var shapeAddSelect = document.getElementById('yooneSnowAddShapeSelect'); var shapeAddSelect = document.getElementById('yooneSnowAddShapeSelect');
var shapeAddButton = document.getElementById('yooneSnowAddShapeBtn'); var shapeAddButton = document.getElementById('yooneSnowAddShapeBtn');
var emojiSelect = document.getElementById('yooneSnowEmojiSelect'); var emojiSelect = document.getElementById('yooneSnowEmojiSelect');
if (!addButton || !listContainer) { return; } var typeSelect = document.getElementById('yooneSnowAddTypeSelect');
var paneDefault = document.getElementById('yooneSnowAddDefaultPane');
var paneEmoji = document.getElementById('yooneSnowAddEmojiPane');
var paneMedia = document.getElementById('yooneSnowAddMediaPane');
// 统一列表容器为形状列表容器 旧容器不再使用
listContainer = shapeListContainer;
emojiListContainer = shapeListContainer;
if (!shapeListContainer) { return; }
// 形状与媒体权重容器元素 获取以便动态更新 // 权重输入已整合到卡片内部 旧容器不再使用
var weightsContainer = document.getElementById('yooneSnowWeightsContainer');
var shapeWeightsBox = document.getElementById('yooneSnowShapeWeights');
var mediaWeightsBox = document.getElementById('yooneSnowMediaWeights');
var emojiWeightsBox = document.getElementById('yooneSnowEmojiWeights');
// 形状标签映射用于显示友好名称 // 形状标签映射用于显示友好名称
var shapeLabelsMap = { var shapeLabelsMap = {
@ -53,8 +56,8 @@
canvas.width = 32; canvas.width = 32;
canvas.height = 32; canvas.height = 32;
canvas.style.marginRight = '6px'; canvas.style.marginRight = '6px';
// 为白色形状提供可见的暗底与边框 提升可读性 // 使用中性灰色背景提升可读性
canvas.style.background = '#2d3748'; canvas.style.backgroundColor = '#e6e6e6';
canvas.style.border = '1px solid #ddd'; canvas.style.border = '1px solid #ddd';
canvas.style.borderRadius = '4px'; canvas.style.borderRadius = '4px';
var ctx = canvas.getContext('2d'); var ctx = canvas.getContext('2d');
@ -106,6 +109,9 @@
img.style.height = '28px'; img.style.height = '28px';
img.style.objectFit = 'contain'; img.style.objectFit = 'contain';
img.style.marginRight = '6px'; img.style.marginRight = '6px';
img.style.backgroundColor = '#e6e6e6';
img.style.border = '1px solid #ddd';
img.style.borderRadius = '4px';
return img; return img;
} }
// 默认降级 使用文本首字母表示 // 默认降级 使用文本首字母表示
@ -114,6 +120,10 @@
fallback.height = 32; fallback.height = 32;
fallback.style.marginRight = '6px'; fallback.style.marginRight = '6px';
var fctx = fallback.getContext('2d'); var fctx = fallback.getContext('2d');
// 使用中性灰色背景提升可读性
fallback.style.backgroundColor = '#e6e6e6';
fallback.style.border = '1px solid #ddd';
fallback.style.borderRadius = '4px';
fctx.fillStyle = '#555'; fctx.fillStyle = '#555';
fctx.font = '12px system-ui'; fctx.font = '12px system-ui';
fctx.textAlign = 'center'; fctx.textAlign = 'center';
@ -122,140 +132,23 @@
fctx.fillText(txt, 16, 16); fctx.fillText(txt, 16, 16);
return fallback; return fallback;
} }
// 初始化时为现有卡片补齐预览
// 为形状复选框添加图形预览 替代纯文字展示 // 为现有形状卡片填充预览 保证刷新页面后仍可见
function renderShapeCheckboxPreviews(){ function renderExistingShapeCardPreviews(){
// 查找所有形状复选框 遍历其父级标签容器 if (!shapeListContainer) { return; }
var inputs = document.querySelectorAll('input[name="yoone_snow_mixed_items[]"]'); var items = shapeListContainer.querySelectorAll('.yoone-snow-shape-item');
inputs.forEach(function(cb){
var label = cb.closest('label');
if (!label) { return; }
// 条件判断 若已绘制过预览则跳过
if (label.querySelector('.yoone-shape-preview')) { return; }
var key = cb.value;
// 创建预览节点并插入在文本前
var wrap = document.createElement('span');
wrap.className = 'yoone-shape-preview';
wrap.style.display = 'inline-flex';
wrap.style.alignItems = 'center';
wrap.style.marginRight = '6px';
var previewEl = createShapePreviewElement(key);
wrap.appendChild(previewEl);
// 插入到复选框后的位置便于对齐
cb.insertAdjacentElement('afterend', wrap);
// 同步更新文字标签 保留友好名称
var nameSpan = label.querySelector('span');
if (!nameSpan){
nameSpan = document.createElement('span');
nameSpan.textContent = shapeLabelsMap[key] || key;
nameSpan.style.marginLeft = '4px';
wrap.insertAdjacentElement('afterend', nameSpan);
}
});
}
// 同步形状权重输入 根据勾选状态增删对应输入
function ensureShapeWeightInputs(){
if (!shapeWeightsBox) { return; }
var presentKeys = {};
var existingLabels = shapeWeightsBox.querySelectorAll('label[data-shape-key]');
existingLabels.forEach(function(el){ presentKeys[el.getAttribute('data-shape-key')] = el; });
var items = shapeListContainer ? shapeListContainer.querySelectorAll('.yoone-snow-shape-item') : [];
items.forEach(function(item){ items.forEach(function(item){
var key = item.getAttribute('data-shape-key'); var key = item.getAttribute('data-shape-key');
if (!presentKeys[key]){ var host = item.querySelector('.yoone-snow-shape-preview');
var lab = document.createElement('label'); if (!host) { return; }
lab.setAttribute('data-shape-key', key); // 条件判断 若已有内容则跳过 避免重复渲染
lab.style.display = 'flex'; if (host.childNodes && host.childNodes.length > 0) { return; }
lab.style.flexDirection = 'column'; var previewEl = createShapePreviewElement(key);
lab.style.minWidth = '160px'; if (previewEl) { host.appendChild(previewEl); }
var title = document.createElement('span');
title.textContent = shapeLabelsMap[key] || key;
var input = document.createElement('input');
input.type = 'number';
input.min = '0';
input.name = 'yoone_snow_shape_weights[' + key + ']';
input.value = '1';
input.style.width = '120px';
lab.appendChild(title);
lab.appendChild(input);
shapeWeightsBox.appendChild(lab);
}
}); });
for (var k in presentKeys){
var stillSelected = shapeListContainer && shapeListContainer.querySelector('.yoone-snow-shape-item[data-shape-key="' + k + '"]');
if (!stillSelected){ presentKeys[k].remove(); }
}
} }
renderExistingShapeCardPreviews();
// 添加媒体权重输入 根据附件 ID 创建输入 默认值为 1
function addMediaWeightInput(attachmentId){
if (!mediaWeightsBox) { return; }
var exist = mediaWeightsBox.querySelector('label[data-attachment-id="' + String(attachmentId) + '"]');
if (exist) { return; }
var lab = document.createElement('label');
lab.setAttribute('data-attachment-id', String(attachmentId));
lab.style.display = 'flex';
lab.style.flexDirection = 'column';
lab.style.minWidth = '160px';
var title = document.createElement('span');
title.textContent = 'Media ' + String(attachmentId);
var input = document.createElement('input');
input.type = 'number';
input.min = '0';
input.name = 'yoone_snow_media_weights[' + String(attachmentId) + ']';
input.value = '1';
input.style.width = '120px';
lab.appendChild(title);
lab.appendChild(input);
mediaWeightsBox.appendChild(lab);
}
// 添加 emoji 權重輸入 根據字符創建輸入 默認值為 1
function addEmojiWeightInput(emojiChar){
if (!emojiWeightsBox) { return; }
var key = String(emojiChar);
if (key.trim() === '') { return; }
var exist = emojiWeightsBox.querySelector('label[data-emoji-char="' + key + '"]');
if (exist) { return; }
var lab = document.createElement('label');
lab.setAttribute('data-emoji-char', key);
lab.style.display = 'flex';
lab.style.flexDirection = 'column';
lab.style.minWidth = '160px';
var title = document.createElement('span');
title.textContent = key;
var input = document.createElement('input');
input.type = 'number';
input.min = '0';
input.name = 'yoone_snow_emoji_weights[' + key + ']';
input.value = '1';
input.style.width = '120px';
lab.appendChild(title);
lab.appendChild(input);
emojiWeightsBox.appendChild(lab);
}
// 移除 emoji 權重輸入 根據字符刪除對應輸入
function removeEmojiWeightInput(emojiChar){
if (!emojiWeightsBox) { return; }
var key = String(emojiChar);
var exist = emojiWeightsBox.querySelector('label[data-emoji-char="' + key + '"]');
if (exist) { exist.remove(); }
}
// 移除媒体权重输入 根据附件 ID 删除对应输入
function removeMediaWeightInput(attachmentId){
if (!mediaWeightsBox) { return; }
var exist = mediaWeightsBox.querySelector('label[data-attachment-id="' + String(attachmentId) + '"]');
if (exist) { exist.remove(); }
}
// 初始化时同步一次形状权重输入
ensureShapeWeightInputs();
// 初始化时为形状复选框添加图形预览
renderShapeCheckboxPreviews();
ensureShapeWeightInputs();
// 添加形状卡片 并插入预览与隐藏输入 用于保存选择 // 添加形状卡片 并插入预览与隐藏输入 用于保存选择
function addShapeBox(shapeKey){ function addShapeBox(shapeKey){
@ -278,8 +171,18 @@
previewHost.style.width = '32px'; previewHost.style.width = '32px';
previewHost.style.height = '32px'; previewHost.style.height = '32px';
previewHost.style.marginBottom = '6px'; previewHost.style.marginBottom = '6px';
previewHost.style.backgroundColor = '#e6e6e6';
previewHost.style.border = '1px solid #ddd';
previewHost.style.borderRadius = '4px';
var nameSpan = document.createElement('span'); var nameSpan = document.createElement('span');
nameSpan.textContent = shapeLabelsMap[key] || key; nameSpan.textContent = shapeLabelsMap[key] || key;
var weightInput = document.createElement('input');
weightInput.type = 'number';
weightInput.min = '0';
weightInput.name = 'yoone_snow_shape_weights[' + key + ']';
weightInput.value = '1';
weightInput.style.width = '120px';
weightInput.style.marginTop = '6px';
var input = document.createElement('input'); var input = document.createElement('input');
input.type = 'hidden'; input.type = 'hidden';
input.name = 'yoone_snow_mixed_items[]'; input.name = 'yoone_snow_mixed_items[]';
@ -291,26 +194,16 @@
cancelBtn.style.marginTop = '6px'; cancelBtn.style.marginTop = '6px';
wrapper.appendChild(previewHost); wrapper.appendChild(previewHost);
wrapper.appendChild(nameSpan); wrapper.appendChild(nameSpan);
wrapper.appendChild(weightInput);
wrapper.appendChild(input); wrapper.appendChild(input);
wrapper.appendChild(cancelBtn); wrapper.appendChild(cancelBtn);
shapeListContainer.appendChild(wrapper); shapeListContainer.appendChild(wrapper);
var previewEl = createShapePreviewElement(key); var previewEl = createShapePreviewElement(key);
if (previewEl) { previewHost.appendChild(previewEl); } if (previewEl) { previewHost.appendChild(previewEl); }
ensureShapeWeightInputs();
} }
// 监听形状复选框变化事件 动态刷新权重输入
document.addEventListener('change', function(event){
var t = event.target;
if (t && t.name === 'yoone_snow_mixed_items[]'){
ensureShapeWeightInputs();
// 复选状态变更后也尝试补齐预览
renderShapeCheckboxPreviews();
}
});
// 绑定移除按钮事件 使用事件委托处理动态元素 // 绑定移除按钮事件 使用事件委托处理动态元素
listContainer.addEventListener('click', function(event){ shapeListContainer.addEventListener('click', function(event){
// 条件判断 如果点击的是移除按钮则执行删除 // 条件判断 如果点击的是移除按钮则执行删除
var target = event.target; var target = event.target;
if (target && target.classList.contains('yoone-snow-remove-media')){ if (target && target.classList.contains('yoone-snow-remove-media')){
@ -318,30 +211,29 @@
if (item) { if (item) {
var aidStr = item.getAttribute('data-attachment-id'); var aidStr = item.getAttribute('data-attachment-id');
item.remove(); item.remove();
// 同步移除对应媒体权重输入 // 媒体权重输入已在卡片内 无需额外删除
if (aidStr) { removeMediaWeightInput(aidStr); }
} }
} }
}); });
// 綁定移除 emoji 按鈕事件 使用事件委託處理動態元素 // 綁定移除 emoji 按鈕事件 使用事件委託處理動態元素
if (emojiListContainer){ if (shapeListContainer){
emojiListContainer.addEventListener('click', function(event){ shapeListContainer.addEventListener('click', function(event){
var target = event.target; var target = event.target;
if (target && target.classList.contains('yoone-snow-remove-emoji')){ if (target && target.classList.contains('yoone-snow-remove-emoji')){
var item = target.closest('.yoone-snow-emoji-item'); var item = target.closest('.yoone-snow-emoji-item');
if (item){ if (item){
var ch = item.getAttribute('data-emoji-char'); var ch = item.getAttribute('data-emoji-char');
item.remove(); item.remove();
// 同步移除對應 emoji 權重輸入 // Emoji 权重输入已在卡片内 无需额外删除
if (ch) { removeEmojiWeightInput(ch); }
} }
} }
}); });
} }
// 打开媒体选择器 支持多选 图片和 SVG // 打开媒体选择器 支持多选 图片和 SVG
addButton.addEventListener('click', function(){ if (addMediaUnifiedButton){
addMediaUnifiedButton.addEventListener('click', function(){
// 条件判断 如果 wp.media 不可用则终止 // 条件判断 如果 wp.media 不可用则终止
if (typeof wp === 'undefined' || !wp.media) { return; } if (typeof wp === 'undefined' || !wp.media) { return; }
var frame = wp.media({ var frame = wp.media({
@ -370,6 +262,13 @@
img.style.width = '72px'; img.style.width = '72px';
img.style.height = '72px'; img.style.height = '72px';
img.style.objectFit = 'contain'; img.style.objectFit = 'contain';
var weightInput = document.createElement('input');
weightInput.type = 'number';
weightInput.min = '0';
weightInput.name = 'yoone_snow_media_weights[' + String(id) + ']';
weightInput.value = '1';
weightInput.style.width = '120px';
weightInput.style.marginTop = '6px';
var input = document.createElement('input'); var input = document.createElement('input');
input.type = 'hidden'; input.type = 'hidden';
input.name = 'yoone_snow_media_items[]'; input.name = 'yoone_snow_media_items[]';
@ -380,15 +279,15 @@
removeBtn.textContent = 'Remove'; removeBtn.textContent = 'Remove';
removeBtn.style.marginTop = '6px'; removeBtn.style.marginTop = '6px';
wrapper.appendChild(img); wrapper.appendChild(img);
wrapper.appendChild(weightInput);
wrapper.appendChild(input); wrapper.appendChild(input);
wrapper.appendChild(removeBtn); wrapper.appendChild(removeBtn);
listContainer.appendChild(wrapper); listContainer.appendChild(wrapper);
// 同步添加媒体权重输入 默认权重为 1
addMediaWeightInput(id);
}); });
}); });
frame.open(); frame.open();
}); });
}
// Emoji 别名映射 用於文本搜索到 emoji 字符 // Emoji 别名映射 用於文本搜索到 emoji 字符
var emojiAliasMap = { var emojiAliasMap = {
@ -474,10 +373,10 @@
// 將 emoji 字符添加到列表 並創建隱藏輸入 // 將 emoji 字符添加到列表 並創建隱藏輸入
function addEmojiByChar(ch){ function addEmojiByChar(ch){
if (!emojiListContainer) { return; } if (!shapeListContainer) { return; }
var key = String(ch); var key = String(ch);
if (key.trim() === '') { return; } if (key.trim() === '') { return; }
var exist = emojiListContainer.querySelector('.yoone-snow-emoji-item[data-emoji-char="' + key + '"]'); var exist = shapeListContainer.querySelector('.yoone-snow-emoji-item[data-emoji-char="' + key + '"]');
if (exist) { return; } if (exist) { return; }
var wrapper = document.createElement('div'); var wrapper = document.createElement('div');
wrapper.className = 'yoone-snow-emoji-item'; wrapper.className = 'yoone-snow-emoji-item';
@ -491,6 +390,21 @@
preview.textContent = key; preview.textContent = key;
preview.style.fontSize = '28px'; preview.style.fontSize = '28px';
preview.style.lineHeight = '32px'; preview.style.lineHeight = '32px';
preview.style.backgroundColor = '#e6e6e6';
preview.style.border = '1px solid #ddd';
preview.style.borderRadius = '4px';
preview.style.width = '32px';
preview.style.height = '32px';
preview.style.display = 'flex';
preview.style.alignItems = 'center';
preview.style.justifyContent = 'center';
var weightInput = document.createElement('input');
weightInput.type = 'number';
weightInput.min = '0';
weightInput.name = 'yoone_snow_emoji_weights[' + key + ']';
weightInput.value = '1';
weightInput.style.width = '120px';
weightInput.style.marginTop = '6px';
var input = document.createElement('input'); var input = document.createElement('input');
input.type = 'hidden'; input.type = 'hidden';
input.name = 'yoone_snow_emoji_items[]'; input.name = 'yoone_snow_emoji_items[]';
@ -501,11 +415,10 @@
removeBtn.textContent = 'Cancel'; removeBtn.textContent = 'Cancel';
removeBtn.style.marginTop = '6px'; removeBtn.style.marginTop = '6px';
wrapper.appendChild(preview); wrapper.appendChild(preview);
wrapper.appendChild(weightInput);
wrapper.appendChild(input); wrapper.appendChild(input);
wrapper.appendChild(removeBtn); wrapper.appendChild(removeBtn);
emojiListContainer.appendChild(wrapper); shapeListContainer.appendChild(wrapper);
// 同步添加 emoji 權重輸入 默認權重為 1
addEmojiWeightInput(key);
} }
// 綁定 emoji 添加按鈕 點擊後優先按別名匹配 否則直接添加字符 // 綁定 emoji 添加按鈕 點擊後優先按別名匹配 否則直接添加字符
@ -541,14 +454,6 @@
}); });
} }
// 初始化時為現有 emoji 列表補齊權重輸入
if (emojiListContainer){
var existing = emojiListContainer.querySelectorAll('.yoone-snow-emoji-item');
existing.forEach(function(node){
var ch = node.getAttribute('data-emoji-char');
if (ch) { addEmojiWeightInput(ch); }
});
}
if (shapeListContainer){ if (shapeListContainer){
shapeListContainer.addEventListener('click', function(event){ shapeListContainer.addEventListener('click', function(event){
var target = event.target; var target = event.target;
@ -557,7 +462,6 @@
if (item){ if (item){
var key = item.getAttribute('data-shape-key'); var key = item.getAttribute('data-shape-key');
item.remove(); item.remove();
ensureShapeWeightInputs();
} }
} }
}); });
@ -582,6 +486,19 @@
} }
}); });
} }
// 切換類型面板 顯示對應控件 其他隱藏
function updateTypePane(){
var t = typeSelect ? String(typeSelect.value || '') : '';
if (!paneDefault || !paneEmoji || !paneMedia) { return; }
paneDefault.style.display = (t === 'default') ? 'flex' : 'none';
paneEmoji.style.display = (t === 'emoji') ? 'flex' : 'none';
paneMedia.style.display = (t === 'media') ? 'flex' : 'none';
}
if (typeSelect){
typeSelect.addEventListener('change', updateTypePane);
updateTypePane();
}
} }
// 条件判断 如果文档尚未加载则等待 DOMContentLoaded 事件 // 条件判断 如果文档尚未加载则等待 DOMContentLoaded 事件

View File

@ -234,7 +234,27 @@ function yoone_snow_admin_enqueue($hook) {
// 注册并加载后台交互脚本 该脚本复用 shapes 渲染器进行预览 // 注册并加载后台交互脚本 该脚本复用 shapes 渲染器进行预览
$admin_script_handle = 'yoone-snow-admin-media'; $admin_script_handle = 'yoone-snow-admin-media';
wp_register_script($admin_script_handle, plugins_url('js/admin-media.js', __FILE__), array($shape_index_handle, $shape_utils_handle), '1.1.0', true); // 设置 admin 脚本依赖所有形状脚本 以保证渲染器已就绪
wp_register_script(
$admin_script_handle,
plugins_url('js/admin-media.js', __FILE__),
array(
$shape_index_handle,
$shape_utils_handle,
$shape_dot_handle,
$shape_flake_handle,
$shape_yuanbao_handle,
$shape_coin_handle,
$shape_santa_handle,
$shape_cane_handle,
$shape_sock_handle,
$shape_tree_handle,
$shape_reindeer_handle,
$shape_berry_handle
),
'1.1.0',
true
);
// 传递资源映射用于后台形状预览 保持与前端一致 // 传递资源映射用于后台形状预览 保持与前端一致
wp_localize_script($admin_script_handle, 'YooneSnowAdmin', array( wp_localize_script($admin_script_handle, 'YooneSnowAdmin', array(
'assetsMap' => array( 'assetsMap' => array(
@ -298,6 +318,7 @@ function yoone_snow_register_settings() {
'yoone_snow_mixed_items', 'yoone_snow_mixed_items',
'Shapes', 'Shapes',
function() { function() {
// 读取默认形状列表 用于初始渲染卡片
$current_list = get_option('yoone_snow_mixed_items', array('dot','flake')); $current_list = get_option('yoone_snow_mixed_items', array('dot','flake'));
if (is_string($current_list)) { if (is_string($current_list)) {
$current_list = array_filter(array_map('trim', explode(',', $current_list))); $current_list = array_filter(array_map('trim', explode(',', $current_list)));
@ -315,18 +336,72 @@ function yoone_snow_register_settings() {
'reindeer' => 'Reindeer', 'reindeer' => 'Reindeer',
'christmas_berry' => 'Christmas Berry', 'christmas_berry' => 'Christmas Berry',
); );
// 读取已选择的媒体与 emoji 列表 用于统一卡片渲染
$current_media = get_option('yoone_snow_media_items', array());
if (!is_array($current_media)) { $current_media = array(); }
$current_emojis = get_option('yoone_snow_emoji_items', array());
if (!is_array($current_emojis)) { $current_emojis = array(); }
// 读取当前权重值 用于在卡片中预填
$shape_weights_current = get_option('yoone_snow_shape_weights', array());
if (!is_array($shape_weights_current)) { $shape_weights_current = array(); }
$media_weights_current = get_option('yoone_snow_media_weights', array());
if (!is_array($media_weights_current)) { $media_weights_current = array(); }
$emoji_weights_current = get_option('yoone_snow_emoji_weights', array());
if (!is_array($emoji_weights_current)) { $emoji_weights_current = array(); }
echo '<div id="yooneSnowShapeList" style="display:flex;flex-wrap:wrap;gap:12px;">'; echo '<div id="yooneSnowShapeList" style="display:flex;flex-wrap:wrap;gap:12px;">';
// 渲染默认形状卡片
foreach ($current_list as $key) { foreach ($current_list as $key) {
if (!isset($options[$key])) { continue; } if (!isset($options[$key])) { continue; }
echo '<div class="yoone-snow-shape-item" data-shape-key="' . esc_attr($key) . '" style="border:1px solid #ddd;padding:8px;display:flex;flex-direction:column;align-items:center;min-width:96px;">'; echo '<div class="yoone-snow-shape-item" data-shape-key="' . esc_attr($key) . '" style="border:1px solid #ddd;padding:8px;display:flex;flex-direction:column;align-items:center;min-width:96px;">';
echo '<div class="yoone-snow-shape-preview" style="width:32px;height:32px;margin-bottom:6px;"></div>'; echo '<div class="yoone-snow-shape-preview" style="width:32px;height:32px;margin-bottom:6px;background-color:#e6e6e6;border:1px solid #ddd;border-radius:4px;"></div>';
echo '<span>' . esc_html($options[$key]) . '</span>'; echo '<span>' . esc_html($options[$key]) . '</span>';
$shapeWeightVal = isset($shape_weights_current[$key]) ? intval($shape_weights_current[$key]) : 1;
echo '<input type="number" min="0" name="yoone_snow_shape_weights[' . esc_attr($key) . ']" value="' . esc_attr($shapeWeightVal) . '" style="width:120px;margin-top:6px;" />';
echo '<input type="hidden" name="yoone_snow_mixed_items[]" value="' . esc_attr($key) . '" />'; echo '<input type="hidden" name="yoone_snow_mixed_items[]" value="' . esc_attr($key) . '" />';
echo '<button type="button" class="button yoone-snow-cancel-shape" style="margin-top:6px;">Cancel</button>'; echo '<button type="button" class="button yoone-snow-cancel-shape" style="margin-top:6px;">Cancel</button>';
echo '</div>'; echo '</div>';
} }
// 渲染 emoji 卡片
foreach ($current_emojis as $emoji_char) {
$label = trim((string)$emoji_char);
if ($label === '') { continue; }
echo '<div class="yoone-snow-emoji-item" data-emoji-char="' . esc_attr($label) . '" style="border:1px solid #ddd;padding:8px;display:flex;flex-direction:column;align-items:center;min-width:96px;">';
echo '<div style="font-size:28px;line-height:32px;margin-bottom:6px;width:32px;height:32px;display:flex;align-items:center;justify-content:center;background-color:#e6e6e6;border:1px solid #ddd;border-radius:4px;">' . esc_html($label) . '</div>';
$emojiWeightVal = isset($emoji_weights_current[$label]) ? intval($emoji_weights_current[$label]) : 1;
echo '<input type="number" min="0" name="yoone_snow_emoji_weights[' . esc_attr($label) . ']" value="' . esc_attr($emojiWeightVal) . '" style="width:120px;margin-top:6px;" />';
echo '<input type="hidden" name="yoone_snow_emoji_items[]" value="' . esc_attr($label) . '" />';
echo '<button type="button" class="button yoone-snow-remove-emoji" style="margin-top:6px;">Cancel</button>';
echo '</div>';
}
// 渲染媒体卡片
foreach ($current_media as $attachment_id) {
$url = wp_get_attachment_thumb_url($attachment_id);
if (!$url) { $url = wp_get_attachment_url($attachment_id); }
$safe_id = intval($attachment_id);
$safe_url = esc_url($url);
echo '<div class="yoone-snow-media-item" data-attachment-id="' . $safe_id . '" style="border:1px solid #ddd;padding:8px;display:flex;flex-direction:column;align-items:center;min-width:96px;">';
echo '<img src="' . $safe_url . '" alt="media" style="width:72px;height:72px;object-fit:contain;margin-bottom:6px;background-color:#e6e6e6;border:1px solid #ddd;border-radius:4px;" />';
$mediaWeightVal = isset($media_weights_current[$safe_id]) ? intval($media_weights_current[$safe_id]) : 1;
echo '<input type="number" min="0" name="yoone_snow_media_weights[' . $safe_id . ']" value="' . esc_attr($mediaWeightVal) . '" style="width:120px;margin-top:6px;" />';
echo '<input type="hidden" name="yoone_snow_media_items[]" value="' . $safe_id . '" />';
echo '<button type="button" class="button yoone-snow-remove-media" style="margin-top:6px;">Remove</button>';
echo '</div>';
}
echo '</div>'; echo '</div>';
echo '<div style="margin-top:8px;display:flex;gap:8px;align-items:center;">';
// 统一添加控件 包含类型下拉与三类子控件
echo '<div id="yooneSnowAddUnified" style="margin-top:8px;display:flex;flex-direction:column;gap:8px;">';
echo '<div style="display:flex;gap:8px;align-items:center;">';
echo '<label>Type <select id="yooneSnowAddTypeSelect" style="min-width:180px;">';
echo '<option value="">Select type</option>';
echo '<option value="default">Default</option>';
echo '<option value="emoji">Emoji</option>';
echo '<option value="media">Media</option>';
echo '</select></label>';
echo '</div>';
// 默认形状子面板
echo '<div id="yooneSnowAddDefaultPane" style="display:none;gap:8px;align-items:center;">';
echo '<select id="yooneSnowAddShapeSelect" style="min-width:240px;"><option value="">Select shape</option>'; echo '<select id="yooneSnowAddShapeSelect" style="min-width:240px;"><option value="">Select shape</option>';
foreach ($options as $key => $label) { foreach ($options as $key => $label) {
echo '<option value="' . esc_attr($key) . '">' . esc_html($label) . '</option>'; echo '<option value="' . esc_attr($key) . '">' . esc_html($label) . '</option>';
@ -334,42 +409,27 @@ function yoone_snow_register_settings() {
echo '</select>'; echo '</select>';
echo '<button type="button" class="button" id="yooneSnowAddShapeBtn">Add Shape</button>'; echo '<button type="button" class="button" id="yooneSnowAddShapeBtn">Add Shape</button>';
echo '</div>'; echo '</div>';
echo '<p class="description">Choose shapes to render</p>'; // Emoji 子面板
echo '<div id="yooneSnowAddEmojiPane" style="display:none;gap:8px;align-items:center;">';
echo '<select id="yooneSnowEmojiSelect" style="min-width:240px;"><option value="">Select emoji</option></select>';
echo '<input type="text" id="yooneSnowAddEmojiInput" placeholder="Type emoji or alias" style="width:240px;" />';
echo '<button type="button" class="button" id="yooneSnowAddEmoji">Add Emoji</button>';
echo '<div id="yooneSnowEmojiSuggest" style="margin-top:6px;display:flex;flex-wrap:wrap;gap:6px;"></div>';
echo '</div>';
// 媒体子面板
echo '<div id="yooneSnowAddMediaPane" style="display:none;gap:8px;align-items:center;">';
echo '<button type="button" class="button button-primary" id="yooneSnowAddMediaUnified">Add Images</button>';
echo '</div>';
echo '<p class="description">Add shapes by type all in one list</p>';
echo '</div>';
// 权重输入已集成到各类型卡片之中 无需单独权重区域
}, },
'yoone_snow', 'yoone_snow',
'yoone_snow_section' 'yoone_snow_section'
); );
// 添加 emoji 形狀選擇區域 放置在 Media Shapes 之前 // 移除單獨 emoji 字段 渲染與交互已整合到 Shapes 字段
add_settings_field(
'yoone_snow_emoji_items',
'Emoji Shapes',
function() {
// 渲染當前 emoji 列表與添加搜索框
$current_emojis = get_option('yoone_snow_emoji_items', array());
if (!is_array($current_emojis)) { $current_emojis = array(); }
echo '<div id="yooneSnowEmojiList" style="display:flex;flex-wrap:wrap;gap:12px;align-items:flex-start;">';
foreach ($current_emojis as $emoji_char) {
$label = trim((string)$emoji_char);
if ($label === '') { continue; }
echo '<div class="yoone-snow-emoji-item" data-emoji-char="' . esc_attr($label) . '" style="border:1px solid #ddd;padding:8px;display:flex;flex-direction:column;align-items:center;">';
echo '<div style="font-size:28px;line-height:32px;">' . esc_html($label) . '</div>';
echo '<input type="hidden" name="yoone_snow_emoji_items[]" value="' . esc_attr($label) . '" />';
echo '<button type="button" class="button yoone-snow-remove-emoji" style="margin-top:6px;">Cancel</button>';
echo '</div>';
}
echo '</div>';
echo '<div style="margin-top:8px;display:flex;gap:8px;align-items:center;">';
echo '<select id="yooneSnowEmojiSelect" style="min-width:240px;"><option value="">Select emoji</option></select>';
echo '<input type="text" id="yooneSnowAddEmojiInput" placeholder="Type emoji or alias" style="width:240px;" />';
echo '<button type="button" class="button" id="yooneSnowAddEmoji">Add Emoji</button>';
echo '</div>';
echo '<div id="yooneSnowEmojiSuggest" style="margin-top:6px;display:flex;flex-wrap:wrap;gap:6px;"></div>';
echo '<p class="description">Search by alias or paste emoji character then add</p>';
},
'yoone_snow',
'yoone_snow_section'
);
register_setting('yoone_snow_options', 'yoone_snow_shape_weights', array( register_setting('yoone_snow_options', 'yoone_snow_shape_weights', array(
'type' => 'array', 'type' => 'array',
@ -431,33 +491,7 @@ function yoone_snow_register_settings() {
'default' => array(), 'default' => array(),
)); ));
// 添加媒体形状选择字段 支持从媒体库选择图片或 SVG // 移除單獨 Media 字段 渲染與交互已整合到 Shapes 字段
add_settings_field(
'yoone_snow_media_items',
'Media Shapes',
function() {
// 读取当前已选择的附件 ID 列表 并渲染缩略图列表与添加按钮
$current_media = get_option('yoone_snow_media_items', array());
if (!is_array($current_media)) { $current_media = array(); }
echo '<div id="yooneSnowMediaList" style="display:flex;flex-wrap:wrap;gap:12px;">';
foreach ($current_media as $attachment_id) {
$url = wp_get_attachment_thumb_url($attachment_id);
if (!$url) { $url = wp_get_attachment_url($attachment_id); }
$safe_id = intval($attachment_id);
$safe_url = esc_url($url);
echo '<div class="yoone-snow-media-item" data-attachment-id="' . $safe_id . '" style="border:1px solid #ddd;padding:8px;display:flex;flex-direction:column;align-items:center;">';
echo '<img src="' . $safe_url . '" alt="media" style="width:72px;height:72px;object-fit:contain;" />';
echo '<input type="hidden" name="yoone_snow_media_items[]" value="' . $safe_id . '" />';
echo '<button type="button" class="button yoone-snow-remove-media" style="margin-top:6px;">Remove</button>';
echo '</div>';
}
echo '</div>';
echo '<p><button type="button" class="button button-primary" id="yooneSnowAddMedia">Add Images</button></p>';
echo '<p class="description">Choose images or SVG from media library to render</p>';
},
'yoone_snow',
'yoone_snow_section'
);
// 注册媒体权重设置项 将附件 ID 映射到权重数值 默认 1 // 注册媒体权重设置项 将附件 ID 映射到权重数值 默认 1
register_setting('yoone_snow_options', 'yoone_snow_media_weights', array( register_setting('yoone_snow_options', 'yoone_snow_media_weights', array(
@ -479,82 +513,7 @@ function yoone_snow_register_settings() {
'default' => array(), 'default' => array(),
)); ));
// 在媒体形状字段之后渲染权重字段 包含形状权重与媒体权重 // 移除單獨 Weights 字段 權重輸入已置於 Shapes 字段下方
add_settings_field(
'yoone_snow_weights_combined',
'Weights',
function() {
// 渲染形状权重 输入仅显示已勾选的形状 默认 1
$selected_shapes = get_option('yoone_snow_mixed_items', array('dot','flake'));
if (is_string($selected_shapes)) {
$selected_shapes = array_filter(array_map('trim', explode(',', $selected_shapes)));
}
if (!is_array($selected_shapes)) { $selected_shapes = array('dot','flake'); }
$shape_labels = array(
'dot' => 'Dot',
'flake' => 'Snowflake',
'yuanbao' => 'Yuanbao',
'coin' => 'Coin',
'santa_hat' => 'Santa Hat',
'candy_cane' => 'Candy Cane',
'christmas_sock' => 'Christmas Sock',
'christmas_tree' => 'Christmas Tree',
'reindeer' => 'Reindeer',
'christmas_berry' => 'Christmas Berry',
'emoji' => 'Emoji',
);
$shape_weights_current = get_option('yoone_snow_shape_weights', array());
if (!is_array($shape_weights_current)) { $shape_weights_current = array(); }
echo '<div id="yooneSnowWeightsContainer" style="margin-top:8px;">';
echo '<div id="yooneSnowShapeWeights" style="display:flex;flex-wrap:wrap;gap:12px;margin-bottom:12px;">';
foreach ($selected_shapes as $sk) {
if (!isset($shape_labels[$sk])) { continue; }
$val = isset($shape_weights_current[$sk]) ? intval($shape_weights_current[$sk]) : 1;
echo '<label data-shape-key="' . esc_attr($sk) . '" style="display:flex;flex-direction:column;min-width:160px;">';
echo '<span>' . esc_html($shape_labels[$sk]) . '</span>';
echo '<input type="number" min="0" name="yoone_snow_shape_weights[' . esc_attr($sk) . ']" value="' . esc_attr($val) . '" style="width:120px;" />';
echo '</label>';
}
echo '</div>';
// 渲染媒体权重 按当前媒体列表生成 输入名称为附件 ID 映射 默认 1
$current_media = get_option('yoone_snow_media_items', array());
if (!is_array($current_media)) { $current_media = array(); }
$media_weights_current = get_option('yoone_snow_media_weights', array());
if (!is_array($media_weights_current)) { $media_weights_current = array(); }
echo '<div id="yooneSnowMediaWeights" style="display:flex;flex-wrap:wrap;gap:12px;">';
foreach ($current_media as $attachment_id) {
$aid = intval($attachment_id);
$label = 'Media ' . $aid;
$val = isset($media_weights_current[$aid]) ? intval($media_weights_current[$aid]) : 1;
echo '<label data-attachment-id="' . esc_attr($aid) . '" style="display:flex;flex-direction:column;min-width:160px;">';
echo '<span>' . esc_html($label) . '</span>';
echo '<input type="number" min="0" name="yoone_snow_media_weights[' . esc_attr($aid) . ']" value="' . esc_attr($val) . '" style="width:120px;" />';
echo '</label>';
}
echo '</div>';
// 渲染 emoji 权重 输入为字符键 默认 1
$emoji_items_current = get_option('yoone_snow_emoji_items', array());
if (!is_array($emoji_items_current)) { $emoji_items_current = array(); }
$emoji_weights_current = get_option('yoone_snow_emoji_weights', array());
if (!is_array($emoji_weights_current)) { $emoji_weights_current = array(); }
echo '<div id="yooneSnowEmojiWeights" style="display:flex;flex-wrap:wrap;gap:12px;margin-top:12px;">';
foreach ($emoji_items_current as $emojiChar) {
$label = trim((string)$emojiChar);
if ($label === '') { continue; }
$val = isset($emoji_weights_current[$label]) ? intval($emoji_weights_current[$label]) : 1;
echo '<label data-emoji-char="' . esc_attr($label) . '" style="display:flex;flex-direction:column;min-width:160px;">';
echo '<span>' . esc_html($label) . '</span>';
echo '<input type="number" min="0" name="yoone_snow_emoji_weights[' . esc_attr($label) . ']" value="' . esc_attr($val) . '" style="width:120px;" />';
echo '</label>';
}
echo '</div>';
echo '<p class="description">Higher value increases selection probability 0 disables default is 1</p>';
echo '</div>';
// 绑定交互脚本 通过已有 admin 脚本实现动态刷新
},
'yoone_snow',
'yoone_snow_section'
);
// 注册 emoji 列表设置项 保存为字符串数组 // 注册 emoji 列表设置项 保存为字符串数组
register_setting('yoone_snow_options', 'yoone_snow_emoji_items', array( register_setting('yoone_snow_options', 'yoone_snow_emoji_items', array(