diff --git a/assets/css/admin.css b/assets/css/admin.css
new file mode 100644
index 0000000..d8ba8f0
--- /dev/null
+++ b/assets/css/admin.css
@@ -0,0 +1,1003 @@
+/**
+ * Admin styles for Collection Manager
+ */
+
+/* General Layout */
+.bis-wrap {
+ margin: 20px 0;
+}
+
+.bis-container {
+ display: flex;
+ gap: 24px;
+ flex-wrap: wrap;
+ margin-top: 24px;
+}
+
+.bis-sidebar {
+ flex: 0 0 320px;
+}
+
+.bis-content {
+ flex: 1;
+ min-width: 500px;
+}
+
+@media (max-width: 1100px) {
+ .bis-container {
+ flex-direction: column;
+ }
+
+ .bis-sidebar,
+ .bis-content {
+ flex: 1 1 100%;
+ width: 100%;
+ min-width: 0;
+ }
+}
+
+.bis-card {
+ background: #fff;
+ border: 1px solid #ccd0d4;
+ border-radius: 4px;
+ padding: 24px;
+ margin-bottom: 24px;
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
+}
+
+.bis-version-tag {
+ font-size: 13px;
+ padding-left: 10px;
+ font-weight: normal;
+ opacity: 0.7;
+}
+
+/* Form Elements */
+.bis-form-section {
+ margin-bottom: 30px;
+ padding-bottom: 24px;
+ border-bottom: 1px solid #f0f0f1;
+}
+
+.bis-section-title {
+ margin-top: 0;
+ margin-bottom: 16px;
+ padding-bottom: 12px;
+ border-bottom: 1px solid #f0f0f1;
+ font-size: 16px;
+ color: #1d2327;
+}
+
+.bis-section-description {
+ margin-top: -8px;
+ margin-bottom: 16px;
+ color: #646970;
+}
+
+.bis-form-row {
+ margin-bottom: 18px;
+}
+
+.bis-form-row label {
+ display: block;
+ margin-bottom: 6px;
+ font-weight: 600;
+ color: #1d2327;
+}
+
+.bis-form-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
+ gap: 16px;
+ margin-bottom: 18px;
+}
+
+.bis-form-group {
+ margin-bottom: 4px;
+}
+
+.bis-required {
+ color: #d63638;
+}
+
+.bis-form-actions {
+ margin-top: 24px;
+ padding-top: 20px;
+ border-top: 1px solid #f0f0f1;
+ display: flex;
+ justify-content: space-between;
+}
+
+.bis-checkbox-label {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ cursor: pointer;
+}
+
+.bis-select {
+ width: 100%;
+ max-width: 100%;
+}
+
+/* Icon Select */
+.bis-icon-select-wrapper {
+ position: relative;
+ display: flex;
+ align-items: center;
+}
+
+.bis-selected-icon {
+ margin-left: 10px;
+ font-size: 18px;
+ width: 24px;
+ height: 24px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: #f0f6fc;
+ border-radius: 50%;
+ color: #2271b1;
+}
+
+/* Screenshot Upload */
+.bis-screenshot-field {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+.bis-screenshot-preview {
+ margin-top: 10px;
+}
+
+.bis-screenshot-preview img {
+ max-width: 100%;
+ height: auto;
+ max-height: 200px;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+}
+
+/* Collections List */
+.bis-collections-list {
+ margin: 15px 0;
+}
+
+.bis-collections-list ul {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+
+.bis-collection-item {
+ display: flex;
+ align-items: center;
+ padding: 12px;
+ margin-bottom: 10px;
+ border: 1px solid #e5e5e5;
+ border-radius: 4px;
+ background: #f9f9f9;
+ transition: all 0.2s ease;
+}
+
+.bis-collection-item:hover {
+ background: #f0f0f1;
+ border-color: #c3c4c7;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
+}
+
+.bis-collection-icon {
+ flex: 0 0 40px;
+ height: 40px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: #e9f0f5;
+ border-radius: 20px;
+ margin-right: 12px;
+}
+
+.bis-collection-icon .dashicons {
+ font-size: 20px;
+ width: 20px;
+ height: 20px;
+ color: #2271b1;
+}
+
+.bis-collection-details {
+ flex: 1;
+}
+
+.bis-collection-details h4 {
+ margin: 0 0 6px 0;
+ font-size: 14px;
+ color: #1d2327;
+}
+
+.bis-collection-meta {
+ font-size: 12px;
+ color: #646970;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 10px;
+ align-items: center;
+}
+
+.bis-item-count {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+}
+
+.bis-item-count .dashicons {
+ font-size: 14px;
+ width: 14px;
+ height: 14px;
+}
+
+.bis-meta-tag {
+ display: inline-block;
+ padding: 2px 6px;
+ font-size: 11px;
+ border-radius: 10px;
+ background: #f0f0f1;
+}
+
+.bis-category-tag {
+ background: #e9f0f5;
+ color: #2271b1;
+}
+
+.bis-level-tag {
+ background: #f0f6e5;
+ color: #3c6e21;
+}
+
+.bis-collection-actions {
+ display: flex;
+ gap: 6px;
+}
+
+.bis-collection-actions .button {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+}
+
+.bis-collection-actions .dashicons {
+ font-size: 14px;
+ width: 14px;
+ height: 14px;
+}
+
+.bis-empty-message {
+ padding: 16px;
+ background: #f0f0f1;
+ border-radius: 4px;
+ text-align: center;
+ color: #646970;
+}
+
+.bis-actions {
+ display: flex;
+ justify-content: space-between;
+ margin-top: 20px;
+}
+
+.bis-actions .button {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+}
+
+.bis-actions .dashicons {
+ font-size: 16px;
+ width: 16px;
+ height: 16px;
+}
+
+/* API Info */
+.bis-api-info {
+ margin: 16px 0;
+ font-size: 13px;
+}
+
+.bis-api-endpoint {
+ margin-bottom: 16px;
+}
+
+.bis-api-endpoint label,
+.bis-collection-endpoints label {
+ display: block;
+ font-weight: 600;
+ margin-bottom: 6px;
+}
+
+.bis-endpoint-url {
+ display: flex;
+ align-items: center;
+ margin-bottom: 10px;
+}
+
+.bis-endpoint-url code {
+ flex: 1;
+ padding: 6px 8px;
+ background: #f0f0f1;
+ border-radius: 3px;
+ word-break: break-all;
+ font-size: 12px;
+}
+
+.bis-copy-url {
+ margin-left: 5px;
+}
+
+.bis-client-instructions {
+ margin-top: 20px;
+ padding-top: 15px;
+ border-top: 1px solid #f0f0f1;
+}
+
+.bis-client-instructions h4 {
+ margin-top: 0;
+}
+
+.bis-client-instructions ol {
+ margin-left: 20px;
+}
+
+/* Export Options */
+.bis-export-options {
+ margin: 15px 0;
+}
+
+.bis-export-label {
+ display: block;
+ margin-bottom: 6px;
+ font-weight: 600;
+}
+
+.bis-export-collections {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ margin-top: 12px;
+}
+
+.bis-export-collections .dashicons {
+ font-size: 16px;
+ width: 16px;
+ height: 16px;
+}
+
+/* Tabs */
+.bis-tabs {
+ margin-top: 20px;
+}
+
+.bis-tabs-nav {
+ display: flex;
+ border-bottom: 1px solid #c3c4c7;
+ margin-bottom: 20px;
+ gap: 4px;
+}
+
+.bis-tab {
+ padding: 10px 16px;
+ background: none;
+ border: none;
+ cursor: pointer;
+ border-bottom: 2px solid transparent;
+ margin-bottom: -1px;
+ font-weight: 500;
+ display: flex;
+ align-items: center;
+ gap: 6px;
+}
+
+.bis-tab .dashicons {
+ font-size: 16px;
+ width: 16px;
+ height: 16px;
+}
+
+.bis-tab.active {
+ border-color: #2271b1;
+ color: #2271b1;
+ font-weight: 600;
+}
+
+.bis-tab:hover:not(.active) {
+ color: #135e96;
+ border-color: #dcdcde;
+}
+
+.bis-tab-content {
+ display: none;
+}
+
+.bis-tab-content.active {
+ display: block;
+}
+
+/* Source Sections */
+.bis-source-sections {
+ display: flex;
+ flex-direction: column;
+ gap: 24px;
+}
+
+.bis-source-section {
+ border: 1px solid #e5e5e5;
+ border-radius: 6px;
+ padding: 16px;
+ background: #fbfbfb;
+}
+
+.bis-source-section h4 {
+ margin-top: 0;
+ padding-bottom: 10px;
+ border-bottom: 1px solid #eee;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 14px;
+}
+
+.bis-source-section h4 .dashicons {
+ color: #2271b1;
+}
+
+.bis-source-description {
+ margin-top: -6px;
+ margin-bottom: 16px;
+ color: #646970;
+ font-size: 13px;
+}
+
+/* Items */
+.bis-items-container {
+ min-height: 50px;
+ margin: 15px 0;
+ transition: all 0.2s ease;
+}
+
+.bis-item {
+ background: #fff;
+ border: 1px solid #e5e5e5;
+ border-radius: 4px;
+ margin-bottom: 10px;
+ transition: all 0.2s ease;
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03);
+}
+
+.bis-item:hover {
+ border-color: #c3c4c7;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
+}
+
+.bis-item-header {
+ padding: 12px 15px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ cursor: move;
+ background: #f9f9f9;
+ border-top-left-radius: 4px;
+ border-top-right-radius: 4px;
+ border-bottom: 1px solid #f0f0f1;
+}
+
+.bis-item-info {
+ flex: 1;
+}
+
+.bis-item-title {
+ margin: 0;
+ font-size: 14px;
+ color: #1d2327;
+}
+
+.bis-item-slug {
+ color: #646970;
+ font-size: 12px;
+ margin-top: 2px;
+}
+
+.bis-item-actions {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+.bis-item-description {
+ padding: 12px 15px;
+ color: #646970;
+ font-size: 13px;
+}
+
+.bis-item-placeholder {
+ border: 2px dashed #c3c4c7;
+ height: 40px;
+ border-radius: 4px;
+ margin-bottom: 10px;
+ background: #f0f0f1;
+}
+
+.bis-required-toggle {
+ display: flex;
+ align-items: center;
+ font-size: 12px;
+ color: #555;
+}
+
+.bis-required-toggle input {
+ margin-right: 5px;
+}
+
+.bis-add-item {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+}
+
+.bis-add-item .dashicons {
+ font-size: 14px;
+ width: 14px;
+ height: 14px;
+}
+
+/* Modal */
+.bis-modal {
+ display: none;
+ position: fixed;
+ z-index: 100000;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ overflow: auto;
+ background-color: rgba(0, 0, 0, 0.4);
+}
+
+.bis-modal-content {
+ position: relative;
+ background-color: #fff;
+ margin: 10% auto;
+ padding: 0;
+ border-radius: 6px;
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
+ width: 80%;
+ max-width: 600px;
+ animation: fadeIn 0.3s;
+}
+
+@keyframes fadeIn {
+ from { opacity: 0; }
+ to { opacity: 1; }
+}
+
+.bis-modal-header {
+ padding: 16px 20px;
+ border-bottom: 1px solid #eee;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.bis-modal-header h3 {
+ margin: 0;
+ font-size: 16px;
+}
+
+.bis-modal-close {
+ color: #aaa;
+ font-size: 24px;
+ font-weight: bold;
+ cursor: pointer;
+ background: none;
+ border: none;
+ padding: 0;
+ line-height: 20px;
+}
+
+.bis-modal-close:hover {
+ color: #555;
+}
+
+.bis-modal-body {
+ padding: 20px;
+}
+
+.bis-modal-footer {
+ padding: 16px 20px;
+ border-top: 1px solid #eee;
+ display: flex;
+ justify-content: flex-end;
+ gap: 10px;
+}
+
+body.bis-modal-open {
+ overflow: hidden;
+}
+
+/* Welcome Card */
+.bis-welcome-intro {
+ font-size: 15px;
+ color: #646970;
+ margin-bottom: 24px;
+}
+
+.bis-welcome-content {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(270px, 1fr));
+ gap: 24px;
+ margin: 28px 0;
+}
+
+.bis-welcome-step {
+ display: flex;
+ gap: 16px;
+ align-items: flex-start;
+}
+
+.bis-step-icon {
+ width: 48px;
+ height: 48px;
+ background: #e9f0f5;
+ border-radius: 24px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.bis-step-icon .dashicons {
+ font-size: 24px;
+ width: 24px;
+ height: 24px;
+ color: #2271b1;
+}
+
+.bis-step-content {
+ flex: 1;
+}
+
+.bis-step-content h3 {
+ margin: 0 0 10px 0;
+ font-size: 16px;
+ color: #1d2327;
+}
+
+.bis-step-content p {
+ margin: 0;
+ color: #646970;
+ font-size: 14px;
+}
+
+.bis-welcome-actions {
+ text-align: center;
+ margin-top: 32px;
+}
+
+.bis-welcome-actions .button {
+ padding: 8px 20px;
+ height: auto;
+ font-size: 15px;
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.bis-welcome-actions .dashicons {
+ font-size: 18px;
+ width: 18px;
+ height: 18px;
+}
+
+/* Utility Classes */
+.bis-form-actions .button {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+}
+
+.bis-form-actions .dashicons {
+ font-size: 16px;
+ width: 16px;
+ height: 16px;
+}
+
+.description {
+ font-size: 12px;
+ color: #646970;
+ margin-top: 4px;
+ margin-bottom: 0;
+}
+
+/* Add some animations for better feedback */
+@keyframes highlight {
+ 0% { background-color: #f0f6fc; }
+ 100% { background-color: transparent; }
+}
+
+.bis-highlight {
+ animation: highlight 1.5s ease-out;
+}
+
+.bis-save-success {
+ color: #00a32a;
+}
+
+.bis-save-error {
+ color: #d63638;
+}
+
+/* Dark mode support */
+@media (prefers-color-scheme: dark) {
+ body.is-dark-theme .bis-card,
+ body.is-dark-theme .bis-item,
+ body.is-dark-theme .bis-modal-content,
+ body.is-dark-theme .bis-source-section {
+ background-color: #1d2327;
+ border-color: #2c3338;
+ }
+
+ body.is-dark-theme .bis-item-header {
+ background-color: #2c3338;
+ border-color: #3c434a;
+ }
+
+ body.is-dark-theme .bis-section-title,
+ body.is-dark-theme .bis-form-section,
+ body.is-dark-theme .bis-form-actions,
+ body.is-dark-theme .bis-modal-header,
+ body.is-dark-theme .bis-modal-footer {
+ border-color: #3c434a;
+ }
+
+ body.is-dark-theme .bis-collection-item {
+ background-color: #2c3338;
+ border-color: #3c434a;
+ }
+
+ body.is-dark-theme .bis-collection-item:hover {
+ background-color: #32373c;
+ border-color: #50575e;
+ }
+
+ body.is-dark-theme .bis-endpoint-url code {
+ background-color: #2c3338;
+ }
+
+ body.is-dark-theme .bis-empty-message {
+ background-color: #2c3338;
+ }
+
+ body.is-dark-theme .bis-tabs-nav {
+ border-color: #3c434a;
+ }
+}
+/**
+ * 需要添加到 admin.css 文件中的通知样式
+ */
+
+/* 通知消息样式 */
+.bis-notification {
+ position: fixed;
+ bottom: 20px;
+ right: 20px;
+ padding: 12px 16px;
+ background: #fff;
+ border-left: 4px solid #72aee6;
+ border-radius: 4px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ z-index: 999999;
+ transform: translateY(100px);
+ opacity: 0;
+ transition: transform 0.3s ease, opacity 0.3s ease;
+ max-width: 400px;
+}
+
+.bis-notification-show {
+ transform: translateY(0);
+ opacity: 1;
+}
+
+.bis-notification-success {
+ border-left-color: #00a32a;
+}
+
+.bis-notification-error {
+ border-left-color: #d63638;
+}
+
+.bis-notification-warning {
+ border-left-color: #dba617;
+}
+
+.bis-notification .dashicons {
+ font-size: 18px;
+ width: 18px;
+ height: 18px;
+ color: #72aee6;
+}
+
+.bis-notification-success .dashicons {
+ color: #00a32a;
+}
+
+.bis-notification-error .dashicons {
+ color: #d63638;
+}
+
+.bis-notification-warning .dashicons {
+ color: #dba617;
+}
+
+.bis-notification-message {
+ flex: 1;
+ font-size: 14px;
+}
+
+.bis-notification-close {
+ background: none;
+ border: none;
+ cursor: pointer;
+ padding: 0;
+ color: #646970;
+}
+
+.bis-notification-close:hover {
+ color: #1d2327;
+}
+
+.bis-notification-close .dashicons {
+ font-size: 16px;
+ width: 16px;
+ height: 16px;
+ color: inherit;
+}
+
+/* 加载蒙层 */
+.bis-loading-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.5);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ z-index: 100000;
+ color: #fff;
+}
+
+.bis-loading-overlay .spinner {
+ float: none;
+ margin: 0 auto 10px;
+ background-color: #fff;
+}
+
+/* 暗色模式支持 */
+body.is-dark-theme .bis-notification {
+ background-color: #2c3338;
+ color: #f0f0f1;
+}
+
+body.is-dark-theme .bis-notification-close {
+ color: #a7aaad;
+}
+
+body.is-dark-theme .bis-notification-close:hover {
+ color: #f0f0f1;
+}
+/**
+ * 为slug显示和工具提示添加的额外CSS样式
+ *
+ */
+
+/* Slug 展示样式 */
+.bis-slug-display {
+ margin-bottom: 18px;
+ padding: 10px;
+ background-color: #f8f8f8;
+ border-radius: 4px;
+ border-left: 3px solid #2271b1;
+}
+
+.bis-slug-info-wrapper {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+.bis-slug-info {
+ padding: 5px 8px;
+ background: #f0f0f1;
+ border-radius: 3px;
+ font-size: 13px;
+ color: #3c434a;
+ flex-grow: 1;
+}
+
+.bis-regenerate-slug {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+}
+
+.bis-regenerate-slug .dashicons {
+ font-size: 14px;
+ width: 14px;
+ height: 14px;
+}
+
+/* 工具提示样式 */
+.bis-tooltip {
+ position: relative;
+ cursor: help;
+ color: #646970;
+}
+
+.bis-tooltip .dashicons {
+ width: 16px;
+ height: 16px;
+ font-size: 16px;
+ vertical-align: middle;
+}
+
+.bis-tooltip-content {
+ position: absolute;
+ top: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ background-color: #333;
+ color: #fff;
+ padding: 6px 10px;
+ border-radius: 4px;
+ font-size: 12px;
+ white-space: nowrap;
+ z-index: 100;
+ margin-top: 8px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
+ max-width: 250px;
+ width: max-content;
+}
+
+.bis-tooltip-content::before {
+ content: '';
+ position: absolute;
+ bottom: 100%;
+ left: 50%;
+ margin-left: -5px;
+ border-width: 5px;
+ border-style: solid;
+ border-color: transparent transparent #333 transparent;
+}
+
+/* 暗色模式支持 */
+body.is-dark-theme .bis-slug-display {
+ background-color: #2c3338;
+ border-left-color: #72aee6;
+}
+
+body.is-dark-theme .bis-slug-info {
+ background-color: #1d2327;
+ color: #f0f0f1;
+}
+
+body.is-dark-theme .bis-tooltip-content {
+ background-color: #1d2327;
+ color: #f0f0f1;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.5);
+}
+
+body.is-dark-theme .bis-tooltip-content::before {
+ border-color: transparent transparent #1d2327 transparent;
+}
diff --git a/assets/js/admin.js b/assets/js/admin.js
new file mode 100644
index 0000000..06b494e
--- /dev/null
+++ b/assets/js/admin.js
@@ -0,0 +1,1031 @@
+/**
+ * Admin JavaScript for Collection Manager
+ */
+jQuery(document).ready(function($) {
+ // Collection data model
+ let currentCollection = {
+ name: '',
+ description: '',
+ icon: 'dashicons-admin-plugins',
+ category: 'business',
+ level: 'beginner',
+ author: '',
+ screenshot: '',
+ plugins: {
+ repository: [],
+ wenpai: [],
+ url: []
+ },
+ themes: {
+ repository: [],
+ wenpai: [],
+ url: []
+ }
+ };
+
+ let editingItemId = null;
+ let nextItemId = 1;
+
+ // Initialize UI
+ initTabs();
+ initItemSortable();
+ initEvents();
+ initIconSelection();
+ initTooltips();
+
+ // Tab navigation
+ function initTabs() {
+ $('.bis-tab').on('click', function() {
+ const tab = $(this).data('tab');
+ $('.bis-tab').removeClass('active');
+ $(this).addClass('active');
+ $('.bis-tab-content').removeClass('active').hide();
+ $('#bis-tab-' + tab).addClass('active').show();
+ });
+ }
+
+ // Initialize tooltips
+ function initTooltips() {
+ $('.bis-tooltip').hover(function() {
+ const tooltip = $(this).attr('data-tooltip');
+ if (tooltip) {
+ const $tooltipElement = $('
');
+ $tooltipElement.text(tooltip);
+ $(this).append($tooltipElement);
+ }
+ }, function() {
+ $(this).find('.bis-tooltip-content').remove();
+ });
+ }
+
+ // Icon selection functionality
+ function initIconSelection() {
+ const selectedIcon = $('#bis-collection-icon').val();
+ $('.bis-selected-icon').attr('class', 'bis-selected-icon dashicons ' + selectedIcon);
+
+ $('#bis-collection-icon').on('change', function() {
+ const selectedIcon = $(this).val();
+ $('.bis-selected-icon').attr('class', 'bis-selected-icon dashicons ' + selectedIcon);
+ });
+ }
+
+ // Make items sortable
+ function initItemSortable() {
+ $('.bis-items-container').sortable({
+ placeholder: 'bis-item-placeholder',
+ handle: '.bis-item-header',
+ update: function() {
+ updateCollectionFromUI();
+ }
+ });
+ }
+
+ // Initialize all event handlers
+ function initEvents() {
+ // Create new collection
+ $('.bis-new-collection').on('click', function() {
+ resetForm();
+ $('#bis-welcome-card').hide();
+ $('#bis-editor-title').text(bisAjax.i18n.add_collection || '创建新集合');
+ $('#bis-editor-card').show();
+ });
+
+ // Edit collection
+ $(document).on('click', '.bis-edit-collection', function() {
+ const collectionId = $(this).data('id');
+ loadCollection(collectionId);
+ });
+
+ // Regenerate slug
+ $(document).on('click', '.bis-regenerate-slug', function(e) {
+ e.preventDefault();
+ if (confirm(bisAjax.i18n.confirm_regenerate_slug || '这将为这个集合生成一个新的 slug。API URL 和这个集合的书签可能会失效。继续吗?')) {
+ $(this).data('force-new-slug', true);
+ saveCollection(true);
+ }
+ });
+
+ // Delete collection
+ $(document).on('click', '.bis-delete-collection', function() {
+ const collectionId = $(this).data('id');
+ const collectionName = $(this).closest('.bis-collection-item').find('h4').text();
+ if (confirm(bisAjax.i18n.confirm_delete || `确定要删除集合 "${collectionName}" 吗?`)) {
+ deleteCollection(collectionId);
+ }
+ });
+
+ // Cancel edit
+ $('.bis-cancel-edit').on('click', function() {
+ $('#bis-editor-card').hide();
+ $('#bis-welcome-card').show();
+ });
+
+ // Save collection
+ $('#bis-collection-form').on('submit', function(e) {
+ e.preventDefault();
+ saveCollection();
+ return false;
+ });
+
+ // Add item button
+ $('.bis-add-item').on('click', function() {
+ const itemType = $(this).data('type');
+ const itemSource = $(this).data('source');
+
+ // Show modal with empty form
+ editingItemId = null;
+ $('#bis-item-type').val(itemType);
+ $('#bis-item-source').val(itemSource);
+ $('#bis-item-id').val('');
+ $('#bis-item-slug').val('');
+ $('#bis-item-name').val('');
+ $('#bis-item-description').val('');
+ $('#bis-item-required').prop('checked', false);
+
+ // Show URL field if needed
+ $('#bis-url-field').toggle(itemSource === 'url');
+ if (itemSource === 'url') {
+ $('#bis-item-url').val('');
+ }
+
+ $('#bis-modal-title').text(
+ itemType === 'plugin' ?
+ bisAjax.i18n.add_plugin || '添加插件' :
+ bisAjax.i18n.add_theme || '添加主题'
+ );
+
+ showModal('#bis-item-modal');
+ });
+
+ // Modal close
+ $('.bis-modal-close, .bis-modal-cancel').on('click', function() {
+ closeModals();
+ });
+
+ // Click outside modal to close
+ $(document).on('click', '.bis-modal', function(e) {
+ if ($(e.target).hasClass('bis-modal')) {
+ closeModals();
+ }
+ });
+
+ // Save item
+ $('.bis-save-item').on('click', function() {
+ saveItem();
+ });
+
+ // Edit item (delegated)
+ $(document).on('click', '.bis-edit-item', function() {
+ const $item = $(this).closest('.bis-item');
+ const itemId = parseInt($item.data('id'));
+ const itemType = $item.closest('.bis-tab-content').attr('id') === 'bis-tab-plugins' ? 'plugin' : 'theme';
+ const itemSource = $item.closest('.bis-source-section').data('source');
+
+ // Find the item in the collection data
+ const items = itemType === 'plugin' ? currentCollection.plugins[itemSource] : currentCollection.themes[itemSource];
+ const item = items.find(i => i.id === itemId);
+
+ if (item) {
+ editingItemId = itemId;
+ $('#bis-item-type').val(itemType);
+ $('#bis-item-source').val(itemSource);
+ $('#bis-item-id').val(itemId);
+ $('#bis-item-slug').val(item.slug);
+ $('#bis-item-name').val(item.name);
+ $('#bis-item-description').val(item.description || '');
+ $('#bis-item-required').prop('checked', item.required || false);
+
+ // Show URL field if needed
+ $('#bis-url-field').toggle(itemSource === 'url');
+ if (itemSource === 'url') {
+ $('#bis-item-url').val(item.url || '');
+ }
+
+ $('#bis-modal-title').text('编辑' + (itemType === 'plugin' ? '插件' : '主题'));
+ showModal('#bis-item-modal');
+ }
+ });
+
+ // Remove item (delegated)
+ $(document).on('click', '.bis-remove-item', function() {
+ const $item = $(this).closest('.bis-item');
+ $item.fadeOut(300, function() {
+ $item.remove();
+ updateCollectionFromUI();
+ });
+ });
+
+ // Toggle item required
+ $(document).on('change', '.bis-item-required', function() {
+ updateCollectionFromUI();
+ });
+
+ // Upload screenshot
+ $('.bis-upload-screenshot').on('click', function(e) {
+ e.preventDefault();
+
+ // If the media frame already exists, reopen it
+ if (mediaUploader) {
+ mediaUploader.open();
+ return;
+ }
+
+ // Create the media frame
+ mediaUploader = wp.media({
+ title: '选择或上传截图',
+ button: {
+ text: '使用此图片'
+ },
+ multiple: false
+ });
+
+ // When an image is selected, run a callback
+ mediaUploader.on('select', function() {
+ const attachment = mediaUploader.state().get('selection').first().toJSON();
+ $('#bis-collection-screenshot').val(attachment.url);
+ updateScreenshotPreview(attachment.url);
+ });
+
+ // Open the media uploader
+ mediaUploader.open();
+ });
+
+ // Update screenshot preview when URL changes
+ $('#bis-collection-screenshot').on('change', function() {
+ updateScreenshotPreview($(this).val());
+ });
+
+ // Export format change
+ $('#bis-export-format').on('change', function() {
+ $('#bis-export-collection-select').toggle($(this).val() === 'selected');
+ });
+
+ // Export collections
+ $('.bis-export-collections').on('click', function() {
+ exportCollections();
+ });
+
+ // Copy URL buttons
+ $('.bis-copy-url').on('click', function() {
+ const url = $(this).data('url');
+ copyToClipboard(url);
+
+ // Show success message
+ const $button = $(this);
+ $button.html('');
+ setTimeout(function() {
+ $button.html('');
+ }, 2000);
+ });
+
+ // Import collection
+ $('.bis-import-collection').on('click', function() {
+ showModal('#bis-import-modal');
+ });
+
+ // Import submit
+ $('.bis-import-submit').on('click', function() {
+ importCollections();
+ });
+
+ // Form key event handling
+ $('#bis-item-form').on('keypress', function(e) {
+ if (e.which === 13) {
+ e.preventDefault();
+ $('.bis-save-item').click();
+ }
+ });
+
+ // Keyboard shortcut (Escape to close modals)
+ $(document).on('keydown', function(e) {
+ if (e.key === 'Escape') {
+ closeModals();
+ }
+ });
+
+ // Prevent modals from closing when clicking inside the modal content
+ $('.bis-modal-content').on('click', function(e) {
+ e.stopPropagation();
+ });
+
+ // Check if URL parameters contain action=edit and id=xyz
+ const urlParams = new URLSearchParams(window.location.search);
+ const action = urlParams.get('action');
+ const id = urlParams.get('id');
+
+ if (action === 'edit' && id) {
+ // Auto-load collection for editing
+ loadCollection(id);
+ }
+ }
+
+ // Media uploader instance
+ let mediaUploader = null;
+
+ // Show a modal
+ function showModal(selector) {
+ $(selector).fadeIn(300);
+ $('body').addClass('bis-modal-open');
+ }
+
+ // Close all modals
+ function closeModals() {
+ $('.bis-modal').fadeOut(200);
+ $('body').removeClass('bis-modal-open');
+ }
+
+ // Update screenshot preview
+ function updateScreenshotPreview(url) {
+ const $preview = $('#bis-screenshot-preview');
+ if (url) {
+ $preview.html(`
`);
+ } else {
+ $preview.empty();
+ }
+ }
+
+ // Reset form to create a new collection
+ function resetForm() {
+ $('#bis-collection-id').val('');
+ $('#bis-collection-name').val('');
+ $('#bis-collection-description').val('');
+ $('#bis-collection-icon').val('dashicons-admin-plugins');
+ $('#bis-collection-category').val('business');
+ $('#bis-collection-level').val('beginner');
+ $('#bis-collection-author').val(document.title.split(' ‹ ')[1] || '');
+ $('#bis-collection-screenshot').val('');
+ $('#bis-screenshot-preview').empty();
+
+ // 隐藏 slug 信息 (只在编辑现有集合时显示)
+ $('.bis-slug-display').hide();
+ $('.bis-slug-info').text('');
+ $('.bis-regenerate-slug').data('force-new-slug', false);
+
+ // Clear all items
+ $('.bis-items-container').empty();
+
+ // Reset collection data
+ currentCollection = {
+ name: '',
+ description: '',
+ icon: 'dashicons-admin-plugins',
+ category: 'business',
+ level: 'beginner',
+ author: document.title.split(' ‹ ')[1] || '',
+ screenshot: '',
+ plugins: {
+ repository: [],
+ wenpai: [],
+ url: []
+ },
+ themes: {
+ repository: [],
+ wenpai: [],
+ url: []
+ }
+ };
+
+ // Reset item ID counter
+ nextItemId = 1;
+
+ // Update icon preview
+ initIconSelection();
+ }
+
+ // Load collection data into form
+ function loadCollection(collectionId) {
+ // Add loading state to the editor card
+ const $editorCard = $('#bis-editor-card');
+ const $loadingOverlay = $('');
+ $editorCard.append($loadingOverlay);
+
+ $.ajax({
+ url: bisAjax.ajaxurl,
+ type: 'GET',
+ data: {
+ action: 'bis_get_collection',
+ nonce: bisAjax.nonce,
+ collection_id: collectionId
+ },
+ success: function(response) {
+ $loadingOverlay.remove();
+
+ if (response.success) {
+ const collection = response.data.collection;
+ currentCollection = collection;
+
+ // Update form fields
+ $('#bis-collection-id').val(collectionId);
+ $('#bis-collection-name').val(collection.name);
+ $('#bis-collection-description').val(collection.description);
+ $('#bis-collection-icon').val(collection.icon);
+ $('#bis-collection-category').val(collection.category);
+ $('#bis-collection-level').val(collection.level);
+ $('#bis-collection-author').val(collection.author);
+ $('#bis-collection-screenshot').val(collection.screenshot);
+ updateScreenshotPreview(collection.screenshot);
+
+ // 显示 slug 信息
+ $('.bis-slug-display').show();
+ $('.bis-slug-info').text(collectionId);
+
+ // 重置强制生成新 slug 标记
+ $('.bis-regenerate-slug').data('force-new-slug', false);
+
+ // Update icon preview
+ initIconSelection();
+
+ // Clear all items first
+ $('.bis-items-container').empty();
+
+ // Add plugins and themes to UI
+ renderItems('plugin', 'repository', collection.plugins.repository || []);
+ renderItems('plugin', 'wenpai', collection.plugins.wenpai || []);
+ renderItems('plugin', 'url', collection.plugins.url || []);
+ renderItems('theme', 'repository', collection.themes.repository || []);
+ renderItems('theme', 'wenpai', collection.themes.wenpai || []);
+ renderItems('theme', 'url', collection.themes.url || []);
+
+ // Find the highest item ID to continue from
+ nextItemId = 1;
+ const updateNextId = (items) => {
+ if (!items) return;
+ items.forEach(item => {
+ if (item.id && item.id >= nextItemId) {
+ nextItemId = item.id + 1;
+ }
+ });
+ };
+
+ updateNextId(collection.plugins.repository);
+ updateNextId(collection.plugins.wenpai);
+ updateNextId(collection.plugins.url);
+ updateNextId(collection.themes.repository);
+ updateNextId(collection.themes.wenpai);
+ updateNextId(collection.themes.url);
+
+ // Show editor
+ $('#bis-welcome-card').hide();
+ $('#bis-editor-title').text((bisAjax.i18n.edit_collection || '编辑集合') + ': ' + collection.name);
+ $('#bis-editor-card').show();
+ } else {
+ console.error('加载集合失败:', response.data);
+ showNotification(response.data.message || '加载集合时出错', 'error');
+ }
+ },
+ error: function(xhr, status, error) {
+ $loadingOverlay.remove();
+ console.error('AJAX错误:', xhr.responseText);
+ showNotification('加载集合时出错: ' + error, 'error');
+ }
+ });
+ }
+
+ // Render items to the UI
+ function renderItems(type, source, items) {
+ if (!items || !Array.isArray(items)) return;
+
+ const $container = $(`#bis-${type}s-${source}`);
+
+ items.forEach(item => {
+ // Ensure item has an ID
+ if (!item.id) {
+ item.id = nextItemId++;
+ }
+
+ const itemHtml = generateItemHtml(item);
+ $container.append(itemHtml);
+ });
+ }
+
+ // Generate HTML for an item
+ function generateItemHtml(item) {
+ let template = $('#bis-item-template').html();
+
+ template = template.replace(/{{id}}/g, item.id);
+ template = template.replace(/{{name}}/g, escapeHtml(item.name));
+ template = template.replace(/{{slug}}/g, escapeHtml(item.slug));
+ template = template.replace(/{{description}}/g, escapeHtml(item.description || ''));
+ template = template.replace(/{{required}}/g, item.required ? 'checked' : '');
+
+ return template;
+ }
+
+ // Escape HTML to prevent XSS
+ function escapeHtml(text) {
+ if (!text) return '';
+ return text
+ .replace(/&/g, "&")
+ .replace(//g, ">")
+ .replace(/"/g, """)
+ .replace(/'/g, "'");
+ }
+
+ // Highlight newly added/edited elements
+ function highlightElement($element) {
+ $element.addClass('bis-highlight');
+ setTimeout(function() {
+ $element.removeClass('bis-highlight');
+ }, 1500);
+ }
+
+ // Save an item from the modal
+ function saveItem() {
+ const itemType = $('#bis-item-type').val();
+ const itemSource = $('#bis-item-source').val();
+ const itemId = editingItemId || nextItemId++;
+ const itemSlug = $('#bis-item-slug').val();
+ const itemName = $('#bis-item-name').val();
+ const itemDescription = $('#bis-item-description').val();
+ const itemRequired = $('#bis-item-required').is(':checked');
+
+ if (!itemSlug || !itemName) {
+ if (!itemSlug) {
+ $('#bis-item-slug').focus();
+ showNotification('Slug 是必填项', 'warning');
+ } else {
+ $('#bis-item-name').focus();
+ showNotification('名称是必填项', 'warning');
+ }
+ return;
+ }
+
+ // Create item object
+ const item = {
+ id: itemId,
+ slug: itemSlug,
+ name: itemName,
+ description: itemDescription,
+ required: itemRequired
+ };
+
+ // Add URL if it's a URL source
+ if (itemSource === 'url') {
+ item.url = $('#bis-item-url').val();
+
+ if (!item.url) {
+ $('#bis-item-url').focus();
+ showNotification('URL 是必填项', 'warning');
+ return;
+ }
+ }
+
+ // Add to collection data
+ const items = itemType === 'plugin' ? currentCollection.plugins[itemSource] : currentCollection.themes[itemSource];
+
+ if (editingItemId) {
+ // Update existing item
+ const index = items.findIndex(i => i.id === editingItemId);
+ if (index !== -1) {
+ items[index] = item;
+ }
+ } else {
+ // Add new item
+ items.push(item);
+ }
+
+ // Update UI
+ const $container = $(`#bis-${itemType}s-${itemSource}`);
+
+ if (editingItemId) {
+ // Update existing item in UI
+ const $item = $container.find(`.bis-item[data-id="${editingItemId}"]`);
+ const $newItem = $(generateItemHtml(item));
+ $item.replaceWith($newItem);
+ highlightElement($newItem);
+ } else {
+ // Add new item to UI
+ const $newItem = $(generateItemHtml(item));
+ $container.append($newItem);
+ highlightElement($newItem);
+ }
+
+ // Close modal
+ closeModals();
+
+ // Show success notification
+ showNotification(editingItemId ? '项目已更新' : '项目已添加', 'success');
+ }
+
+ // Update collection data from UI
+ function updateCollectionFromUI() {
+ // Basic info
+ currentCollection.name = $('#bis-collection-name').val();
+ currentCollection.description = $('#bis-collection-description').val();
+ currentCollection.icon = $('#bis-collection-icon').val();
+ currentCollection.category = $('#bis-collection-category').val();
+ currentCollection.level = $('#bis-collection-level').val();
+ currentCollection.author = $('#bis-collection-author').val();
+ currentCollection.screenshot = $('#bis-collection-screenshot').val();
+
+ // Get the order and required status of plugins and themes
+ updateItemsFromUI('plugin', 'repository');
+ updateItemsFromUI('plugin', 'wenpai');
+ updateItemsFromUI('plugin', 'url');
+ updateItemsFromUI('theme', 'repository');
+ updateItemsFromUI('theme', 'wenpai');
+ updateItemsFromUI('theme', 'url');
+ }
+
+ // Update items from UI
+ function updateItemsFromUI(type, source) {
+ const $container = $(`#bis-${type}s-${source}`);
+ const items = type === 'plugin' ? currentCollection.plugins[source] : currentCollection.themes[source];
+
+ // Get the current order of items
+ const itemIds = $container.find('.bis-item').map(function() {
+ return parseInt($(this).data('id'));
+ }).get();
+
+ // Get required status for each item
+ $container.find('.bis-item').each(function() {
+ const id = parseInt($(this).data('id'));
+ const required = $(this).find('.bis-item-required').is(':checked');
+
+ // Update required status in collection data
+ const item = items.find(i => i.id === id);
+ if (item) {
+ item.required = required;
+ }
+ });
+
+ // Reorder items based on UI
+ const reorderedItems = [];
+ itemIds.forEach(id => {
+ const item = items.find(i => i.id === id);
+ if (item) {
+ reorderedItems.push(item);
+ }
+ });
+
+ // Update collection with reordered items
+ if (type === 'plugin') {
+ currentCollection.plugins[source] = reorderedItems;
+ } else {
+ currentCollection.themes[source] = reorderedItems;
+ }
+ }
+
+ // Add debug logging function
+ function logDebug(label, data) {
+ console.group('调试: ' + label);
+ console.log(data);
+ console.groupEnd();
+ }
+
+ // Save collection
+ function saveCollection(forceNewSlug = false) {
+ updateCollectionFromUI();
+
+ // Validate required fields
+ if (!currentCollection.name) {
+ $('#bis-collection-name').focus();
+ showNotification(bisAjax.i18n.name_required || '集合名称是必填项', 'warning');
+ return;
+ }
+
+ // Check if we have at least one plugin or theme
+ let hasItems = false;
+ let totalItemCount = 0;
+
+ for (const source in currentCollection.plugins) {
+ totalItemCount += currentCollection.plugins[source].length;
+ if (currentCollection.plugins[source].length > 0) {
+ hasItems = true;
+ }
+ }
+
+ if (!hasItems) {
+ for (const source in currentCollection.themes) {
+ totalItemCount += currentCollection.themes[source].length;
+ if (currentCollection.themes[source].length > 0) {
+ hasItems = true;
+ }
+ }
+ }
+
+ if (!hasItems) {
+ showNotification(bisAjax.i18n.item_required || '请至少添加一个插件或主题到集合中', 'warning');
+ // Switch to plugins tab
+ $('.bis-tab[data-tab="plugins"]').click();
+ return;
+ }
+
+ // Prepare data for saving
+ const slug = $('#bis-collection-id').val();
+ const $saveButton = $('.bis-save-collection');
+
+ // Add loading state to button
+ const originalButtonText = $saveButton.html();
+ $saveButton.prop('disabled', true)
+ .html('' +
+ (bisAjax.i18n.saving || '保存中...'));
+
+ // 判断是否需要强制生成新的 slug
+ if (forceNewSlug || $('.bis-regenerate-slug').data('force-new-slug')) {
+ forceNewSlug = true;
+ }
+
+ // 添加日志记录
+ logDebug('发送数据', {
+ collection: currentCollection,
+ collection_id: slug,
+ force_new_slug: forceNewSlug
+ });
+
+ $.ajax({
+ url: bisAjax.ajaxurl,
+ type: 'POST',
+ data: {
+ action: 'bis_save_collection',
+ nonce: bisAjax.nonce,
+ collection: JSON.stringify(currentCollection),
+ collection_id: slug,
+ force_new_slug: forceNewSlug
+ },
+ success: function(response) {
+ logDebug('保存响应', response);
+
+ if (response.success) {
+ const newSlug = response.data.collection_id;
+
+ // 如果 slug 发生了变化,更新表单
+ if (slug !== newSlug) {
+ $('#bis-collection-id').val(newSlug);
+ $('.bis-slug-info').text(newSlug);
+
+ // 显示 slug 信息区域
+ $('.bis-slug-display').show();
+
+ if (forceNewSlug) {
+ showNotification('已成功重新生成 Slug', 'success');
+ } else {
+ showNotification(bisAjax.i18n.slug_generated || '已为此集合自动生成 slug', 'info', 5000);
+ }
+
+ // 重置强制生成新 slug 标记
+ $('.bis-regenerate-slug').data('force-new-slug', false);
+ } else {
+ // 显示成功消息
+ showNotification('集合保存成功!', 'success');
+ }
+
+ // 重新加载页面以显示更新后的集合
+ setTimeout(function() {
+ window.location.reload();
+ }, 1500);
+ } else {
+ showNotification(response.data.message || bisAjax.i18n.save_error || '保存集合时出错', 'error');
+ $saveButton.prop('disabled', false).html(originalButtonText);
+ }
+ },
+ error: function(xhr, status, error) {
+ console.error('保存错误:', xhr.responseText);
+ showNotification(bisAjax.i18n.save_error || '保存集合时出错', 'error');
+ $saveButton.prop('disabled', false).html(originalButtonText);
+ }
+ });
+ }
+
+ // Show notification messages
+ function showNotification(message, type = 'info', duration = 3000) {
+ // Remove any existing notifications
+ $('.bis-notification').remove();
+
+ // Set notification class based on type
+ let notificationClass = 'bis-notification-info';
+ let iconClass = 'dashicons-info';
+
+ if (type === 'success') {
+ notificationClass = 'bis-notification-success';
+ iconClass = 'dashicons-yes';
+ } else if (type === 'error') {
+ notificationClass = 'bis-notification-error';
+ iconClass = 'dashicons-no';
+ } else if (type === 'warning') {
+ notificationClass = 'bis-notification-warning';
+ iconClass = 'dashicons-warning';
+ }
+
+ // Create notification element
+ const $notification = $(
+ `
+
+ ${message}
+
+
`
+ );
+
+ // Add to page
+ $('body').append($notification);
+
+ // Show with animation
+ setTimeout(function() {
+ $notification.addClass('bis-notification-show');
+ }, 10);
+
+ // Close button functionality
+ $notification.find('.bis-notification-close').on('click', function() {
+ $notification.removeClass('bis-notification-show');
+ setTimeout(function() {
+ $notification.remove();
+ }, 300);
+ });
+
+ // Auto close after duration
+ if (duration > 0) {
+ setTimeout(function() {
+ $notification.removeClass('bis-notification-show');
+ setTimeout(function() {
+ $notification.remove();
+ }, 300);
+ }, duration);
+ }
+ }
+
+ // Delete collection
+ function deleteCollection(slug) {
+ // Add loading overlay
+ const $loadingOverlay = $('');
+ $('body').append($loadingOverlay);
+
+ $.ajax({
+ url: bisAjax.ajaxurl,
+ type: 'POST',
+ data: {
+ action: 'bis_delete_collection',
+ nonce: bisAjax.nonce,
+ collection_id: slug
+ },
+ success: function(response) {
+ $loadingOverlay.remove();
+
+ if (response.success) {
+ // Show success message
+ showNotification('集合已成功删除', 'success');
+ // Reload page to reflect changes
+ setTimeout(function() {
+ window.location.reload();
+ }, 1000);
+ } else {
+ showNotification(response.data.message || '删除集合时出错', 'error');
+ }
+ },
+ error: function(xhr, status, error) {
+ $loadingOverlay.remove();
+ console.error('删除错误:', xhr.responseText);
+ showNotification('删除集合时出错: ' + error, 'error');
+ }
+ });
+ }
+
+ // Export collections
+ function exportCollections() {
+ const format = $('#bis-export-format').val();
+
+ if (format === 'all') {
+ // Export all collections
+ $.ajax({
+ url: bisAjax.rest_url,
+ type: 'GET',
+ success: function(response) {
+ downloadJson(response, 'collections.json');
+ showNotification('所有集合导出成功', 'success');
+ },
+ error: function(xhr, status, error) {
+ console.error('导出错误:', xhr.responseText);
+ showNotification('导出集合时出错: ' + error, 'error');
+ }
+ });
+ } else {
+ // Export selected collection
+ const slug = $('#bis-export-collection').val();
+
+ if (!slug) {
+ showNotification('请选择要导出的集合', 'warning');
+ return;
+ }
+
+ // 使用 REST API 路径获取集合
+ // 注意:我们使用正确的 URL 路径,"collection" 而不是 "collections"
+ const collectionUrl = bisAjax.rest_url.replace('/collections', '/collection/' + slug);
+
+ $.ajax({
+ url: collectionUrl,
+ type: 'GET',
+ success: function(response) {
+ // For single collection, we can optionally strip the wrapper and just export the collection
+ const collection = response.collections[slug];
+ downloadJson(collection, slug + '.json');
+ showNotification('集合导出成功', 'success');
+ },
+ error: function(xhr, status, error) {
+ console.error('导出失败:', xhr.responseJSON);
+ showNotification('导出集合时出错: ' + error, 'error');
+ }
+ });
+ }
+ }
+
+ // Import collections
+ function importCollections() {
+ const importData = $('#bis-import-data').val();
+
+ if (!importData) {
+ showNotification('请粘贴集合 JSON 数据', 'warning');
+ return;
+ }
+
+ try {
+ // Validate JSON
+ JSON.parse(importData);
+
+ // Add loading state
+ const $importButton = $('.bis-import-submit');
+ const originalButtonText = $importButton.text();
+ $importButton.prop('disabled', true)
+ .html('导入中...');
+
+ // Submit import
+ $.ajax({
+ url: bisAjax.ajaxurl,
+ type: 'POST',
+ data: {
+ action: 'bis_import_collection',
+ nonce: bisAjax.nonce,
+ import_data: importData
+ },
+ success: function(response) {
+ if (response.success) {
+ // Close modal
+ closeModals();
+ // Show success message
+ showNotification(response.data.message || '集合导入成功', 'success');
+ // Reload page to reflect changes
+ setTimeout(function() {
+ window.location.reload();
+ }, 1000);
+ } else {
+ $importButton.prop('disabled', false).text(originalButtonText);
+ showNotification(response.data.message || '导入集合时出错', 'error');
+ }
+ },
+ error: function(xhr, status, error) {
+ $importButton.prop('disabled', false).text(originalButtonText);
+ console.error('导入错误:', xhr.responseText);
+ showNotification('导入集合时出错: ' + error, 'error');
+ }
+ });
+ } catch (e) {
+ showNotification(bisAjax.i18n.invalid_json || 'JSON 格式无效', 'error');
+ }
+ }
+
+ // Utility functions
+ function downloadJson(data, filename) {
+ const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(data, null, 2));
+ const downloadAnchorNode = document.createElement('a');
+ downloadAnchorNode.setAttribute("href", dataStr);
+ downloadAnchorNode.setAttribute("download", filename);
+ document.body.appendChild(downloadAnchorNode);
+ downloadAnchorNode.click();
+ downloadAnchorNode.remove();
+ }
+
+ function copyToClipboard(text) {
+ // 使用现代 Clipboard API (如果可用)
+ if (navigator.clipboard && window.isSecureContext) {
+ navigator.clipboard.writeText(text).then(() => {
+ showNotification('已复制到剪贴板', 'success', 2000);
+ }).catch(() => {
+ // 如果 Clipboard API 失败,回退到传统方法
+ fallbackCopyToClipboard(text);
+ });
+ } else {
+ // 对于不支持 Clipboard API 的浏览器使用传统方法
+ fallbackCopyToClipboard(text);
+ }
+ }
+
+ function fallbackCopyToClipboard(text) {
+ const textarea = document.createElement('textarea');
+ textarea.value = text;
+ textarea.style.position = 'fixed'; // 防止滚动到视图底部
+ document.body.appendChild(textarea);
+ textarea.select();
+
+ try {
+ const successful = document.execCommand('copy');
+ if (successful) {
+ showNotification('已复制到剪贴板', 'success', 2000);
+ } else {
+ showNotification('复制失败,请手动复制', 'warning');
+ }
+ } catch (err) {
+ showNotification('复制失败: ' + err, 'error');
+ }
+
+ document.body.removeChild(textarea);
+ }
+});
diff --git a/bulk-installer-server.php b/bulk-installer-server.php
new file mode 100644
index 0000000..c2aa63a
--- /dev/null
+++ b/bulk-installer-server.php
@@ -0,0 +1,585 @@
+ 'GET',
+ 'callback' => 'bis_rest_get_collections',
+ 'permission_callback' => '__return_true'
+ ]);
+
+ // 按 slug 获取特定合集 - 使用 WordPress 标准的 slug 格式(字母、数字、连字符)
+ register_rest_route('bulk-installer-server/v1', '/collection/(?P[\w-]+)', [
+ 'methods' => 'GET',
+ 'callback' => 'bis_rest_get_collection',
+ 'permission_callback' => '__return_true'
+ ]);
+}
+
+/**
+ * REST API handler for getting all collections
+ */
+function bis_rest_get_collections() {
+ $collections = bis_get_collections();
+
+ return new WP_REST_Response([
+ 'version' => BIS_VERSION,
+ 'last_updated' => gmdate('Y-m-d'),
+ 'collections' => $collections
+ ], 200);
+}
+
+/**
+ * REST API handler for getting a specific collection
+ */
+function bis_rest_get_collection($request) {
+ $slug = $request['slug'];
+ $collections = bis_get_collections();
+
+ if (!isset($collections[$slug])) {
+ return new WP_REST_Response([
+ 'error' => 'Collection not found',
+ 'requested_slug' => $slug,
+ 'available_slugs' => array_keys($collections)
+ ], 404);
+ }
+
+ return new WP_REST_Response([
+ 'version' => BIS_VERSION,
+ 'last_updated' => gmdate('Y-m-d'),
+ 'collections' => [
+ $slug => $collections[$slug]
+ ]
+ ], 200);
+}
+
+/**
+ * Add admin menu page
+ */
+function bis_add_menu_page() {
+ add_menu_page(
+ __('Collection Manager', 'bulk-installer-server'),
+ __('Collection Manager', 'bulk-installer-server'),
+ 'manage_options',
+ 'bulk-installer-server',
+ 'bis_render_admin_page',
+ 'dashicons-layout',
+ 65
+ );
+}
+
+/**
+ * Enqueue admin scripts and styles
+ */
+function bis_admin_scripts($hook) {
+ if ($hook !== 'toplevel_page_bulk-installer-server') {
+ return;
+ }
+
+ wp_enqueue_style('bis-admin-style', BIS_URL . 'assets/css/admin.css', [], BIS_VERSION);
+ wp_enqueue_script('bis-admin', BIS_URL . 'assets/js/admin.js', ['jquery', 'jquery-ui-sortable'], BIS_VERSION, true);
+
+ wp_localize_script('bis-admin', 'bisAjax', [
+ 'nonce' => wp_create_nonce('bis_nonce'),
+ 'ajaxurl' => admin_url('admin-ajax.php'),
+ 'rest_url' => rest_url('bulk-installer-server/v1/collections'),
+ 'siteurl' => site_url(),
+ 'i18n' => [
+ 'confirm_delete' => __('Are you sure you want to delete this collection?', 'bulk-installer-server'),
+ 'saving' => __('Saving...', 'bulk-installer-server'),
+ 'save_error' => __('Error saving collection', 'bulk-installer-server'),
+ 'add_plugin' => __('Add Plugin', 'bulk-installer-server'),
+ 'add_theme' => __('Add Theme', 'bulk-installer-server'),
+ 'invalid_json' => __('Invalid JSON format', 'bulk-installer-server'),
+ 'add_collection' => __('Create New Collection', 'bulk-installer-server'),
+ 'edit_collection' => __('Edit Collection', 'bulk-installer-server'),
+ 'slug_generated' => __('A slug has been automatically generated for this collection', 'bulk-installer-server'),
+ 'name_required' => __('Collection name is required', 'bulk-installer-server'),
+ 'item_required' => __('Please add at least one plugin or theme', 'bulk-installer-server'),
+ 'confirm_regenerate_slug' => __('This will generate a new slug for this collection. API URLs and bookmarks to this collection may break. Continue?', 'bulk-installer-server')
+ ]
+ ]);
+
+ // Enqueue WordPress media scripts
+ wp_enqueue_media();
+}
+
+/**
+ * Render the admin page
+ */
+function bis_render_admin_page() {
+ if (!current_user_can('manage_options')) {
+ wp_die(__('You do not have sufficient permissions to access this page.', 'bulk-installer-server'));
+ }
+
+ $collections = bis_get_collections();
+ include BIS_PATH . 'templates/admin-page.php';
+}
+
+/**
+ * Get all collections
+ *
+ * @return array Collections data
+ */
+function bis_get_collections() {
+ $collections = get_option('bis_collections', []);
+ return $collections;
+}
+
+/**
+ * Generate a WordPress-compatible slug
+ *
+ * @param string $name The collection name
+ * @param array $existing_collections Existing collections
+ * @return string The generated slug
+ */
+function bis_generate_slug($name, $existing_collections = []) {
+ // 使用 WordPress 原生函数生成 slug
+ $slug = sanitize_title($name);
+
+ // 如果 slug 为空(可能由于只包含特殊字符),则生成一个默认 slug
+ if (empty($slug)) {
+ $slug = 'collection-' . substr(md5($name), 0, 8);
+ }
+
+ $original_slug = $slug;
+ $counter = 1;
+
+ // 确保 slug 的唯一性
+ while (isset($existing_collections[$slug])) {
+ $slug = $original_slug . '-' . $counter;
+ $counter++;
+ }
+
+ return $slug;
+}
+
+/**
+ * Save a collection
+ *
+ * @param array $collection Collection data
+ * @param string $slug Collection slug
+ * @param bool $force_new_slug Whether to force generating a new slug
+ * @return bool|string True if successful, error message if failed
+ */
+function bis_save_collection($collection, $slug = '', $force_new_slug = false) {
+ if (!current_user_can('manage_options')) {
+ return __('Insufficient permissions', 'bulk-installer-server');
+ }
+
+ $collections = bis_get_collections();
+
+ // 获取集合名称
+ $name = isset($collection['name']) ? trim($collection['name']) : '';
+
+ if (empty($name)) {
+ return __('Collection name is required', 'bulk-installer-server');
+ }
+
+ // 处理 slug 生成
+ if (empty($slug) || $force_new_slug) {
+ // 生成新的 slug
+ $slug = bis_generate_slug($name, $collections);
+ } else if (!isset($collections[$slug])) {
+ // 如果提供了 slug 但不存在,检查它是否是有效的
+ if (!preg_match('/^[\w-]+$/', $slug)) {
+ // 无效的 slug,生成新的
+ $slug = bis_generate_slug($name, $collections);
+ }
+ }
+
+ // Sanitize collection data
+ $collection['name'] = sanitize_text_field($name);
+ $collection['slug'] = $slug; // 存储生成的 slug
+ $collection['description'] = isset($collection['description']) ? sanitize_textarea_field($collection['description']) : '';
+ $collection['icon'] = isset($collection['icon']) ? sanitize_text_field($collection['icon']) : 'dashicons-admin-plugins';
+ $collection['category'] = isset($collection['category']) ? sanitize_text_field($collection['category']) : 'other';
+ $collection['level'] = isset($collection['level']) ? sanitize_text_field($collection['level']) : 'beginner';
+ $collection['author'] = isset($collection['author']) ? sanitize_text_field($collection['author']) : get_bloginfo('name');
+
+ if (!empty($collection['screenshot'])) {
+ $collection['screenshot'] = esc_url_raw($collection['screenshot']);
+ }
+
+ // Sanitize plugins/themes
+ $collection['plugins'] = isset($collection['plugins']) ? bis_sanitize_items($collection['plugins']) : ['repository' => [], 'wenpai' => [], 'url' => []];
+ $collection['themes'] = isset($collection['themes']) ? bis_sanitize_items($collection['themes']) : ['repository' => [], 'wenpai' => [], 'url' => []];
+
+ // Store in the collections array
+ $collections[$slug] = $collection;
+
+ // Save to the database
+ $updated = update_option('bis_collections', $collections);
+
+ if (!$updated) {
+ return __('Failed to save collection', 'bulk-installer-server');
+ }
+
+ return true;
+}
+
+/**
+ * Sanitize plugin/theme items
+ *
+ * @param array $items Items to sanitize
+ * @return array Sanitized items
+ */
+function bis_sanitize_items($items) {
+ $sanitized = [
+ 'repository' => [],
+ 'wenpai' => [],
+ 'url' => []
+ ];
+
+ if (!is_array($items)) {
+ return $sanitized;
+ }
+
+ foreach (['repository', 'wenpai', 'url'] as $source) {
+ if (!isset($items[$source]) || !is_array($items[$source])) {
+ continue;
+ }
+
+ foreach ($items[$source] as $item) {
+ if (is_array($item)) {
+ $sanitized_item = [
+ 'id' => isset($item['id']) ? absint($item['id']) : 0,
+ 'slug' => sanitize_text_field($item['slug'] ?? ''),
+ 'name' => sanitize_text_field($item['name'] ?? ''),
+ 'description' => sanitize_textarea_field($item['description'] ?? ''),
+ 'required' => !empty($item['required'])
+ ];
+
+ // Add URL for URL source items
+ if ($source === 'url' && !empty($item['url'])) {
+ $sanitized_item['url'] = esc_url_raw($item['url']);
+ }
+
+ $sanitized[$source][] = $sanitized_item;
+ } else if (is_string($item)) {
+ $sanitized[$source][] = sanitize_text_field($item);
+ }
+ }
+ }
+
+ return $sanitized;
+}
+
+/**
+ * Ajax handler for getting a collection
+ */
+function bis_ajax_get_collection() {
+ check_ajax_referer('bis_nonce', 'nonce');
+
+ if (!current_user_can('manage_options')) {
+ wp_send_json_error([
+ 'message' => __('Insufficient permissions', 'bulk-installer-server')
+ ]);
+ }
+
+ $slug = isset($_GET['collection_id']) ? sanitize_text_field(wp_unslash($_GET['collection_id'])) : '';
+
+ if (empty($slug)) {
+ wp_send_json_error([
+ 'message' => __('No collection ID provided', 'bulk-installer-server')
+ ]);
+ }
+
+ $collections = bis_get_collections();
+
+ if (!isset($collections[$slug])) {
+ wp_send_json_error([
+ 'message' => __('Collection not found', 'bulk-installer-server'),
+ 'requested_slug' => $slug,
+ 'available_slugs' => array_keys($collections)
+ ]);
+ }
+
+ wp_send_json_success([
+ 'collection' => $collections[$slug]
+ ]);
+}
+
+/**
+ * Ajax handler for saving collections
+ */
+function bis_ajax_save_collection() {
+ check_ajax_referer('bis_nonce', 'nonce');
+
+ if (!current_user_can('manage_options')) {
+ wp_send_json_error([
+ 'message' => __('Insufficient permissions', 'bulk-installer-server')
+ ]);
+ }
+
+ $collection_data = isset($_POST['collection']) ? json_decode(stripslashes($_POST['collection']), true) : [];
+ $slug = isset($_POST['collection_id']) ? sanitize_text_field(wp_unslash($_POST['collection_id'])) : '';
+ $force_new_slug = isset($_POST['force_new_slug']) && $_POST['force_new_slug'] === 'true';
+
+ if (empty($collection_data) || !is_array($collection_data)) {
+ wp_send_json_error([
+ 'message' => __('Invalid collection data', 'bulk-installer-server')
+ ]);
+ }
+
+ $result = bis_save_collection($collection_data, $slug, $force_new_slug);
+
+ if ($result === true) {
+ // 如果是新集合,我们需要找到新的 slug
+ if (empty($slug) || $force_new_slug) {
+ $slug = bis_generate_slug($collection_data['name'], bis_get_collections());
+
+ // 再次检查是否匹配,因为可能在保存过程中其他集合也创建了相同的 slug
+ $collections = bis_get_collections();
+ foreach ($collections as $collection_slug => $collection) {
+ if ($collection['name'] === $collection_data['name'] && $collection_slug !== $slug) {
+ $slug = $collection_slug;
+ break;
+ }
+ }
+ }
+
+ wp_send_json_success([
+ 'message' => __('Collection saved successfully', 'bulk-installer-server'),
+ 'collection_id' => $slug,
+ 'collections' => bis_get_collections()
+ ]);
+ } else {
+ wp_send_json_error([
+ 'message' => $result
+ ]);
+ }
+}
+
+/**
+ * Ajax handler for deleting collections
+ */
+function bis_ajax_delete_collection() {
+ check_ajax_referer('bis_nonce', 'nonce');
+
+ if (!current_user_can('manage_options')) {
+ wp_send_json_error([
+ 'message' => __('Insufficient permissions', 'bulk-installer-server')
+ ]);
+ }
+
+ $slug = isset($_POST['collection_id']) ? sanitize_text_field(wp_unslash($_POST['collection_id'])) : '';
+
+ if (empty($slug)) {
+ wp_send_json_error([
+ 'message' => __('No collection ID provided', 'bulk-installer-server')
+ ]);
+ }
+
+ $collections = bis_get_collections();
+
+ if (!isset($collections[$slug])) {
+ wp_send_json_error([
+ 'message' => __('Collection not found', 'bulk-installer-server')
+ ]);
+ }
+
+ unset($collections[$slug]);
+
+ $updated = update_option('bis_collections', $collections);
+
+ if (!$updated) {
+ wp_send_json_error([
+ 'message' => __('Failed to delete collection', 'bulk-installer-server')
+ ]);
+ }
+
+ wp_send_json_success([
+ 'message' => __('Collection deleted successfully', 'bulk-installer-server'),
+ 'collections' => $collections
+ ]);
+}
+
+/**
+ * Ajax handler for importing collections
+ */
+function bis_ajax_import_collection() {
+ check_ajax_referer('bis_nonce', 'nonce');
+
+ if (!current_user_can('manage_options')) {
+ wp_send_json_error([
+ 'message' => __('Insufficient permissions', 'bulk-installer-server')
+ ]);
+ }
+
+ $json_data = isset($_POST['import_data']) ? json_decode(stripslashes($_POST['import_data']), true) : [];
+
+ if (empty($json_data) || !is_array($json_data)) {
+ wp_send_json_error([
+ 'message' => __('Invalid JSON data', 'bulk-installer-server')
+ ]);
+ }
+
+ $imported = 0;
+ $existing_collections = bis_get_collections();
+
+ // Check if we have a "collections" key (full format) or direct collection data
+ if (isset($json_data['collections']) && is_array($json_data['collections'])) {
+ foreach ($json_data['collections'] as $imported_slug => $collection) {
+ if (isset($existing_collections[$imported_slug])) {
+ // 已存在同名集合,生成新 slug
+ $result = bis_save_collection($collection);
+ } else {
+ // 使用导入文件中的 slug
+ $result = bis_save_collection($collection, $imported_slug);
+ }
+
+ if ($result === true) {
+ $imported++;
+ }
+ }
+ } else {
+ // Assume it's a single collection
+ $result = bis_save_collection($json_data);
+
+ if ($result === true) {
+ $imported++;
+ }
+ }
+
+ if ($imported > 0) {
+ wp_send_json_success([
+ 'message' => sprintf(_n('%d collection imported successfully', '%d collections imported successfully', $imported, 'bulk-installer-server'), $imported),
+ 'collections' => bis_get_collections()
+ ]);
+ } else {
+ wp_send_json_error([
+ 'message' => __('No collections were imported', 'bulk-installer-server')
+ ]);
+ }
+}
+
+/**
+ * Plugin activation hook
+ */
+register_activation_hook(__FILE__, 'bis_activate');
+function bis_activate() {
+ if (version_compare(PHP_VERSION, '7.4', '<')) {
+ deactivate_plugins(plugin_basename(__FILE__));
+ wp_die(__('This plugin requires PHP 7.4 or higher.', 'bulk-installer-server'));
+ }
+
+ // Create default collection if none exist
+ $collections = bis_get_collections();
+
+ if (empty($collections)) {
+ $default_collection = [
+ 'name' => __('Business Website', 'bulk-installer-server'),
+ 'description' => __('Essential plugins for a professional business website.', 'bulk-installer-server'),
+ 'icon' => 'dashicons-building',
+ 'category' => 'business',
+ 'level' => 'beginner',
+ 'author' => get_bloginfo('name'),
+ 'plugins' => [
+ 'repository' => [
+ [
+ 'id' => 1,
+ 'slug' => 'wordpress-seo',
+ 'name' => 'Yoast SEO',
+ 'description' => __('The leading SEO plugin for WordPress', 'bulk-installer-server'),
+ 'required' => true
+ ],
+ [
+ 'id' => 2,
+ 'slug' => 'contact-form-7',
+ 'name' => 'Contact Form 7',
+ 'description' => __('Simple but flexible contact form plugin', 'bulk-installer-server'),
+ 'required' => true
+ ]
+ ],
+ 'wenpai' => [],
+ 'url' => []
+ ],
+ 'themes' => [
+ 'repository' => [
+ [
+ 'id' => 3,
+ 'slug' => 'astra',
+ 'name' => 'Astra',
+ 'description' => __('Fast, lightweight theme for business websites', 'bulk-installer-server'),
+ 'required' => false
+ ]
+ ],
+ 'wenpai' => [],
+ 'url' => []
+ ]
+ ];
+
+ bis_save_collection($default_collection, 'business');
+ }
+
+ // Create required directories
+ $upload_dir = wp_upload_dir();
+ $export_dir = $upload_dir['basedir'] . '/bis-exports';
+
+ if (!file_exists($export_dir)) {
+ wp_mkdir_p($export_dir);
+ }
+
+ // Create .htaccess file to protect directory
+ $htaccess_file = $export_dir . '/.htaccess';
+ if (!file_exists($htaccess_file)) {
+ $htaccess_content = "Options -Indexes\n";
+ $htaccess_content .= "\n";
+ $htaccess_content .= "Header set Access-Control-Allow-Origin \"*\"\n";
+ $htaccess_content .= "Header set Content-Type \"application/json\"\n";
+ $htaccess_content .= "\n";
+
+ file_put_contents($htaccess_file, $htaccess_content);
+ }
+}
+
+/**
+ * Get collection JSON URL
+ *
+ * @param string $slug Collection slug
+ * @return string Collection JSON URL
+ */
+function bis_get_collection_json_url($slug) {
+ return rest_url("bulk-installer-server/v1/collection/" . urlencode($slug));
+}
diff --git a/templates/admin-page.php b/templates/admin-page.php
new file mode 100644
index 0000000..096ed5f
--- /dev/null
+++ b/templates/admin-page.php
@@ -0,0 +1,514 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+