bytefun
Version:
一个打通了原型设计、UI设计与代码转换、跨平台原生代码开发等的平台
2,821 lines • 96 kB
HTML
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>32号旅游APP 后端API管理系统</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--bg-primary: #1B1B1B;
--bg-secondary: #232323;
--bg-tertiary: #2C2C2C;
--bg-four: #323232;
--text-primary: #FFFFFF;
--text-secondary: #8E8E93;
--border-color: #2C2C2C;
--border-hover-color: #4D4D4D;
--input-bg: #232323;
--success-color: #34C759;
--warning-color: #FF9500;
--error-color: #67211d;
--error-hover-color: #8a2420;
--primary-color: #303d4b;
--primary-hover-color: #485b70;
}
[data-theme="light"] {
--bg-primary: #FDFDFD;
--bg-secondary: #F5F5F5;
--bg-tertiary: #E8E8E8;
--bg-four: #e4e4e4;
--bg-text: #E8E8E8;
--text-primary: #1C1C1E;
--text-secondary: #6D6D70;
--border-color: #D1D1D6;
--border-hover-color: #cdcdd2;
--input-bg: #FFFFFF;
}
body {
font-family: 'Poppins', 'Arial', sans-serif;
background-color: var(--bg-primary);
color: var(--text-primary);
line-height: 1.6;
transition: background-color 0.3s ease, color 0.3s ease;
}
.container {
margin: 0 auto;
padding: 12px;
max-width: 1400px;
}
.header {
text-align: center;
margin-bottom: 24px;
padding: 20px;
background: linear-gradient(135deg, #007AFF, #5856D6);
color: white;
border-radius: 12px;
position: relative;
}
/* 悬浮模块列表容器 */
.floating-modules-container {
position: fixed;
top: 20px;
left: 20px;
right: 120px;
z-index: 100;
}
/* 右侧按钮组 */
.right-button-group {
position: fixed;
top: 20px;
right: 20px;
display: flex;
flex-direction: column;
gap: 12px;
z-index: 101;
}
.right-btn {
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 3px 6px;
cursor: pointer;
font-size: 12px;
color: var(--text-primary);
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 6px;
min-width: 90px;
justify-content: center;
}
.right-btn:hover {
background: var(--bg-tertiary);
border-color: var(--primary-color);
}
.right-btn.add-module {
background: var(--primary-color);
color: white;
}
.right-btn.add-module:hover {
background: var(--primary-hover-color);
}
.right-btn.save {
background: var(--primary-color);
color: white;
}
.right-btn.save:hover {
background: var(--primary-hover-color);
}
/* 右下角悬浮添加API按钮 */
.floating-add-api-btn {
position: fixed;
bottom: 30px;
right: 30px;
width: 40px;
height: 40px;
background: var(--primary-color);
color: white;
border: none;
border-radius: 50%;
cursor: pointer;
font-size: 18px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
z-index: 1000;
}
.floating-add-api-btn:hover {
background: #0056CC;
transform: scale(1.1);
box-shadow: 0 6px 25px rgba(0, 122, 255, 0.4);
}
.floating-add-api-btn:active {
transform: scale(0.95);
}
.floating-modules-list {
display: flex;
gap: 12px;
overflow-x: auto;
background-color: var(--bg-primary);
padding: 4px 6px;
border-radius: 10px;
scrollbar-width: thin;
scrollbar-color: var(--border-color) transparent;
}
.floating-modules-list::-webkit-scrollbar {
height: 6px;
}
.floating-modules-list::-webkit-scrollbar-track {
background: transparent;
}
.floating-modules-list::-webkit-scrollbar-thumb {
background: var(--border-color);
border-radius: 3px;
}
.floating-modules-list::-webkit-scrollbar-thumb:hover {
background: var(--text-secondary);
}
.floating-module-item {
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 8px;
padding-right: 5px;
padding-left: 8px;
padding-top: 4px;
padding-bottom: 4px;
cursor: pointer;
transition: all 0.3s ease;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: space-between;
}
.floating-module-item:hover {
background: var(--bg-tertiary);
border-color: var(--primary-color);
}
.floating-module-item.active {
background: var(--primary-color);
border-color: var(--primary-color);
color: white;
}
.floating-module-info {
flex: 1;
display: flex;
align-items: center;
gap: 8px;
}
.floating-module-name {
font-size: 13px;
font-weight: 500;
color: inherit;
}
.floating-api-count {
background: rgba(255, 255, 255, 0.2);
color: inherit;
padding: 2px 6px;
border-radius: 10px;
font-size: 10px;
font-weight: 600;
}
.floating-module-item.active .floating-api-count {
background: rgba(255, 255, 255, 0.3);
}
.floating-module-actions {
display: flex;
}
.floating-action-btn {
background: transparent;
border: none;
color: var(--text-secondary);
cursor: pointer;
padding: 4px;
border-radius: 4px;
font-size: 12px;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
}
.floating-action-btn:hover {
background: rgba(255, 255, 255, 0.1);
color: var(--text-primary);
}
.floating-module-item.active .floating-action-btn {
color: rgba(255, 255, 255, 0.8);
}
.floating-module-item.active .floating-action-btn:hover {
color: white;
background: rgba(255, 255, 255, 0.2);
}
.floating-action-btn.delete:hover {
color: var(--error-color);
background: rgba(255, 59, 48, 0.1);
}
.header h1 {
font-size: 24px;
font-weight: 700;
margin-bottom: 8px;
}
.header p {
font-size: 16px;
opacity: 0.9;
}
.header-controls {
position: absolute;
top: 20px;
right: 20px;
display: flex;
gap: 12px;
}
.header-btn {
background: rgba(255, 255, 255, 0.2);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 8px;
padding: 8px 16px;
cursor: pointer;
font-size: 14px;
color: white;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 6px;
}
.header-btn:hover {
background: rgba(255, 255, 255, 0.3);
}
.top-module-card {
background: var(--bg-secondary);
border-radius: 12px;
padding: 20px;
margin-bottom: 20px;
border: 1px solid var(--border-color);
margin-top: 20px;
}
.module-section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.module-section-title {
font-size: 16px;
font-weight: 600;
color: var(--text-primary);
}
.module-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 16px;
}
.module-grid-item {
background: var(--bg-tertiary);
border: 1px solid var(--border-color);
border-radius: 8px;
padding-left: 16px;
padding-top: 16px;
padding-bottom: 30px;
padding-right: 10px;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
}
.module-grid-item:hover {
background-color: var(--bg-four);
}
.module-grid-item.active {
border-color: var(--primary-color);
background: var(--bg-primary);
box-shadow: 0 4px 12px rgba(0, 122, 255, 0.15);
}
.add-module-btn {
background: var(--primary-color);
color: white;
border: none;
padding: 8px;
border-radius: 6px;
cursor: pointer;
font-size: 12px;
display: flex;
align-items: center;
gap: 4px;
transition: all 0.3s ease;
}
.add-module-btn:hover {
background: var(--primary-hover-color);
}
.module-grid-info {
flex: 1;
}
.module-grid-name {
font-size: 14px;
font-weight: 600;
color: var(--text-primary);
margin-bottom: 6px;
line-height: 1.3;
}
.edit-btn {
position: absolute;
bottom: 8px;
right: 60px;
}
.delete-btn {
position: absolute;
bottom: 8px;
right: 8px;
}
.module-grid-action-btn {
background: transparent;
border: 1px solid var(--border-color);
color: var(--text-secondary);
cursor: pointer;
padding: 2px 6px;
border-radius: 3px;
font-size: 10px;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 2px;
}
.module-grid-action-btn:hover {
color: var(--text-primary);
border: 1px solid transparent;
}
.delete-btn:hover {
color: var(--error-color);
border: 1px solid transparent;
}
.api-count {
background: var(--primary-color);
color: white;
padding: 2px 8px;
border-radius: 12px;
font-size: 11px;
margin-left: 3px;
}
.content-area {
background: var(--bg-secondary);
border-radius: 8px;
padding: 16px;
border: 1px solid var(--border-color);
overflow-y: auto;
min-width: 0;
}
.content-header {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 16px;
border-bottom: 1px solid var(--border-color);
}
.content-title {
font-size: 15px;
font-weight: 600;
color: var(--text-primary);
}
.add-api-btn {
background: var(--primary-color);
color: white;
border: none;
padding: 8px;
border-radius: 8px;
cursor: pointer;
font-size: 12px;
display: flex;
align-items: center;
gap: 6px;
transition: all 0.3s ease;
}
.add-api-btn:hover {
background: var(--primary-hover-color);
}
.api-list {
display: grid;
gap: 16px;
margin-top: 50px;
}
.api-item {
background: var(--bg-tertiary);
border: 1px solid var(--border-color);
border-radius: 6px;
padding: 16px;
transition: all 0.3s ease;
}
.api-item:hover {
border-color: var(--primary-color);
}
.api-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
max-width: 600px;
}
.api-basic-info {
flex: 1;
}
.api-name-row {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 6px;
}
.api-name-input {
font-size: 14px;
font-weight: 600;
color: var(--text-primary);
line-height: 1.3;
background: transparent;
border: none;
outline: none;
padding: 3px 6px;
width: auto;
min-width: 100px;
max-width: 300px;
}
.api-name-input:focus {
background: var(--input-bg);
border: 1px solid var(--primary-color);
border-radius: 4px;
}
.api-delete-btn {
background: transparent;
border: 1px solid var(--border-color);
color: var(--text-secondary);
cursor: pointer;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 4px;
flex-shrink: 0;
}
.api-delete-btn:hover {
color: var(--error-color);
border-color: var(--error-color);
background: rgba(255, 59, 48, 0.1);
}
.api-path {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.method-select {
background: var(--input-bg);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 4px 8px;
color: var(--text-primary);
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
}
.api-path-input {
background: transparent;
border: none;
outline: none;
color: var(--text-secondary);
font-family: 'Monaco', monospace;
font-size: 14px;
padding: 2px 4px;
min-width: 300px;
}
.api-path-input:focus {
background: var(--input-bg);
border: 1px solid var(--primary-color);
border-radius: 4px;
}
.auth-checkbox {
display: flex;
align-items: center;
gap: 4px;
font-size: 12px;
color: var(--text-secondary);
}
.auth-checkbox input {
width: 14px;
height: 14px;
}
.method-tag {
padding: 3px 6px;
border-radius: 4px;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
}
/* 自定义复选框样式 */
.custom-checkbox {
display: inline-flex;
align-items: center;
gap: 6px;
cursor: pointer;
user-select: none;
}
.custom-checkbox input[type="checkbox"] {
display: none;
}
.custom-checkbox .checkbox-icon {
width: 16px;
height: 16px;
border: 1px solid var(--border-color);
border-radius: 3px;
display: flex;
align-items: center;
justify-content: center;
background: var(--input-bg);
transition: all 0.3s ease;
font-size: 10px;
color: transparent;
}
.custom-checkbox input[type="checkbox"]:checked+.checkbox-icon {
background: var(--primary-color);
border-color: var(--primary-color);
color: white;
}
.custom-checkbox:hover .checkbox-icon {
border-color: var(--primary-color);
}
/* 自定义下拉选择框样式 */
.custom-select {
position: relative;
display: inline-block;
}
.custom-select select {
appearance: none;
-webkit-appearance: none;
-moz-appearance: none;
background: var(--input-bg);
border: 1px solid var(--border-color);
border-radius: 6px;
padding: 8px 30px 8px 12px;
color: var(--text-primary);
font-size: 12px;
cursor: pointer;
width: 100%;
transition: all 0.3s ease;
}
/* 自定义下拉选项样式 */
.custom-select select option {
background: var(--bg-secondary);
color: var(--text-primary);
padding: 8px 12px;
border: none;
font-size: 12px;
}
.custom-select select option:hover {
background: var(--bg-tertiary);
color: var(--text-primary);
}
.custom-select select option:checked,
.custom-select select option:selected {
background: var(--primary-color);
color: white;
}
/* Firefox 特殊处理 */
@-moz-document url-prefix() {
.custom-select select {
background-image: none;
}
.custom-select select option {
background-color: var(--bg-secondary);
color: var(--text-primary);
}
.custom-select select option:hover {
background-color: var(--bg-tertiary) !important;
}
.custom-select select option:checked {
background-color: var(--primary-color) !important;
color: white !important;
}
}
/* WebKit 浏览器特殊处理 */
@media screen and (-webkit-min-device-pixel-ratio:0) {
.custom-select select {
background-image: none;
}
.custom-select select option {
background: var(--bg-secondary);
color: var(--text-primary);
}
}
.method-select {
min-width: 80px;
max-width: 100px;
}
.param-select {
min-width: 90px;
max-width: 120px;
}
.custom-select select:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 2px rgba(0, 122, 255, 0.1);
}
.custom-select::after {
content: '\f107';
font-family: 'Font Awesome 6 Free';
font-weight: 900;
position: absolute;
right: 8px;
top: 50%;
transform: translateY(-50%);
color: var(--text-secondary);
pointer-events: none;
transition: transform 0.3s ease;
z-index: 1;
}
.custom-select:hover::after {
color: var(--text-primary);
}
.custom-select:focus-within::after {
transform: translateY(-50%) rotate(180deg);
color: var(--primary-color);
}
/* 自定义下拉菜单样式 */
.custom-dropdown {
position: relative;
display: inline-block;
}
.dropdown-button {
background: var(--input-bg);
border: 1px solid var(--border-color);
border-radius: 6px;
padding: 8px 30px 8px 12px;
color: var(--text-primary);
font-size: 12px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: space-between;
min-width: 80px;
transition: all 0.3s ease;
}
.dropdown-button:hover {
border-color: var(--primary-color);
}
.dropdown-button.active {
border-color: var(--primary-color);
box-shadow: 0 0 0 2px rgba(0, 122, 255, 0.1);
}
.dropdown-arrow {
margin-left: 8px;
transition: transform 0.3s ease;
}
.dropdown-button.active .dropdown-arrow {
transform: rotate(180deg);
}
.dropdown-menu {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 6px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
z-index: 1000;
opacity: 0;
visibility: hidden;
transform: translateY(-10px);
transition: all 0.3s ease;
max-height: 200px;
overflow-y: auto;
}
.dropdown-menu.show {
opacity: 1;
visibility: visible;
transform: translateY(0);
}
.dropdown-item {
padding: 8px 12px;
cursor: pointer;
transition: background-color 0.2s ease;
font-size: 12px;
color: var(--text-primary);
}
.dropdown-item:hover {
background: var(--bg-tertiary);
}
.dropdown-item.selected {
background: var(--primary-color);
color: white;
}
/* 自定义确认对话框 */
.confirm-dialog-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 2000;
opacity: 0;
visibility: hidden;
transition: opacity 0.3s ease, visibility 0.3s ease;
}
.confirm-dialog-overlay.show {
opacity: 1;
visibility: visible;
}
.confirm-dialog {
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 16px;
padding: 24px;
min-width: 400px;
max-width: 500px;
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.4);
transform: scale(0.9) translateY(-20px);
transition: transform 0.3s ease;
}
.confirm-dialog-overlay.show .confirm-dialog {
transform: scale(1) translateY(0);
}
.confirm-dialog-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 16px;
}
.confirm-dialog-icon {
width: 48px;
height: 48px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
flex-shrink: 0;
}
.confirm-dialog-icon.warning {
background: rgba(255, 149, 0, 0.1);
color: var(--warning-color);
}
.confirm-dialog-icon.danger {
background: rgba(255, 59, 48, 0.1);
color: var(--error-color);
}
.confirm-dialog-icon.info {
background: rgba(0, 122, 255, 0.1);
color: var(--primary-color);
}
.confirm-dialog-title {
font-size: 18px;
font-weight: 600;
color: var(--text-primary);
margin: 0;
}
.confirm-dialog-message {
color: var(--text-secondary);
font-size: 14px;
line-height: 1.5;
margin-bottom: 24px;
}
.confirm-dialog-actions {
display: flex;
gap: 12px;
justify-content: flex-end;
}
.confirm-dialog-btn {
padding: 10px 20px;
border: none;
border-radius: 8px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
min-width: 80px;
}
.confirm-dialog-btn.cancel {
background: var(--bg-tertiary);
color: var(--text-primary);
border: 1px solid var(--border-color);
}
.confirm-dialog-btn.cancel:hover {
background: var(--bg-primary);
}
.confirm-dialog-btn.confirm {
background: var(--error-color);
color: white;
}
.confirm-dialog-btn.confirm:hover {
background: #E5342B;
}
.confirm-dialog-btn.primary {
background: var(--primary-color);
color: white;
}
.confirm-dialog-btn.primary:hover {
background: #0056CC;
}
.method-get {
background: #E3F2FD;
color: #1976D2;
}
.method-post {
background: #E8F5E8;
color: #388E3C;
}
.method-put {
background: #FFF3E0;
color: #F57C00;
}
.method-delete {
background: #FFEBEE;
color: #D32F2F;
}
.api-path-text {
font-family: 'Monaco', monospace;
font-size: 14px;
color: var(--text-secondary);
}
.api-actions {
display: flex;
gap: 8px;
}
.api-action-btn {
background: transparent;
border: 1px solid var(--border-color);
color: var(--text-secondary);
cursor: pointer;
padding: 6px 12px;
border-radius: 6px;
font-size: 12px;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 4px;
}
.api-action-btn:hover {
color: var(--text-primary);
border: 1px solid transparent;
}
.api-action-btn.delete:hover {
color: var(--error-color);
border: 1px solid transparent;
}
.auth-badge {
padding: 2px 6px;
border-radius: 4px;
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
}
.auth-required {
background: #FFEBEE;
color: #D32F2F;
}
.auth-not-required {
background: #E8F5E8;
color: #388E3C;
}
.api-details {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
margin-top: 12px;
}
.detail-section {
background: var(--bg-primary);
border-radius: 4px;
padding: 12px;
}
.response-textarea {
width: 100%;
min-height: auto;
background: var(--bg-primary);
border: none;
border-radius: 0;
padding: 12px;
color: var(--text-primary);
font-family: 'Monaco', 'Consolas', monospace;
font-size: 12px;
line-height: 1.4;
resize: none;
overflow: hidden;
transition: none;
}
.response-textarea:focus {
outline: none;
}
.detail-header {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 8px;
}
.detail-title {
font-size: 13px;
font-weight: 600;
color: var(--text-primary);
}
.add-param-btn-inline {
background: var(--primary-color);
color: white;
border: none;
padding: 4px 8px;
border-radius: 4px;
cursor: pointer;
font-size: 10px;
display: flex;
align-items: center;
gap: 3px;
transition: all 0.3s ease;
}
.add-param-btn-inline:hover {
background: var(--primary-hover-color);
}
.param-edit-item {
display: grid;
grid-template-columns: 1fr 100px 80px 1fr 30px;
gap: 8px;
padding-bottom: 8px;
align-items: center;
}
.param-edit-item:last-child {
border-bottom: none;
}
.param-input {
background: transparent;
border: none;
outline: none;
color: var(--text-primary);
font-size: 13px;
padding: 4px;
}
.param-input:focus {
background: var(--input-bg);
border: 1px solid var(--primary-color);
border-radius: 4px;
}
.param-select {
background: var(--input-bg);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 4px;
color: var(--text-primary);
font-size: 12px;
}
.required-checkbox {
display: flex;
align-items: center;
gap: 4px;
font-size: 11px;
color: var(--text-secondary);
}
.required-checkbox input {
width: 12px;
height: 12px;
}
.param-desc-input {
background: transparent;
border: none;
outline: none;
color: var(--text-secondary);
font-size: 12px;
padding: 4px;
}
.param-desc-input:focus {
background: var(--input-bg);
border: 1px solid var(--primary-color);
border-radius: 4px;
}
.remove-param-btn {
background: var(--error-color);
color: white;
border: none;
border-radius: 4px;
padding: 4px;
cursor: pointer;
font-size: 10px;
display: flex;
align-items: center;
justify-content: center;
}
.remove-param-btn:hover {
background: var(--error-hover-color);
}
.param-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
border-bottom: 1px solid var(--border-color);
}
.param-item:last-child {
border-bottom: none;
}
.param-name {
font-family: 'Monaco', monospace;
font-size: 13px;
color: var(--text-primary);
}
.param-type {
font-size: 12px;
color: var(--text-secondary);
}
.param-required {
background: var(--error-color);
color: white;
padding: 2px 6px;
border-radius: 4px;
font-size: 10px;
}
.empty-state {
text-align: center;
padding: 60px 20px;
color: var(--text-secondary);
}
.empty-state i {
font-size: 48px;
margin-bottom: 16px;
opacity: 0.5;
}
.empty-state h3 {
font-size: 18px;
margin-bottom: 8px;
}
.empty-state p {
font-size: 14px;
}
/* 模态框样式 */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
opacity: 0;
visibility: hidden;
transition: opacity 0.3s ease, visibility 0.3s ease;
}
.modal-overlay.show {
opacity: 1;
visibility: visible;
}
.modal {
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 24px;
min-width: 500px;
max-width: 800px;
max-height: 80vh;
overflow-y: auto;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
transform: scale(0.9);
transition: transform 0.3s ease;
}
.modal-overlay.show .modal {
transform: scale(1);
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 16px;
border-bottom: 1px solid var(--border-color);
}
.modal-title {
font-size: 20px;
font-weight: 600;
color: var(--text-primary);
}
.modal-close {
background: transparent;
border: none;
color: var(--text-secondary);
cursor: pointer;
font-size: 20px;
padding: 4px;
}
.modal-close:hover {
color: var(--text-primary);
}
.form-group {
margin-bottom: 20px;
}
.form-label {
display: block;
font-size: 14px;
font-weight: 500;
color: var(--text-primary);
margin-bottom: 8px;
}
.form-input {
width: 100%;
padding: 12px;
border: 1px solid var(--border-color);
border-radius: 6px;
background: var(--input-bg);
color: var(--text-primary);
font-size: 14px;
transition: border-color 0.3s ease;
}
.form-input:focus {
outline: none;
border-color: var(--primary-color);
}
.form-textarea {
min-height: 80px;
resize: vertical;
}
.form-select {
width: 100%;
padding: 12px;
border: 1px solid var(--border-color);
border-radius: 6px;
background: var(--input-bg);
color: var(--text-primary);
font-size: 14px;
cursor: pointer;
}
.form-checkbox {
display: flex;
align-items: center;
gap: 8px;
}
.form-checkbox input {
width: 16px;
height: 16px;
}
.modal-actions {
display: flex;
gap: 12px;
justify-content: flex-end;
margin-top: 24px;
padding-top: 16px;
border-top: 1px solid var(--border-color);
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
}
.btn-primary {
background: var(--primary-color);
color: white;
}
.btn-primary:hover {
background: #0056CC;
}
.btn-secondary {
background: var(--bg-tertiary);
color: var(--text-primary);
border: 1px solid var(--border-color);
}
.btn-secondary:hover {
background: var(--bg-primary);
}
.params-list {
border: 1px solid var(--border-color);
border-radius: 6px;
background: var(--bg-primary);
}
.param-row {
display: grid;
grid-template-columns: 1fr 100px 80px 60px 40px;
gap: 12px;
padding: 12px;
border-bottom: 1px solid var(--border-color);
align-items: center;
}
.param-row:last-child {
border-bottom: none;
}
.param-row.header {
background: var(--bg-tertiary);
font-weight: 600;
font-size: 12px;
color: var(--text-secondary);
}
.param-input {
padding: 6px 8px;
border: 1px solid var(--border-color);
border-radius: 4px;
background: var(--input-bg);
color: var(--text-primary);
font-size: 12px;
}
.param-select {
padding: 6px 8px;
border: 1px solid var(--border-color);
border-radius: 4px;
background: var(--input-bg);
color: var(--text-primary);
font-size: 12px;
}
.remove-param-btn {
background: var(--error-color);
color: white;
border: none;
border-radius: 4px;
padding: 4px;
cursor: pointer;
font-size: 12px;
}
.add-param-btn {
background: var(--success-color);
color: white;
border: none;
border-radius: 6px;
padding: 8px 12px;
cursor: pointer;
font-size: 12px;
margin-top: 8px;
display: flex;
align-items: center;
gap: 4px;
}
@media (max-width: 1024px) {
.module-grid {
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 12px;
}
.content-area {
padding: 12px;
}
.api-item {
padding: 12px;
}
.api-details {
grid-template-columns: 1fr;
gap: 8px;
}
}
@media (max-width: 768px) {
.top-controls {
top: 10px;
right: 10px;
gap: 8px;
}
.control-btn {
font-size: 12px;
padding: 6px 12px;
}
.module-grid {
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 12px;
}
.content-area {
padding: 12px;
border-radius: 6px;
}
}
@media (max-width: 480px) {
.container {
padding: 8px;
}
.top-controls {
top: 8px;
right: 8px;
gap: 6px;
}
.control-btn {
font-size: 11px;
padding: 4px 8px;
}
.top-module-card {
padding: 12px;
margin-bottom: 16px;
border-radius: 8px;
margin-top: 50px;
}
.module-section-title {
font-size: 16px;
}
.module-grid {
grid-template-columns: 1fr;
gap: 8px;
}
.module-grid-item {
padding: 12px;
}
.content-header {
padding-bottom: 8px;
}
.content-title {
font-size: 16px;
}
.add-api-btn {
padding: 8px 12px;
font-size: 12px;
}
}
</style>
</head>
<body>
<div class="container">
<!-- 悬浮业务模块列表 -->
<div class="floating-modules-container">
<div class="floating-modules-list" id="floatingModulesList">
<!-- 动态生成模块列表 -->
</div>
</div>
<!-- 右侧按钮组 -->
<div class="right-button-group">
<button class="right-btn add-module" onclick="showAddModuleModal()">
<i class="fas fa-plus"></i>
<span>添加模块</span>
</button>
<button class="right-btn save" onclick="saveConfig()">
<i class="fas fa-save"></i>
<span>保存</span>
</button>
<button class="right-btn theme-toggle" onclick="toggleTheme()">
<i class="fas fa-sun"></i>
<span id="theme-text">亮色模式</span>
</button>
</div>
<!-- 主内容区 - API列表 -->
<div class="content-area">
<div class="content-header" style="display: none;">
<h2 class="content-title" id="contentTitle">选择一个模块查看API接口</h2>
<button class="add-api-btn" id="addApiBtn" onclick="showAddApiModal()" style="display: none;">
<i class="fas fa-plus"></i>
添加API接口
</button>
</div>
<div class="api-list" id="apiList">
<div class="empty-state">
<i class="fas fa-cube"></i>
<h3>请选择一个业务模块</h3>
<p>从上方选择一个业务模块来查看和管理其API接口</p>
</div>
</div>
</div>
<!-- 右下角悬浮添加API按钮 -->
<button class="floating-add-api-btn" id="floatingAddApiBtn" onclick="showAddApiModal()" style="display: none;"
title="添加API接口">
<i class="fas fa-plus"></i>
</button>
</div>
<!-- 添加/编辑模块模态框 -->
<div class="modal-overlay" id="moduleModal">
<div class="modal">
<div class="modal-header">
<h3 class="modal-title" id="moduleModalTitle">添加业务模块</h3>
<button class="modal-close" onclick="hideModuleModal()">
<i class="fas fa-times"></i>
</button>
</div>
<form id="moduleForm">
<div class="form-group">
<label class="form-label">模块英文名称</label>
<input type="text" class="form-input" id="moduleEnName" placeholder="例如:accountBizModule" required>
</div>
<div class="form-group">
<label class="form-label">模块中文名称</label>
<input type="text" class="form-input" id="moduleCnName" placeholder="例如:账号系统业务模块" required>
</div>
<div class="form-group">
<label class="form-label">模块描述</label>
<textarea class="form-input form-textarea" id="moduleDescription"
placeholder="描述该模块的主要功能和业务范围"></textarea>
</div>
<div class="modal-actions">
<button type="button" class="btn btn-secondary" onclick="hideModuleModal()">取消</button>
<button type="submit" class="btn btn-primary">保存</button>
</div>
</form>
</div>
</div>
<!-- 添加/编辑API模态框 -->
<div class="modal-overlay" id="apiModal">
<div class="modal">
<div class="modal-header">
<h3 class="modal-title" id="apiModalTitle">添加API接口</h3>
<button class="modal-close" onclick="hideApiModal()">
<i class="fas fa-times"></i>
</button>
</div>
<form id="apiForm">
<div class="form-group">
<label class="form-label">API英文名称</label>
<input type="text" class="form-input" id="apiEnName" placeholder="例如:sendSmsCode" required>
</div>
<div class="form-group">
<label class="form-label">API中文名称</label>
<input type="text" class="form-input" id="apiName" placeholder="例如:发送短信验证码" required>
</div>
<div class="form-group">
<label class="form-label">请求方法</label>
<div class="custom-select">
<select class="form-select" id="apiMethod" required>
<option value="GET">GET</option>
<option value="POST">POST</option>
<option value="PUT">PUT</option>
<option value="DELETE">DELETE</option>
</select>
</div>
</div>
</div>
<div class="form-group">
<label class="form-label">API路径</label>
<input type="text" class="form-input" id="apiPath" placeholder="例如:/client/biz/auth/send/code" required>
</div>
<div class="form-group">
<label class="custom-checkbox form-checkbox">
<input type="checkbox" id="apiAuth">
<span class="checkbox-icon"><i class="fas fa-check"></i></span>
<span class="form-label">需要身份验证</span>
</label>
</div>
<div class="form-group">
<label class="form-label">请求参数</label>
<div class="params-list" id="paramsList">
<div class="param-row header">
<div>参数名称</div>
<div>参数类型</div>
<div>是否必需</div>
<div>描述</div>
<div>操作</div>
</div>
</div>
<button type="button" class="add-param-btn" onclick="addParameterToModal()">
<i class="fas fa-plus"></i>
添加参数
</button>
</div>
<div class="form-group">
<label class="form-label">响应结构</label>
<textarea class="form-input form-textarea response-textarea" id="apiResponse" oninput="autoResize(this)"
placeholder="请输入响应结构的JSON格式,例如:\n{\n \" code\": 0,\n \"msg\": \"success\",\n \"data\":
{}\n}"></textarea>
</div>
<div class="modal-actions">
<button type="button" class="btn btn-secondary" onclick="hideApiModal()">取消</button>
<button type="submit" class="btn btn-primary">保存</button>
</div>
</form>
</div>
</div>
<!-- 自定义确认对话框 -->
<div class="confirm-dialog-overlay" id="confirmDialog">
<div class="confirm-dialog">
<div class="confirm-dialog-header">
<div class="confirm-dialog-icon" id="confirmIcon">
<i class="fas fa-exclamation-triangle"></i>
</div>
<h3 class="confirm-dialog-title" id="confirmTitle">确认操作</h3>
</div>
<div class="confirm-dialog-message" id="confirmMessage">
您确定要执行此操作吗?
</div>
<div class="confirm-dialog-actions">
<button class="confirm-dialog-btn cancel" onclick="hideConfirmDialog(false)">取消</button>
<button class="confirm-dialog-btn confirm" id="confirmBtn" onclick="hideConfirmDialog(true)">确认</button>
</div>
</div>
</div>
<script>
// 全局变量
let apiConfig = {
projectName: "32号旅游",
moduleList: []
};
let currentModuleIndex = -1;
let editingModuleIndex = -1;
let editingApiIndex = -1;
// 初始化
document.addEventListener('DOMContentLoaded', function () {
loadConfig();
renderModuleList();
// 默认选中第一个业务模块
if (apiConfig.moduleList && apiConfig.moduleList.length > 0) {
selectModule(0);
}
});
// 从JSON字符串导入数据
function importDataFromString(jsonString) {
try {
const importedData = JSON.parse(jsonString);
// 验证数据结构
if (validateApiConfigStructure(importedData)) {
apiConfig = importedData;
currentModuleIndex = -1;
renderModuleList();
renderApiList();
// 默认选中第一个业务模块
if (apiConfig.moduleList && apiConfig.moduleList.length > 0) {
selectModule(0);
}
console.log('后端API配置数据导入成功');
return true;
} else {
console.error('导入失败:JSON格式不正确');
return false;
}
} catch (error) {
console.error('导入失败:', error.message);
return false;
}
}
// 验证API配置数据结构
function validateApiConfigStructure(data) {
return data &&
typeof data.projectName === 'string' &&
Array.isArray(data.moduleList);
}
// 加载配置
async function loadConfig() {
try {
// 配置数据将通过VS Code扩展的postMessage传递过来
// 这里只是初始化默认配置,等待外部数据传入
console.log('🔧 [后端设计] 等待配置数据传入...');
apiConfig = {
"projectName": "后端工程",
"moduleList": [
{
"moduleEnName": "accountSystemBizModule",
"moduleCnName": "账号系统业务模块",
"description": "包括发送短信验证码、手机号登录、手机号注册、注销(退出)、账号密码登录业务服务",
"apiList": [
{
"apiEnName": "sendSmsCode",
"name": "发送短信验证码",
"method": "POST",
"path": "/client/biz/auth/send/code",
"auth": "false",
"headers": {
"Content-Type": "application/json"
},
"parameters": [
{
"name": "mobile",
"type": "string",
"required": "true",
"description": "手机号"
}
],
"responses": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"description": "编码 0表示成功,其他值表示失败"
},
"msg": {
"type": "string",
"description": "消息内容"
},
"data": {
"type": "string",
"description": "响应数据"
}
},
"description": "响应"
}
}
]
}
]
}
// 如果已经有配置数据,则渲染
if (apiConfig.moduleList && apiConfig.moduleList.length > 0) {
renderModuleList();
selectModule(0);
}
} catch (error) {
console.log('配置文件加载失败,使用默认配置');
// 即使配置加载失败,也尝试选中第一个模块(如果有的话)
if (apiConfig.moduleList && apiConfig.moduleList.length > 0) {
selectModule(0);
}
}
}
// 渲染悬浮模块列表
function renderModuleList() {
const modulesList = document.getElementById('floatingModulesList');
modulesList.innerHTML = '';
apiConfig.moduleList.forEach((module, index) => {
const moduleItem = document.createElement('div');
moduleItem.className = 'floating-module-item';
if (index === currentModuleIndex) {
moduleItem.classList.add('active');
}
moduleItem.innerHTML = `
<div class="floating-module-info" onclick="selectModule(${index})">
<span class="floating-module-name">${module.moduleCnName}</span>
<span class="floating-api-count">${module.apiList ? module.apiList.length : 0}</span>
</div>
<div class="floating-module-actions">
<button class="floating-action-btn" onclick="event.stopPropagation(); editModule(${index})" title="编辑模块">
<i class="fas fa-edit"></i>
</button>
<button class="floating-action-btn delete" onclick="event.stopPropagation(); deleteModule(${index})" title="删除模块">
<i class="fas fa-trash"></i>
</button>
</div>
`;
modulesList.appendChild(moduleItem);
});
}
// 选择模块
function selectModule(index) {
currentModuleIndex = index;
renderModuleList();
renderApiList();
}
// 渲染API列表
function renderApiList() {
const apiList = document.getElementById('apiList');
const floatingAddApiBtn = document.getElementById('floatingAddApiBtn');
if (currentModuleIndex === -1 || !apiConfig.moduleList[currentModuleIndex]) {
floatingAddApiBtn.style.display = 'none';
apiList.innerHTML = `
<div class="empty-state">
<i class="fas fa-cube"></i>
<h3>请选择一个业务模块</h3>
<p>从上方选择一个业务模块来查看和管理其API接口</p>
</div>
`;
return;
}
const currentModule = apiConfig.moduleList[currentModuleIndex];
floatingAddApiBtn.style.display = 'flex';
if (!currentModule.apiList || currentModule.apiList.length === 0) {
apiList.innerHTML = `
<div class="empty-state">
<i class="fas fa-code"></i>
<h3>暂无API接口</h3>
<p>点击右下角的悬浮按钮来创建第一个接口</p>
</div>
`;
return;
}
apiList.innerHTML = '';
currentModule.apiList.forEach((api, index) => {
const apiItem = document.createElement('div');
apiItem.className = 'api-item';
apiItem.setAttribute('data-path', api.path || ''); // 添加data-path属性用于定位
const methodClass = `method-${api.method.toLowerCase()}`;
apiItem.innerHTML = `
<div class="api-header">
<div class="api-basic-info">
<div class="api-name-row">
<input class="api-name-input" value="${api.name}" onchange="updateApiField(${index}, 'name', this.value)" oninput="autoResizeInput(this)" placeholder="API名称">
<button class="api-delete-btn" onclick="deleteApi(${index})" title="删除接口">
<i class="fas fa-trash"></i>
</button>
</div>
<div class="api-path">
<div class="custom-select">
<select class="method-select" onchange="updateApiField(${index}, 'method', this.value)">
<option value="GET" ${api.method === 'GET' ? 'selected' : ''}>GET</option>
<option value="POST" ${api.method === 'POST' ? 'selected' : ''}>POST</option>
<option value="PUT" ${api.method === 'PUT' ? 'selected' : ''}>PUT</option>
<option value="DELETE" ${api.method === 'DELETE' ? 'selected' : ''}>DELETE</option>
</select>
</div>
<label class="custom-checkbox auth-checkbox">
<input type="checkbox" ${api.auth === 'true' || api.auth === true ? 'checked' : ''} onchange="updateApiField(${index}, 'auth', this.checked)">
<span class="checkbox-icon"><i class="fas fa-check"></i></span>
<span>需要认证</span>
</label>
<input class="api-path-input" value="${api.path}" onchange="updateApiField(${index}, 'path', this.value)" placeholder="/api/path">
</div>
</div>
</div>
<div class="api-details">
<div class="detail-section">
<div class="detail-header">
<div class="detail-title">请求参数</div>
<button class="add-param-btn-inline" onclick="addParameter(${index})">
<i class="fas fa-plus"></i>
参数
</button>
</div>
<div class="parameters-list" id="params-${index}">
${renderEditableParameters(api.parameters || [], index)}
</div>
</div>
<div class="detail-section">
<div class="detail-title">响应结构</div>
<textarea class="response-textarea" id="response-${index}" onchange="updateApiField(${index}, 'responses', this.value)" oninput="autoResize(this)" placeholder="请输入响应结构的JSON格式">${api.responses ? (typeof api.responses === 'string' ? api.responses : JSON.stringify(api.responses, null, 2)) : ''}</textarea>
</div>
</div>
`;
apiList.appendChild(apiItem);
});
// 初始化新渲染的textarea高度和输入框宽度
setTimeout(() => {
initializeTextareaHeights();
initializeInputWidths();
}, 50);
}
// 渲染可编辑参数列表
function renderEditableParameters(parameters, apiIndex) {
if (!parameters || parameters.length === 0) {
return '<div class="param-item"><span class="param-type">暂无参数</span></div>';
}
return parameters.map((param, paramIndex) => `
<div class="param-edit-item">
<input class="param-input" value="${param.name}" onchange="updateParameter(${apiIndex}, ${paramIndex}, 'name', this.value)" placeholder="参数名">
<div class="custom-select">
<select class="param-select" onchange="updateParameter(${apiIndex}, ${paramIndex}, 'type', this.value)">
<option value="string" ${param.type === 'string' ? 'selected' : ''}>string</option>
<option value="number" ${param.type === 'number' ? 'selected' : ''}>number</option>
<option value="boolean" ${param.type === 'boolean' ? 'selected' : ''}>boolean</option>
<option value="object" ${param.type === 'object' ? 'selected' : ''}>object</option>
<option value="array" ${param.type === 'array' ? 'selected' : ''}>array</option>
</select>
</div>
<label class="custom-checkbox required-checkbox">
<input type="checkbox" ${param.required === 'true' || param.required === true ? 'checked' : ''} onchange="updateParameter(${apiIndex}, ${paramIndex}, 'required', this.checked)">
<span class="checkbox-icon"><i class="fas fa-check"></i></span>
<span>必需</span>
</label>
<input class="param-desc-input" value="${param.description || ''}" onchange="updateParameter(${apiIndex}, ${paramIndex}, 'description', this.value)" placeholder="参数描述">
<button class="remove-param-btn" onclick="removeParameterByElement(this, ${apiIndex})">
<i class="fas fa-times"></i>
</button>
</div>
`).join('');
}
// 更新API字段
function updateApiField(apiIndex, field, value) {
if (apiConfig.moduleList[currentModuleIndex] && apiConfig.moduleList[currentModuleIndex].apiList[apiIndex]) {
if (field === 'responses') {
// 尝试解析JSON,如果失败则保存为字符串
try {
apiConfig.moduleList[currentModuleIndex].apiList[apiIndex][field] = JSON.parse(value);
} catch (e) {
apiConfig.moduleList[currentModuleIndex].apiList[apiIndex][field] = value;
}
} else {
apiConfig.moduleList[currentModuleIndex].apiList[apiIndex][field] = value;
}
}
}
// 更新参数
function updateParameter(apiIndex, paramIndex, field, value) {
if (apiConfig.moduleList[currentModuleIndex] &&
apiConfig.moduleList[currentModuleIndex].apiList[apiIndex] &&
apiConfig.moduleList[currentModuleIndex].apiList[apiIndex].parameters[paramIndex]) {
apiConfig.moduleList[currentModuleIndex].apiList[apiIndex].parameters[paramIndex][field] = value;
}
}
// 添加参数
function addParameter(apiIndex) {
if (!apiConfig.moduleList[currentModuleIndex].apiList[apiIndex].parameters) {
apiConfig.moduleList[currentModuleIndex].apiList[apiIndex].parameters = [];
}
apiConfig.moduleList[currentModuleIndex].apiList[apiIndex].parameters.push({
name: '',
type: 'string',
required: false,
description: ''
});
renderApiList();
}
// 通过DOM元素移除参数
function removeParameterByElement(button, apiIndex) {
// 找到参数容器
const paramItem = button.closest('.param-edit-item');
if (!paramItem) {
console.error('无法找到参数项容器');
return;
}
// 找到参数在列表中的索引
const paramsList = paramItem.parentElement;
const paramItems = paramsList.querySelectorAll('.param-edit-item');
let paramIndex = -1;
for (let i = 0; i < paramItems.length; i++) {
if (paramItems[i] === paramItem) {
paramIndex = i;
break;
}
}
if (paramIndex === -1) {
console.error('无法确定参数索引');
return;
}
console.log('删除参数:', apiIndex, paramIndex);
// 从数据中删除参数
if (apiConfig.moduleList[currentModuleIndex] &&
apiConfig.moduleList[currentModuleIndex].apiList[apiIndex] &&
apiConfig.moduleList[currentModuleIndex].apiList[apiIndex].parameters &&
apiConfig.moduleList[currentModuleIndex].apiList[apiIndex].parameters[paramIndex] !== undefined) {
apiConfig.moduleList[currentModuleIndex].apiList[apiIndex].parameters.splice(paramIndex, 1);
renderApiList();
} else {
console.error('删除参数失败,参数不存在:', {
currentModuleIndex,
apiIndex,
paramIndex,
hasModule: !!apiConfig.moduleList[currentModuleIndex],
hasApi: !!(apiConfig.moduleList[currentModuleIndex] && apiConfig.moduleList[currentModuleIndex].apiList[apiIndex]),
hasParams: !!(apiConfig.moduleList[currentModuleIndex] && apiConfig.moduleList[currentModuleIndex].apiList[apiIndex] && apiConfig.moduleList[currentModuleIndex].apiList[apiIndex].parameters)
});
}
}
// 移除参数(保留原函数以防其他地方调用)
function removeParameter(apiIndex, paramIndex) {
console.log('删除参数:', apiIndex, paramIndex);
if (apiConfig.moduleList[currentModuleIndex] &&
apiConfig.moduleList[currentModuleIndex].apiList[apiIndex] &&
apiConfig.moduleList[currentModuleIndex].apiList[apiIndex].parameters &&
apiConfig.moduleList[currentModuleIndex].apiList[apiIndex].parameters[paramIndex] !== undefined) {
apiConfig.moduleList[currentModuleIndex].apiList[apiIndex].parameters.splice(paramIndex, 1);
renderApiList();
} else {
console.error('删除参数失败,参数不存在:', {
currentModuleIndex,
apiIndex,
paramIndex,
hasModule: !!apiConfig.moduleList[currentModuleIndex],
hasApi: !!(apiConfig.moduleList[currentModuleIndex] && apiConfig.moduleList[currentModuleIndex].apiList[apiIndex]),
hasParams: !!(apiConfig.moduleList[currentModuleIndex] && apiConfig.moduleList[currentModuleIndex].apiList[apiIndex] && apiConfig.moduleList[currentModuleIndex].apiList[apiIndex].parameters)
});
}
}
// 显示添加模块模态框
function showAddModuleModal() {
editingModuleIndex = -1;
document.getElementById('moduleModalTitle').textContent = '添加业务模块';
document.getElementById('moduleForm').reset();
document.getElementById('moduleModal').classList.add('show');
}
// 编辑模块
function editModule(index) {
editingModuleIndex = index;
const module = apiConfig.moduleList[index];
document.getElementById('moduleModalTitle').textContent = '编辑业务模块';
document.getElementById('moduleEnName').value = module.moduleEnName || '';
document.getElementById('moduleCnName').value = module.moduleCnName || '';
document.getElementById('moduleDescription').value = module.description || '';
document.getElementById('moduleModal').classList.add('show');
}
// 隐藏模块模态框
function hideModuleModal() {
document.getElementById('moduleModal').classList.remove('show');
}
// 删除模块
function deleteModule(index) {
showConfirmDialog(
'删除模块',
'确定要删除这个模块吗?这将同时删除该模块下的所有API接口。',
'danger',
function () {
apiConfig.moduleList.splice(index, 1);
if (currentModuleIndex === index) {
currentModuleIndex = -1;
} else if (currentModuleIndex > index) {
currentModuleIndex--;
}
renderModuleList();
renderApiList();
}
);
}
// 添加新API
function showAddApiModal() {
if (currentModuleIndex === -1) {
showAlert('提示', '请先选择一个模块', 'info');
return;
}
const newApi = {
apiEnName: '',
name: '新API接口',
method: 'POST',
path: '/api/new',
auth: 'false',
headers: {
"Content-Type": "application/json"
},
parameters: [],
responses: {
type: "object",
properties: {
code: {
type: "integer",
description: "编码 0表示成功,其他值表示失败"
},
msg: {
type: "string",
description: "消息内容"
},
data: {
type: "object",
description: "响应数据"
}
},
description: "响应"
}
};
if (!apiConfig.moduleList[currentModuleIndex].apiList) {
apiConfig.moduleList[currentModuleIndex].apiList = [];
}
apiConfig.moduleList[currentModuleIndex].apiList.push(newApi);
renderModuleList();
renderApiList();
}
// 编辑API
function editApi(index) {
editingApiIndex = index;
const api = apiConfig.moduleList[currentModuleIndex].apiList[index];
document.getElementById('apiModalTitle').textContent = '编辑API接口';
document.getElementById('apiEnName').value = api.apiEnName || '';
document.getElementById('apiName').value = api.name || '';
document.getElementById('apiMethod').value = api.method || 'POST';
document.getElementById('apiPath').value = api.path || '';
document.getElementById('apiAuth').checked = api.auth === 'true' || api.auth === true;
document.getElementById('apiResponse').value = api.responses ? (typeof api.responses === 'string' ? api.responses : JSON.stringify(api.responses, null, 2)) : '';
// 渲染参数列表
const paramsList = document.getElementById('paramsList');
paramsList.innerHTML = `
<div class="param-row header">
<div>参数名称</div>
<div>参数类型</div>
<div>是否必需</div>
<div>描述</div>
<div>操作</div>
</div>
`;
if (api.parameters && api.parameters.length > 0) {
api.parameters.forEach(param => {
addParameterRow(param);
});
}
document.getElementById('apiModal').classList.add('show');
}
// 隐藏API模态框
function hideApiModal() {
document.getElementById('apiModal').classList.remove('show');
}
// 删除API
function deleteApi(index) {
showConfirmDialog(
'删除API接口',
'确定要删除这个API接口吗?',
'danger',
function () {
apiConfig.moduleList[currentModuleIndex].apiList.splice(index, 1);
renderModuleList();
renderApiList();
}
);
}
// 添加参数到模态框
function addParameterToModal() {
addParameterRow();
}
// 添加参数到API列表
function addParameter(apiIndex) {
if (!apiConfig.moduleList[currentModuleIndex].apiList[apiIndex].parameters) {
apiConfig.moduleList[currentModuleIndex].apiList[apiIndex].parameters = [];
}
apiConfig.moduleList[currentModuleIndex].apiList[apiIndex].parameters.push({
name: '',
type: 'string',
required: false,
description: ''
});
renderApiList();
}
// 添加参数行
function addParameterRow(param = null) {
const paramsList = document.getElementById('paramsList');
const paramRow = document.createElement('div');
paramRow.className = 'param-row';
paramRow.innerHTML = `
<input type="text" class="param-input" placeholder="参数名" value="${param?.name || ''}">
<div class="custom-select">
<select class="param-select">
<option value="string" ${param?.type === 'string' ? 'selected' : ''}>string</option>
<option value="number" ${param?.type === 'number' ? 'selected' : ''}>number</option>
<option value="boolean" ${param?.type === 'boolean' ? 'selected' : ''}>boolean</option>
<option value="object" ${param?.type === 'object' ? 'selected' : ''}>object</option>
<option value="array" ${param?.type === 'array' ? 'selected' : ''}>array</option>
</select>
</div>
<div class="custom-select">
<select class="param-select">
<option value="true" ${param?.required === 'true' || param?.required === true ? 'selected' : ''}>必需</option>
<option value="false" ${param?.required === 'false' || param?.required === false ? 'selected' : ''}>可选</option>
</select>
</div>
<input type="text" class="param-input" placeholder="参数描述" value="${param?.description || ''}">
<button type="button" class="remove-param-btn" onclick="removeParameterFromModal(this)">
<i class="fas fa-times"></i>
</button>
`;
paramsList.appendChild(paramRow);
}
// 移除参数
function removeParameter(button) {
let parent = button.parentElement;
while (parent && !parent.classList.contains('param-row') && !parent.classList.contains('param-edit-item')) {
parent = parent.parentElement;
}
if (parent) {
parent.remove();
}
}
// 移除模态框中的参数
function removeParameterFromModal(button) {
let parent = button.parentElement;
while (parent && !parent.classList.contains('param-row')) {
parent = parent.parentElement;
}
if (parent) {
parent.remove();
}
}
// 处理模块表单提交
document.getElementById('moduleForm').addEventListener('submit', function (e) {
e.preventDefault();
const moduleData = {
moduleEnName: document.getElementById('moduleEnName').value,
moduleCnName: document.getElementById('moduleCnName').value,
description: document.getElementById('moduleDescription').value,
apiList: []
};
if (editingModuleIndex === -1) {
// 添加新模块
apiConfig.moduleList.push(moduleData);
} else {
// 编辑现有模块
const existingModule = apiConfig.moduleList[editingModuleIndex];
moduleData.apiList = existingModule.apiList || [];
apiConfig.moduleList[editingModuleIndex] = moduleData;
}
renderModuleList();
hideModuleModal();
});
// 处理API表单提交
document.getElementById('apiForm').addEventListener('submit', function (e) {
e.preventDefault();
// 收集参数
const paramRows = document.querySelectorAll('#paramsList .param-row:not(.header)');
const parameters = [];
paramRows.forEach(row => {
const inputs = row.querySelectorAll('input, select');
if (inputs[0].value.trim()) {
parameters.push({
name: inputs[0].value.trim(),
type: inputs[1].value,
required: inputs[2].value,
description: inputs[3].value.trim()
});
}
});
// 处理响应结构
let responseData;
const responseText = document.getElementById('apiResponse').value.trim();
if (responseText) {
try {
responseData = JSON.parse(responseText);
} catch (e) {
responseData = responseText; // 如果不是有效JSON,保存为字符串
}
} else {
responseData = {
type: "object",
properties: {
code: {
type: "integer",
description: "编码 0表示成功,其他值表示失败"
},
msg: {
type: "string",
description: "消息内容"
},
data: {
type: "object",
description: "响应数据"
}
},
description: "响应"
};
}
const apiData = {
apiEnName: document.getElementById('apiEnName').value,
name: document.getElementById('apiName').value,
method: document.getElementById('apiMethod').value,
path: document.getElementById('apiPath').value,
auth: document.getElementById('apiAuth').checked ? 'true' : 'false',
headers: {
"Content-Type": "application/json"
},
parameters: parameters,
responses: responseData
};
let isNewApi = false;
let newApiIndex = -1;
if (editingApiIndex === -1) {
// 添加新API
if (!apiConfig.moduleList[currentModuleIndex].apiList) {
apiConfig.moduleList[currentModuleIndex].apiList = [];
}
apiConfig.moduleList[currentModuleIndex].apiList.push(apiData);
newApiIndex = apiConfig.moduleList[currentModuleIndex].apiList.length - 1;
isNewApi = true;
} else {
// 编辑现有API
apiConfig.moduleList[currentModuleIndex].apiList[editingApiIndex] = apiData;
newApiIndex = editingApiIndex;
}
renderModuleList();
renderApiList();
hideApiModal();
// 如果是新添加的API,滚动到新添加的接口卡片
if (isNewApi) {
setTimeout(() => {
scrollToNewApi(newApiIndex);
}, 100);
}
});
// 导入配置
function importConfig() {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.json';
input.onchange = function (e) {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function (e) {
try {
const config = JSON.parse(e.target.result);
apiConfig = config;
currentModuleIndex = -1;
renderModuleList();
renderApiList();
showAlert('导入成功', '配置导入成功!', 'info');
} catch (error) {
showAlert('导入失败', '配置文件格式错误!', 'danger');
}
};
reader.readAsText(file);
}
};
input.click();
}
// 导出配置
function exportConfig() {
const dataStr = JSON.stringify(apiConfig, null, 2);
const dataBlob = new Blob([dataStr], { type: 'application/json' });
const url = URL.createObjectURL(dataBlob);
const link = document.createElement('a');
link.href = url;
link.download = 'backend-design-config.json';
link.click();
URL.revokeObjectURL(url);
}
// 主题切换
function toggleTheme() {
const body = document.body;
const themeText = document.getElementById('theme-text');
const themeIcon = document.querySelector('.theme-toggle i');
if (body.hasAttribute('data-theme')) {
body.removeAttribute('data-theme');
themeText.textContent = '亮色模式';
themeIcon.className = 'fas fa-sun';
} else {
body.setAttribute('data-theme', 'light');
themeText.textContent = '暗色模式';
themeIcon.className = 'fas fa-moon';
}
}
// 自定义确认对话框
let confirmCallback = null;
function showConfirmDialog(title, message, type = 'warning', callback = null) {
const overlay = document.getElementById('confirmDialog');
const titleEl = document.getElementById('confirmTitle');
const messageEl = document.getElementById('confirmMessage');
const iconEl = document.getElementById('confirmIcon');
const confirmBtn = document.getElementById('confirmBtn');
titleEl.textContent = title;
messageEl.textContent = message;
confirmCallback = callback;
// 设置图标和样式
iconEl.className = `confirm-dialog-icon ${type}`;
if (type === 'danger') {
iconEl.innerHTML = '<i class="fas fa-exclamation-triangle"></i>';
confirmBtn.className = 'confirm-dialog-btn confirm';
confirmBtn.textContent = '删除';
} else if (type === 'warning') {
iconEl.innerHTML = '<i class="fas fa-exclamation-triangle"></i>';
confirmBtn.className = 'confirm-dialog-btn primary';
confirmBtn.textContent = '确认';
} else if (type === 'info') {
iconEl.innerHTML = '<i class="fas fa-info-circle"></i>';
confirmBtn.className = 'confirm-dialog-btn primary';
confirmBtn.textContent = '确认';
}
overlay.classList.add('show');
}
function hideConfirmDialog(confirmed) {
const overlay = document.getElementById('confirmDialog');
overlay.classList.remove('show');
if (confirmed && confirmCallback) {
confirmCallback();
}
confirmCallback = null;
}
function showAlert(title, message, type = 'info') {
showConfirmDialog(title, message, type, null);
// 隐藏取消按钮,只显示确认按钮
const cancelBtn = document.querySelector('.confirm-dialog-btn.cancel');
const confirmBtn = document.getElementById('confirmBtn');
cancelBtn.style.display = 'none';
confirmBtn.textContent = '确定';
confirmBtn.className = 'confirm-dialog-btn primary';
// 重新显示取消按钮(为下次使用准备)
setTimeout(() => {
cancelBtn.style.display = 'block';
}, 300);
}
// 自动调整textarea高度
function autoResize(textarea) {
textarea.style.height = 'auto';
textarea.style.height = textarea.scrollHeight + 'px';
}
// 自动调整输入框宽度
function autoResizeInput(input) {
// 创建一个临时的span元素来测量文字宽度
const tempSpan = document.createElement('span');
tempSpan.style.visibility = 'hidden';
tempSpan.style.position = 'absolute';
tempSpan.style.fontSize = window.getComputedStyle(input).fontSize;
tempSpan.style.fontFamily = window.getComputedStyle(input).fontFamily;
tempSpan.style.fontWeight = window.getComputedStyle(input).fontWeight;
tempSpan.textContent = input.value || input.placeholder;
document.body.appendChild(tempSpan);
const textWidth = tempSpan.offsetWidth;
document.body.removeChild(tempSpan);
// 设置输入框宽度,加上一些padding
const newWidth = Math.max(100, Math.min(300, textWidth + 20));
input.style.width = newWidth + 'px';
}
// 初始化所有response textarea的高度
function initializeTextareaHeights() {
const textareas = document.querySelectorAll('.response-textarea');
textareas.forEach(textarea => {
if (textarea.value.trim()) {
autoResize(textarea);
}
});
}
// 初始化所有API名称输入框的宽度
function initializeInputWidths() {
const inputs = document.querySelectorAll('.api-name-input');
inputs.forEach(input => {
autoResizeInput(input);
});
}
// 滚动到接口列表底部
function scrollToNewApi(apiIndex) {
const apiList = document.getElementById('apiList');
if (apiList) {
// 滚动到接口列表的底部
apiList.scrollTo({
top: apiList.scrollHeight,
behavior: 'smooth'
});
// 如果有新添加的接口,给它添加高亮效果
const apiItems = document.querySelectorAll('.api-item');
if (apiItems[apiIndex]) {
// 添加高亮效果
apiItems[apiIndex].style.transition = 'all 0.3s ease';
apiItems[apiIndex].style.backgroundColor = 'rgba(0, 122, 255, 0.1)';
apiItems[apiIndex].style.borderColor = 'var(--primary-color)';
// 3秒后移除高亮效果
setTimeout(() => {
apiItems[apiIndex].style.backgroundColor = '';
apiItems[apiIndex].style.borderColor = '';
}, 3000);
}
}
}
// 点击模态框外部关闭
document.addEventListener('click', function (e) {
if (e.target.classList.contains('modal-overlay')) {
e.target.classList.remove('show');
}
if (e.target.classList.contains('confirm-dialog-overlay')) {
hideConfirmDialog(false);
}
});
// 全局VSCode API实例
let vsCodeApi = null;
// VSCode webview消息监听器
if (typeof acquireVsCodeApi !== 'undefined') {
vsCodeApi = acquireVsCodeApi();
window.addEventListener('message', function (event) {
const message = event.data;
if (message.command === 'importBackendDesignData') {
try {
console.log('🔧 [后端设计] 收到配置数据,正在导入...');
importDataFromString(message.data);
console.log('🔧 [后端设计] 配置数据导入成功');
} catch (error) {
console.error('❌ [后端设计] 导入配置数据失败:', error);
}
}
});
}
// 保存配置功能
function saveConfig() {
try {
const dataStr = JSON.stringify(apiConfig, null, 2);
// 通过VSCode webview API发送数据
if (vsCodeApi) {
vsCodeApi.postMessage({
command: 'saveConfig',
data: dataStr
});
} else {
// 如果不在VSCode环境中,则下载文件作为备用方案
const dataBlob = new Blob([dataStr], { type: 'application/json' });
const url = URL.createObjectURL(dataBlob);
const link = document.createElement('a');
link.href = url;
link.download = 'backend-design-config.json';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
}
// 显示保存成功提示
const saveButton = document.querySelector('.right-btn.save');
if (saveButton) {
const originalText = saveButton.innerHTML;
saveButton.innerHTML = '<i class="fas fa-check"></i><span>已保存</span>';
saveButton.style.background = '#34C759';
setTimeout(() => {
saveButton.innerHTML = originalText;
saveButton.style.background = '';
}, 2000);
}
} catch (error) {
console.error('保存配置失败:', error);
// 保存失败时显示在按钮上
const saveButton = document.querySelector('.right-btn.save');
if (saveButton) {
const originalText = saveButton.innerHTML;
saveButton.innerHTML = '<i class="fas fa-times"></i><span>保存失败</span>';
saveButton.style.background = '#FF3B30';
setTimeout(() => {
saveButton.innerHTML = originalText;
saveButton.style.background = '';
}, 2000);
}
}
}
/**
* 根据API路径滚动到指定的API卡片
*/
function scrollToApiByPath(apiPath) {
try {
console.log('[Backend Design] 尝试滚动到API:', apiPath);
// 查找具有指定data-path属性的API item
const apiItem = document.querySelector(`[data-path="${apiPath}"]`);
if (apiItem) {
console.log('[Backend Design] 找到API卡片,开始滚动');
// 滚动到API item,并添加高亮效果
apiItem.scrollIntoView({
behavior: 'smooth',
block: 'center'
});
// 添加临时高亮效果
apiItem.style.border = '2px solid #007AFF';
apiItem.style.boxShadow = '0 0 10px rgba(0, 122, 255, 0.3)';
// 3秒后移除高亮效果
setTimeout(() => {
apiItem.style.border = '';
apiItem.style.boxShadow = '';
}, 3000);
} else {
console.warn('[Backend Design] 未找到路径为', apiPath, '的API卡片');
// 如果没找到,尝试搜索所有API卡片
const allApiItems = document.querySelectorAll('[data-path]');
console.log('[Backend Design] 当前页面所有API路径:',
Array.from(allApiItems).map(item => item.getAttribute('data-path')));
}
} catch (error) {
console.error('[Backend Design] 滚动到API失败:', error);
}
}
/**
* 监听来自VS Code的消息
*/
window.addEventListener('message', event => {
const message = event.data;
console.log('[Backend Design] 收到消息:', message);
switch (message.type) {
case 'preViewOneAPI':
console.log('[Backend Design] 处理preViewOneAPI消息:', message.apiPath);
if (message.apiPath) {
// 延迟一点确保页面完全渲染
setTimeout(() => {
scrollToApiByPath(message.apiPath);
}, 200);
}
break;
default:
// 其他消息类型的处理
break;
}
});
// 页面加载完成后初始化textarea高度和输入框宽度
document.addEventListener('DOMContentLoaded', function () {
setTimeout(() => {
initializeTextareaHeights();
initializeInputWidths();
}, 100);
});
</script>
</body>
</html>