bytefun
Version:
一个打通了原型设计、UI设计与代码转换、跨平台原生代码开发等的平台
4,870 lines • 208 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>枫叶小说APP UI设计规范</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/spectrum/1.8.1/spectrum.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/spectrum/1.8.1/spectrum.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--bg-primary: #1B1B1B;
--bg-secondary: #232323;
--bg-tertiary: #2C2C2C;
--bg-text: #494949;
--text-primary: #FFFFFF;
--text-secondary: #8E8E93;
--border-color: #2C2C2C;
--input-bg: #232323;
}
[data-theme="light"] {
--bg-primary: #FDFDFD;
--bg-secondary: #F5F5F5;
--bg-tertiary: #E8E8E8;
--bg-text: #E8E8E8;
--text-primary: #1C1C1E;
--text-secondary: #6D6D70;
--border-color: #D1D1D6;
--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-top: 12px;
padding-bottom: 12px;
}
.header {
text-align: center;
margin-bottom: 18px;
padding: 16px;
background: linear-gradient(135deg, #007AFF, #5856D6);
color: white;
border-radius: 8px;
}
.header h1 {
font-size: 20px;
font-weight: 700;
margin-bottom: 6px;
}
.header p {
font-size: 14px;
opacity: 0.9;
}
.section {
background: var(--bg-secondary);
border-radius: 8px;
padding: 16px;
margin-bottom: 16px;
box-shadow: 0 2px 6px #00000040;
border: 1px solid var(--border-color);
transition: background-color 0.3s ease, border-color 0.3s ease;
}
.section-title {
font-size: 18px;
font-weight: 600;
color: var(--text-primary);
margin-bottom: 12px;
display: flex;
align-items: center;
gap: 6px;
}
.subsection {
margin-bottom: 16px;
}
.subsection-title {
font-size: 16px;
font-weight: 500;
color: var(--text-primary);
margin-bottom: 10px;
}
.theme-toggle {
position: fixed;
top: 20px;
right: 36px;
background: var(--bg-primary);
border: 1px solid var(--border-color);
border-radius: 20px;
padding: 6px 12px;
cursor: pointer;
font-size: 12px;
color: var(--text-primary);
transition: all 0.3s ease;
z-index: 1000;
}
.theme-toggle:hover {
background: var(--bg-tertiary);
}
.save-button {
position: fixed;
top: 20px;
right: 130px;
background: var(--bg-primary);
border: 1px solid var(--border-color);
border-radius: 20px;
padding: 6px 12px;
cursor: pointer;
font-size: 12px;
color: var(--text-primary);
transition: all 0.3s ease;
z-index: 1000;
}
.save-button:hover {
background: var(--bg-tertiary);
}
.preview-button {
position: fixed;
top: 20px;
right: 199px;
background: var(--bg-primary);
border: 1px solid var(--border-color);
border-radius: 20px;
padding: 6px 12px;
cursor: pointer;
font-size: 12px;
color: var(--text-primary);
transition: all 0.3s ease;
z-index: 1000;
}
.preview-button:hover {
background: var(--bg-tertiary);
}
/* 手机预览侧滑面板样式 */
.phone-preview-overlay {
position: fixed;
top: 0;
right: -280px;
width: 280px;
height: 100%;
z-index: 10001;
transition: right 0.3s ease;
pointer-events: none;
}
.phone-preview-overlay .phone-frame {
pointer-events: auto;
}
.phone-preview-overlay .phone-frame * {
pointer-events: auto;
}
.phone-preview-overlay.show {
right: 0;
}
.phone-preview-overlay.dragging {
transition: none;
cursor: grabbing;
}
.phone-frame {
width: 413px;
height: 872px;
background: #1a1a1a;
border-radius: 30px;
padding: 10px;
box-shadow: -4px 0 20px rgba(0, 0, 0, 0.3);
position: absolute;
top: 50%;
left: 10px;
transform: translateY(-50%) scale(0.6);
transform-origin: left center;
user-select: none;
}
.phone-frame:not(.dragging) {
cursor: default;
}
.phone-frame.dragging {
cursor: move;
}
.phone-frame .phone-content {
user-select: auto;
cursor: auto;
}
.phone-frame .phone-content input {
user-select: auto;
cursor: text;
pointer-events: auto;
position: relative;
z-index: 1;
}
.phone-frame .preview-input-demo {
user-select: auto;
cursor: text;
pointer-events: auto;
position: relative;
z-index: 1;
}
.phone-frame.dragging {
cursor: grabbing;
z-index: 10005;
}
.phone-screen {
width: 393px;
height: 852px;
background: #FFFFFF;
border-radius: 20px;
overflow: hidden;
position: relative;
}
.phone-content {
width: 100%;
height: 100%;
overflow-y: auto;
padding: 16px;
box-sizing: border-box;
}
.preview-header {
text-align: center;
padding: 20px 0;
border-bottom: 1px solid #E5E5EA;
margin-bottom: 20px;
}
.preview-title {
font-size: 20px;
font-weight: 700;
margin-bottom: 8px;
}
.preview-subtitle {
font-size: 14px;
opacity: 0.7;
}
.preview-section {
margin-bottom: 24px;
}
.preview-card {
background-color: #FFFFFF;
border-radius: 12px;
padding: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
margin-bottom: 16px;
}
.preview-section-title {
font-size: 16px;
font-weight: 600;
margin-bottom: 12px;
}
.preview-typography {
margin-bottom: 8px;
}
.preview-buttons {
display: flex;
flex-direction: column;
gap: 8px;
margin-bottom: 16px;
align-items: center;
}
.preview-button-demo {
padding: 12px 16px;
border: none;
border-radius: 8px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
min-width: 120px;
display: flex;
align-items: center;
justify-content: center;
}
.preview-button-demo i {
font-size: 16px;
}
.preview-inputs {
display: flex;
flex-direction: column;
gap: 8px;
margin-bottom: 16px;
}
.preview-input-demo {
padding: 12px;
border: 1px solid #E5E5EA;
border-radius: 8px;
font-size: 14px;
outline: none;
}
.preview-toasts {
display: flex;
flex-direction: column;
gap: 8px;
margin-bottom: 16px;
}
.preview-toast-demo {
padding: 8px 12px;
border-radius: 20px;
color: white;
text-align: center;
font-size: 12px;
display: flex;
align-items: center;
justify-content: center;
min-height: 32px;
}
.phone-close-btn {
position: absolute;
top: 10px;
right: 10px;
width: 30px;
height: 30px;
background: #FF3B30;
border: none;
border-radius: 50%;
color: white;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
z-index: 10002;
}
.phone-close-btn:hover {
background: #DC3545;
}
/* 预览对话框样式 */
.preview-dialog-overlay {
position: absolute;
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: 10003;
opacity: 0;
visibility: hidden;
transition: opacity 0.3s ease, visibility 0.3s ease;
}
.preview-dialog-overlay.show {
opacity: 1;
visibility: visible;
}
.preview-dialog {
background: white;
border-radius: 12px;
padding: 20px;
max-width: 200px;
text-align: center;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
.preview-dialog-title {
font-size: 16px;
font-weight: 600;
margin-bottom: 12px;
}
.preview-dialog-message {
font-size: 14px;
margin-bottom: 16px;
line-height: 1.4;
}
.preview-dialog-button {
padding: 8px 16px;
border: none;
border-radius: 6px;
font-weight: 500;
cursor: pointer;
}
.component-state-toggle {
position: absolute;
top: 8px;
right: 8px;
display: flex;
align-items: center;
gap: 4px;
font-size: 10px;
color: var(--text-secondary);
}
.component-state-toggle input[type="checkbox"] {
width: 12px;
height: 12px;
cursor: pointer;
}
.component-item {
position: relative;
}
.component-controls {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
margin-top: 12px;
font-size: 10px;
padding-right: 20px;
}
.component-control-group {
display: flex;
flex-direction: column;
gap: 4px;
}
.component-control-label {
color: var(--text-secondary);
font-size: 9px;
}
.component-control-input {
display: flex;
align-items: center;
gap: 4px;
}
.component-control-input input[type="color"] {
width: 20px;
height: 16px;
border: none;
border-radius: 2px;
cursor: pointer;
}
.component-control-input span {
font-family: 'Monaco', monospace;
font-size: 9px;
color: var(--text-secondary);
}
.spectrum-color-picker {
width: 30px;
height: 20px;
border: 1px solid var(--border-color);
border-radius: 3px;
cursor: pointer;
}
/* 自定义Spectrum颜色选择器样式 */
.sp-replacer {
background: transparent !important;
border: 1px solid var(--border-color) !important;
border-radius: 3px !important;
padding: 0 !important;
width: 30px !important;
height: 20px !important;
overflow: hidden !important;
}
.sp-preview {
width: 100% !important;
height: 100% !important;
border: none !important;
margin: 0 !important;
}
.sp-dd {
display: none !important;
}
/* 颜色选择面板主题样式 */
.sp-container {
background-color: var(--bg-secondary) !important;
border: 1px solid #6D6D70 !important;
border-radius: 8px !important;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3) !important;
z-index: 9999 !important;
}
.sp-top {
background-color: var(--bg-secondary) !important;
}
.sp-color,
.sp-hue {
border: 1px solid var(--border-color) !important;
}
.sp-input-container {
background-color: var(--bg-tertiary) !important;
}
.sp-input {
background-color: var(--input-bg) !important;
border: 1px solid var(--border-color) !important;
color: var(--text-primary) !important;
}
.sp-palette-container {
background-color: var(--bg-secondary) !important;
border-top: 1px solid var(--border-color) !important;
border-right: 1px solid var(--border-color) !important;
}
.sp-picker-container {
border-left: 1px solid var(--border-color) !important;
}
.sp-thumb-el {
border: 2px solid #fff !important;
box-shadow: 0 0 3px rgba(0, 0, 0, 0.5) !important;
}
.sp-alpha-handle,
.sp-hue-handle {
border: 2px solid #fff !important;
box-shadow: 0 0 3px rgba(0, 0, 0, 0.5) !important;
}
.sp-button-container {
background-color: var(--bg-tertiary) !important;
border-top: 1px solid var(--border-color) !important;
}
.sp-cancel {
background-color: var(--bg-tertiary) !important;
border: 1px solid var(--border-color) !important;
color: var(--text-primary) !important;
}
.sp-container .sp-button-container .sp-choose {
background-color: #007AFF !important;
border: 1px solid #007AFF !important;
color: #FFFFFF !important;
background-image: none !important;
}
.sp-cancel:hover {
background-color: var(--bg-primary) !important;
}
.sp-container .sp-button-container .sp-choose:hover {
background-color: #0056CC !important;
background-image: none !important;
}
/* 移除白色竖线和间距 */
.sp-sat,
.sp-val,
.sp-top-inner {
border: none !important;
}
.sp-hue {
border-left: none !important;
border-right: none !important;
}
.sp-color {
border-right: none !important;
}
.sp-fill {
border: none !important;
}
.color-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 12px;
margin-bottom: 12px;
}
.color-item {
display: flex;
flex-direction: column;
gap: 6px;
padding: 10px;
border: 1px solid var(--border-color);
border-radius: 6px;
background: var(--bg-tertiary);
transition: background-color 0.3s ease, border-color 0.3s ease;
}
.color-name-row {
display: flex;
align-items: center;
}
.color-name {
font-weight: 600;
color: var(--text-primary);
font-size: 13px;
flex: 1;
}
.color-controls-row {
display: flex;
align-items: center;
gap: 8px;
}
.color-input {
width: 32px;
height: 24px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.color-value {
font-family: 'Monaco', monospace;
font-size: 11px;
color: var(--text-secondary);
flex: 1;
margin-left: 4px;
}
.color-usage-row {
display: flex;
}
.color-usage {
width: 100%;
font-size: 11px;
background: var(--input-bg);
border: 1px solid var(--border-color);
border-radius: 3px;
padding: 4px 6px;
color: var(--text-primary);
outline: none;
min-height: 32px;
resize: vertical;
font-family: inherit;
line-height: 1.4;
transition: background-color 0.3s ease, border-color 0.3s ease, color 0.3s ease;
}
.font-demo {
margin-bottom: 12px;
}
.font-item {
display: flex;
align-items: center;
gap: 10px;
padding: 8px;
border: 1px solid var(--border-color);
border-radius: 6px;
margin-bottom: 6px;
background: var(--bg-text);
transition: background-color 0.3s ease, border-color 0.3s ease;
}
.font-preview {
flex: 1;
}
.font-controls {
display: flex;
gap: 6px;
align-items: center;
font-size: 12px;
flex-wrap: wrap;
}
.font-color-value {
font-family: 'Monaco', monospace;
font-size: 10px;
color: var(--text-secondary);
margin-left: 4px;
}
.font-bold-checkbox {
display: flex;
align-items: center;
gap: 4px;
font-size: 11px;
color: var(--text-primary);
}
.font-bold-checkbox input[type="checkbox"] {
width: 14px;
height: 14px;
cursor: pointer;
}
.size-input {
width: auto;
min-width: 30px;
max-width: 80px;
padding: 3px 6px;
border: 1px solid var(--border-color);
border-radius: 3px;
font-size: 11px;
background: var(--input-bg);
color: var(--text-primary);
outline: none;
transition: background-color 0.3s ease, border-color 0.3s ease, color 0.3s ease;
}
.size-input:focus {
border-color: var(--border-color);
}
.component-demo {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
margin-bottom: 12px;
}
.component-item {
padding: 12px;
border: 1px solid var(--border-color);
border-radius: 6px;
background: var(--bg-tertiary);
transition: background-color 0.3s ease, border-color 0.3s ease;
}
.component-title {
font-weight: 600;
margin-bottom: 8px;
color: var(--text-primary);
font-size: 14px;
}
.btn {
padding: 8px 16px;
border-radius: 6px;
border: none;
cursor: pointer;
font-weight: 500;
transition: all 0.3s ease;
margin: 3px;
font-size: 13px;
}
/* 按钮样式将通过JavaScript动态生成 */
.input-demo {
width: 100%;
padding: 5px;
font-size: 13px;
margin: 3px 0;
outline: none;
box-sizing: border-box;
/* 其他样式将通过JavaScript动态生成 */
}
/* 通用输入框样式 */
input[type="text"],
input[type="email"],
input[type="password"],
input[type="search"],
input[type="tel"],
input[type="url"],
input[type="number"],
textarea,
.input-demo,
.preview-input-demo {
box-sizing: border-box !important;
padding: 5px !important;
}
/* 通用输入框聚焦样式 */
input[type="text"]:focus,
input[type="email"]:focus,
input[type="password"]:focus,
input[type="search"]:focus,
input[type="tel"]:focus,
input[type="url"]:focus,
input[type="number"]:focus,
textarea:focus,
.input-demo:focus,
.preview-input-demo:focus {
border: 1px solid #06677a !important;
outline: none !important;
box-sizing: border-box !important;
}
.spacing-demo {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 12px;
}
.spacing-item {
display: flex;
align-items: center;
gap: 6px;
padding: 6px 8px;
background: var(--bg-tertiary);
border-radius: 6px;
color: var(--text-primary);
font-size: 12px;
transition: background-color 0.3s ease, color 0.3s ease;
}
.spacing-preview {
width: 16px;
height: 16px;
background: #007AFF;
border-radius: 3px;
}
.add-item-btn {
background: #049c2b;
color: white;
border: none;
padding: 6px;
border-radius: 50%;
cursor: pointer;
font-size: 12px;
width: 24px;
height: 24px;
display: inline-flex;
align-items: center;
justify-content: center;
margin-left: 0;
margin-top: 8px;
}
.add-item-btn:hover {
background: #28A745;
}
.title-with-add {
display: flex;
align-items: center;
margin-bottom: 10px;
}
.title-with-add h2,
.title-with-add h3 {
margin: 0;
margin-top: 10px;
margin-right: 8px;
}
.delete-btn {
background: #FF3B30;
color: white;
border: none;
padding: 3px 6px;
border-radius: 3px;
cursor: pointer;
font-size: 10px;
}
.delete-btn:hover {
background: #DC3545;
}
.editable-input {
border: 1px solid var(--border-color);
border-radius: 3px;
padding: 3px 6px;
font-size: 10px;
width: auto;
min-width: 40px;
max-width: 120px;
background: var(--input-bg);
color: var(--text-primary);
outline: none;
transition: background-color 0.3s ease, border-color 0.3s ease, color 0.3s ease;
}
.editable-input:focus {
border-color: var(--border-color);
}
/* 自适应宽度的文本输入框 */
.auto-width-input {
width: auto;
min-width: 30px;
max-width: 150px;
padding: 3px 6px;
border: 1px solid var(--border-color);
border-radius: 3px;
font-size: 11px;
background: var(--input-bg);
color: var(--text-primary);
outline: none;
transition: all 0.3s ease;
}
.auto-width-input:focus {
border-color: var(--border-color);
}
/* 透明背景的自适应输入框 */
.transparent-auto-input {
background: transparent;
border: none;
outline: none;
width: auto;
min-width: 30px;
max-width: 200px;
font-size: inherit;
font-weight: inherit;
color: inherit;
}
.toast-demo {
display: inline-block;
padding: 8px 12px;
border-radius: 14px;
color: white;
margin: 3px;
min-height: 32px;
max-width: 140px;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
}
/* Toast样式将通过JavaScript动态生成 */
/* 自定义确认删除弹框样式 */
.custom-confirm-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: 10000;
opacity: 0;
visibility: hidden;
transition: opacity 0.3s ease, visibility 0.3s ease;
}
.custom-confirm-overlay.show {
opacity: 1;
visibility: visible;
}
.custom-confirm-dialog {
background-color: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 24px;
min-width: 320px;
max-width: 400px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
transform: scale(0.9);
transition: transform 0.3s ease;
}
.custom-confirm-overlay.show .custom-confirm-dialog {
transform: scale(1);
}
.custom-confirm-title {
font-size: 18px;
font-weight: 600;
color: var(--text-primary);
margin-bottom: 12px;
text-align: center;
}
.custom-confirm-message {
font-size: 14px;
color: var(--text-primary);
margin-bottom: 24px;
text-align: center;
line-height: 1.5;
}
.custom-confirm-buttons {
display: flex;
gap: 12px;
justify-content: center;
}
.custom-confirm-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;
}
/* 按钮样式将通过JavaScript动态应用UI规范数据 */
</style>
</head>
<body>
<!-- 自定义确认删除弹框 -->
<div class="custom-confirm-overlay" id="customConfirmOverlay">
<div class="custom-confirm-dialog">
<div class="custom-confirm-title">确认删除</div>
<div class="custom-confirm-message" id="customConfirmMessage">确定要删除这个项目吗?此操作无法撤销。</div>
<div class="custom-confirm-buttons">
<button class="custom-confirm-btn custom-confirm-btn-cancel"
onclick="hideCustomConfirm(false)">取消</button>
<button class="custom-confirm-btn custom-confirm-btn-confirm"
onclick="hideCustomConfirm(true)">确定</button>
</div>
</div>
</div>
<!-- 手机预览浮层 -->
<div class="phone-preview-overlay" id="phonePreviewOverlay">
<div class="phone-frame">
<button class="phone-close-btn" onclick="togglePhonePreview()">
<i class="fas fa-times"></i>
</button>
<div class="phone-screen">
<div class="phone-content" id="phoneContent">
<!-- 预览内容将通过JavaScript动态生成 -->
</div>
<!-- 预览对话框 -->
<div class="preview-dialog-overlay" id="previewDialogOverlay">
<div class="preview-dialog">
<div class="preview-dialog-title">提示</div>
<div class="preview-dialog-message">这是一个使用UI规范的对话框示例</div>
<button class="preview-dialog-button" onclick="hidePreviewDialog()">确定</button>
</div>
</div>
</div>
</div>
</div>
<div class="container">
<div class="header">
<div style="display: flex; justify-content: space-between; align-items: center;">
<div>
<h1>枫叶小说APP UI设计规范</h1>
<p>交互式设计规范展示与编辑工具</p>
</div>
<div class="preview-button" onclick="togglePhonePreview()"
style="position: fixed; z-index: 1000; margin: 0;">
<i class="fas fa-mobile-alt"></i>
<span>预览界面</span>
</div>
<div class="save-button" onclick="saveConfig()" style="position: fixed; z-index: 1000; margin: 0;">
<i class="fas fa-save"></i>
<span>保存</span>
</div>
<div class="theme-toggle" onclick="toggleTheme()" style="position: fixed; z-index: 1000; margin: 0;">
<i class="fas fa-sun"></i>
<span id="theme-text">亮色模式</span>
</div>
</div>
</div>
<!-- 颜色规范 -->
<div class="section">
<h2 class="section-title">
<i class="fas fa-palette"></i>
颜色规范
</h2>
<div class="subsection">
<div class="title-with-add">
<h3 class="subsection-title">1、品牌色系统</h3>
<button class="add-item-btn" onclick="addColorItem('brand')" title="添加品牌色">
<i class="fas fa-plus"></i>
</button>
</div>
<div class="color-grid" id="brand-colors">
<!-- 动态生成 -->
</div>
</div>
<div class="subsection">
<div class="title-with-add">
<h3 class="subsection-title">2、功能色系统</h3>
<button class="add-item-btn" onclick="addColorItem('functional')" title="添加功能色">
<i class="fas fa-plus"></i>
</button>
</div>
<div class="color-grid" id="functional-colors">
<!-- 动态生成 -->
</div>
</div>
<div class="subsection">
<div class="title-with-add">
<h3 class="subsection-title">3、常用色系统</h3>
<button class="add-item-btn" onclick="addColorItem('common')" title="添加常用色">
<i class="fas fa-plus"></i>
</button>
</div>
<div class="color-grid" id="common-colors">
<!-- 动态生成 -->
</div>
</div>
</div>
<!-- 字体规范 -->
<div class="section">
<div class="title-with-add">
<h2 class="section-title">
<i class="fas fa-font"></i>
字体规范
</h2>
<button class="add-item-btn" onclick="addFontItem()" title="添加字体样式">
<i class="fas fa-plus"></i>
</button>
</div>
<div class="font-demo" id="font-demo">
<!-- 动态生成 -->
</div>
</div>
<!-- 组件规范 -->
<div class="section">
<h2 class="section-title">
<i class="fas fa-cube"></i>
组件规范
</h2>
<div id="components-container">
<!-- 动态生成所有组件类型 -->
</div>
</div>
<!-- 间距规范 -->
<div class="section">
<div class="title-with-add">
<h2 class="section-title">
<i class="fas fa-ruler"></i>
间距规范
</h2>
<button class="add-item-btn" onclick="addSpacingItem()" title="添加间距规范">
<i class="fas fa-plus"></i>
</button>
</div>
<div class="spacing-demo" id="spacing-demo">
<!-- 动态生成 -->
</div>
</div>
</div>
<script>
// UI设计规范数据结构
let uiDesignData = {
"version": "1.0.0",
"cnName": "小说之家APP UI设计规范",
"colors": {
"brand": [
{
"enName": "primary",
"cnName": "主色",
"value": "#FF6B35",
"usage": "主要操作按钮、阅读按钮、重要信息强调、品牌标识"
},
{
"enName": "secondary",
"cnName": "次色",
"value": "#4A90E2",
"usage": "次要操作、书架管理、分类标签"
},
{
"enName": "tertiary",
"cnName": "第三色",
"value": "#9B59B6",
"usage": "VIP标识、特殊徽章、推荐标签"
},
{
"enName": "primaryHover",
"cnName": "主色Hover",
"value": "#fe723f",
"usage": "主色在hover状态下的颜色"
},
{
"enName": "secondaryHover",
"cnName": "次色Hover",
"value": "#5596e2",
"usage": "次色在hover状态下的颜色"
},
{
"enName": "tertiaryHover",
"cnName": "第三色Hover",
"value": "#9c5eb5",
"usage": "第三色在hover状态下的颜色"
},
{
"enName": "primaryBgText",
"cnName": "主色背景文字色",
"value": "#FFFFFF",
"usage": "主色背景上的文字颜色"
},
{
"enName": "secondaryBgText",
"cnName": "次色背景文字色",
"value": "#FFFFFF",
"usage": "次色背景上的文字颜色"
}
],
"functional": [
{
"enName": "success",
"cnName": "成功色",
"value": "#34C759",
"usage": "成功状态、完成操作提示"
},
{
"enName": "warning",
"cnName": "警告色",
"value": "#FF9500",
"usage": "警告信息、需要注意的内容"
},
{
"enName": "error",
"cnName": "错误色",
"value": "#FF3B30",
"usage": "错误状态、失败操作提示"
},
{
"enName": "info",
"cnName": "信息色",
"value": "#5AC8FA",
"usage": "一般信息提示、帮助说明"
}
],
"common": [
{
"enName": "pageBgColor",
"cnName": "页面背景色",
"value": "#FAFAFA",
"usage": "主要页面背景"
},
{
"enName": "readingBgColor",
"cnName": "阅读背景色",
"value": "#FFF8E1",
"usage": "阅读器护眼背景色"
},
{
"enName": "nightBgColor",
"cnName": "夜间背景色",
"value": "#1A1A1A",
"usage": "夜间模式背景"
},
{
"enName": "shadow",
"cnName": "阴影色",
"value": "#00000015",
"usage": "书籍卡片阴影、浮层阴影"
},
{
"enName": "mask",
"cnName": "遮罩色",
"value": "#00000080",
"usage": "弹窗遮罩、模态框背景"
},
{
"enName": "border",
"cnName": "边框色",
"value": "#E8E8E8",
"usage": "组件边框、分割线"
},
{
"enName": "focusBorder",
"cnName": "悬停/聚焦边框色",
"value": "#FF6B35",
"usage": "输入框聚焦、按钮悬停边框"
}
]
},
"typography": [
{
"enName": "largeTitle",
"cnName": "大标题",
"size": 24,
"weight": 700,
"color": "#2C2C2E",
"usage": "页面主标题、小说标题"
},
{
"enName": "mediumTitle",
"cnName": "中标题",
"size": 20,
"weight": 600,
"color": "#2C2C2E",
"usage": "章节标题、分类标题"
},
{
"enName": "smallTitle",
"cnName": "小标题",
"size": 18,
"weight": 500,
"color": "#2C2C2E",
"usage": "卡片标题、作者名称"
},
{
"enName": "bodyText",
"cnName": "正文内容",
"size": 16,
"weight": 400,
"color": "#3A3A3C",
"usage": "小说内容、描述文字"
},
{
"enName": "readingText",
"cnName": "阅读正文",
"size": 18,
"weight": 400,
"color": "#2C2C2E",
"usage": "阅读器内小说正文"
},
{
"enName": "helperText",
"cnName": "辅助文字",
"size": 14,
"weight": 400,
"color": "#8E8E93",
"usage": "提示文字、次要信息"
},
{
"enName": "captionText",
"cnName": "说明文字",
"size": 12,
"weight": 400,
"color": "#AEAEB2",
"usage": "时间戳、字数统计、版权信息"
}
],
"spacing": [
{
"enName": "tiny",
"cnName": "极小间距",
"value": 4,
"usage": "图标与文字间距、标签内边距"
},
{
"enName": "small",
"cnName": "小间距",
"value": 8,
"usage": "组件内部元素间距、按钮内边距"
},
{
"enName": "medium",
"cnName": "中间距",
"value": 16,
"usage": "卡片内边距、表单元素间距"
},
{
"enName": "large",
"cnName": "大间距",
"value": 24,
"usage": "页面边距、模块间距"
},
{
"enName": "extraLarge",
"cnName": "超大间距",
"value": 32,
"usage": "页面顶部间距、重要模块分隔"
},
{
"enName": "pageHorizontal",
"cnName": "页面左右间距",
"value": 16,
"usage": "页面内容左右边距"
},
{
"enName": "readingLineHeight",
"cnName": "阅读行间距",
"value": 28,
"usage": "阅读器文字行高"
},
{
"enName": "readingParagraph",
"cnName": "阅读段落间距",
"value": 20,
"usage": "阅读器段落间距"
}
],
"components": {
"buttons": [
{
"enName": "primary",
"cnName": "主要按钮",
"normal": {
"backgroundColor": "#FF6B35",
"textColor": "#FFFFFF",
"hoverBackgroundColor": "#E55A2B"
},
"disabled": {
"backgroundColor": "#E5E5EA",
"textColor": "#8E8E93"
},
"borderRadius": 12
},
{
"enName": "secondary",
"cnName": "次要按钮",
"normal": {
"backgroundColor": "#F8F9FA",
"textColor": "#6b6b6b",
"hoverBackgroundColor": "#E9ECEF"
},
"disabled": {
"backgroundColor": "#F2F2F7",
"textColor": "#C7C7CC"
},
"borderRadius": 12
},
{
"enName": "textButton",
"cnName": "文字按钮",
"normal": {
"backgroundColor": "#00000000",
"textColor": "#FF6B35",
"hoverBackgroundColor": "#FFF5F2"
},
"disabled": {
"backgroundColor": "#00000000",
"textColor": "#AEAEB2"
},
"borderRadius": 8
},
{
"enName": "readingButton",
"cnName": "阅读按钮",
"normal": {
"backgroundColor": "#FF6B35",
"textColor": "#FFFFFF",
"hoverBackgroundColor": "#E55A2B"
},
"disabled": {
"backgroundColor": "#E5E5EA",
"textColor": "#8E8E93"
},
"borderRadius": 20
},
{
"enName": "bookmarkButton",
"cnName": "收藏按钮",
"normal": {
"backgroundColor": "#F8F9FA",
"textColor": "#9B59B6",
"hoverBackgroundColor": "#F3E5F5"
},
"disabled": {
"backgroundColor": "#F2F2F7",
"textColor": "#C7C7CC"
},
"borderRadius": 16
},
{
"enName": "iconButton",
"cnName": "图标按钮",
"normal": {
"backgroundColor": "#FF6B35",
"textColor": "#FFFFFF",
"hoverBackgroundColor": "#E55A2B"
},
"disabled": {
"backgroundColor": "#E5E5EA",
"textColor": "#8E8E93"
},
"borderRadius": 12
}
],
"inputs": [
{
"enName": "default",
"cnName": "默认输入框",
"normal": {
"backgroundColor": "#FFFFFF",
"borderColor": "#E8E8E8",
"textColor": "#2C2C2E",
"placeholderColor": "#8E8E93",
"focusBorderColor": "#FF6B35"
},
"disabled": {
"backgroundColor": "#F2F2F7",
"borderColor": "#E5E5EA",
"textColor": "#8E8E93"
},
"borderRadius": 12
},
{
"enName": "search",
"cnName": "搜索输入框",
"normal": {
"backgroundColor": "#F8F9FA",
"borderColor": "#00000000",
"textColor": "#2C2C2E",
"placeholderColor": "#8E8E93",
"focusBorderColor": "#FF6B35"
},
"disabled": {
"backgroundColor": "#F2F2F7",
"borderColor": "#00000000",
"textColor": "#8E8E93"
},
"borderRadius": 20
}
],
"toasts": [
{
"enName": "info",
"cnName": "信息提示",
"backgroundColor": "#000000CF",
"textColor": "#FFFFFF",
"borderRadius": 20,
"minHeight": 45,
"maxWidth": 200
},
{
"enName": "success",
"cnName": "成功提示",
"backgroundColor": "#34C759",
"textColor": "#FFFFFF",
"borderRadius": 20,
"minHeight": 45,
"maxWidth": 200
},
{
"enName": "warning",
"cnName": "警告提示",
"backgroundColor": "#FF9500",
"textColor": "#FFFFFF",
"borderRadius": 20,
"minHeight": 45,
"maxWidth": 200
},
{
"enName": "error",
"cnName": "错误提示",
"backgroundColor": "#FF3B30",
"textColor": "#FFFFFF",
"borderRadius": 20,
"minHeight": 45,
"maxWidth": 200
}
],
"cards": [
{
"enName": "book",
"cnName": "书籍卡片",
"backgroundColor": "#FFFFFF",
"hoverBackgroundColor": "#F8F9FA",
"borderColor": "#E8E8E8",
"borderRadius": 12,
"shadow": "0 2px 12px #00000010",
"usage": "书籍信息展示卡片"
},
{
"enName": "chapter",
"cnName": "章节卡片",
"backgroundColor": "#FFFFFF",
"hoverBackgroundColor": "#FFF5F2",
"borderColor": "#F0F0F0",
"borderRadius": 8,
"shadow": "0 1px 4px #00000008",
"usage": "章节列表卡片"
},
{
"enName": "author",
"cnName": "作者卡片",
"backgroundColor": "#FFFFFF",
"hoverBackgroundColor": "#F3E5F5",
"borderColor": "#E8E8E8",
"borderRadius": 16,
"shadow": "0 2px 8px #00000012",
"usage": "作者信息展示卡片"
}
],
"tagOrStatus": [
{
"enName": "completed",
"cnName": "完结状态",
"backgroundColor": "#F6FFED",
"textColor": "#52C41A",
"borderRadius": 12,
"borderColor": "#B7EB8F"
},
{
"enName": "updating",
"cnName": "连载状态",
"backgroundColor": "#E6F7FF",
"textColor": "#1890FF",
"borderRadius": 12,
"borderColor": "#91D5FF"
},
{
"enName": "vip",
"cnName": "VIP标识",
"backgroundColor": "#F9F0FF",
"textColor": "#9B59B6",
"borderRadius": 8,
"borderColor": "#D3ADF7"
},
{
"enName": "hot",
"cnName": "热门标签",
"backgroundColor": "#FFF2E8",
"textColor": "#FF6B35",
"borderRadius": 12,
"borderColor": "#FFD8BF"
},
{
"enName": "new",
"cnName": "新书标签",
"backgroundColor": "#FFF0F6",
"textColor": "#EB2F96",
"borderRadius": 12,
"borderColor": "#FFADD2"
},
{
"enName": "category",
"cnName": "分类标签",
"backgroundColor": "#F6F8FA",
"textColor": "#586069",
"borderRadius": 16,
"borderColor": "#D1D5DA"
}
]
}
};
// 更新颜色
function updateColor(input, colorId) {
const colorValue = input.value;
const colorItem = input.closest('.color-item');
const valueSpan = colorItem.querySelector('.color-value');
valueSpan.textContent = colorValue.toUpperCase();
// 更新数据结构
updateColorInData(colorId, colorValue, colorItem);
// 更新相关组件的颜色
updateRelatedComponents(colorId, colorValue);
// 重新生成CSS以应用颜色变化
generateDynamicCSS();
}
// 更新数据结构中的颜色
function updateColorInData(colorId, colorValue, colorItem) {
if (!colorId) return;
// 查找并更新品牌色
const brandColor = uiDesignData.colors.brand.find(c => c.enName === colorId);
if (brandColor) {
brandColor.value = colorValue;
return;
}
// 查找并更新功能色
const functionalColor = uiDesignData.colors.functional.find(c => c.enName === colorId);
if (functionalColor) {
functionalColor.value = colorValue;
return;
}
// 查找并更新常用色
const commonColor = uiDesignData.colors.common.find(c => c.enName === colorId);
if (commonColor) {
commonColor.value = colorValue;
}
}
// 更新相关组件颜色
function updateRelatedComponents(type, color) {
const root = document.documentElement;
switch (type) {
case 'primary':
document.querySelectorAll('.btn-primary').forEach(btn => {
btn.style.backgroundColor = color;
});
break;
case 'primaryBgText':
// 更新主标题颜色
document.querySelectorAll('.header h1, .preview-title').forEach(element => {
element.style.color = color;
});
break;
case 'secondaryBgText':
// 更新副标题颜色
document.querySelectorAll('.header p, .preview-subtitle').forEach(element => {
element.style.color = color;
});
break;
case 'primaryHover':
case 'secondaryHover':
// hover颜色变化会通过CSS动态生成,这里不需要特殊处理
break;
case 'success':
// 更新Toast组件数据中的背景色
const successToast = uiDesignData.components.toasts.find(t => t.enName === 'success');
if (successToast) {
successToast.backgroundColor = color;
}
document.querySelectorAll('.toast-success').forEach(toast => {
toast.style.backgroundColor = color;
});
break;
case 'warning':
// 更新Toast组件数据中的背景色
const warningToast = uiDesignData.components.toasts.find(t => t.enName === 'warning');
if (warningToast) {
warningToast.backgroundColor = color;
}
document.querySelectorAll('.toast-warning').forEach(toast => {
toast.style.backgroundColor = color;
});
break;
case 'error':
// 更新Toast组件数据中的背景色
const errorToast = uiDesignData.components.toasts.find(t => t.enName === 'error');
if (errorToast) {
errorToast.backgroundColor = color;
}
document.querySelectorAll('.toast-error').forEach(toast => {
toast.style.backgroundColor = color;
});
break;
}
// 更新手机预览区域
updatePhonePreviewColors();
}
// 渲染所有内容
function renderAll() {
generateDynamicCSS();
renderColors();
renderTypography();
renderSpacing();
renderComponents();
updatePageTitle();
// 延迟初始化自适应输入框,确保DOM已完全渲染
setTimeout(() => {
initAutoResizeInputs();
}, 100);
}
// 渲染组件
function renderComponents() {
const container = document.getElementById('components-container');
container.innerHTML = '';
// 动态渲染所有组件类型
let componentIndex = 1;
Object.keys(uiDesignData.components).forEach(componentType => {
const componentData = uiDesignData.components[componentType];
if (Array.isArray(componentData) && componentData.length > 0) {
const subsection = createComponentSubsection(componentType, componentData, componentIndex);
container.appendChild(subsection);
componentIndex++;
}
});
// 初始化组件颜色选择器和自适应输入框
setTimeout(() => {
initComponentColorPickers();
initAutoResizeInputs();
}, 200);
}
// 创建组件子区域
function createComponentSubsection(componentType, componentData, index) {
const subsection = document.createElement('div');
subsection.className = 'subsection';
// 获取组件类型的中文名称
const componentTypeNames = {
'buttons': '按钮组件',
'inputs': '输入框组件',
'toasts': 'Toast提示组件',
'cards': '卡片组件',
'tagOrStatus': '标签/状态组件'
};
const typeName = componentTypeNames[componentType] || `${componentType}组件`;
subsection.innerHTML = `
<div class="title-with-add">
<h3 class="subsection-title">${index}、${typeName}</h3>
<button class="add-item-btn" onclick="addGenericComponent('${componentType}')" title="添加${typeName}">
<i class="fas fa-plus"></i>
</button>
</div>
<div class="component-demo" id="${componentType}-demo">
<!-- 动态生成 -->
</div>
`;
// 渲染该类型的所有组件
const demoContainer = subsection.querySelector(`#${componentType}-demo`);
componentData.forEach(component => {
const componentItem = createGenericComponentItem(component, componentType);
demoContainer.appendChild(componentItem);
});
return subsection;
}
// 创建通用组件项
function createGenericComponentItem(componentData, componentType) {
const div = document.createElement('div');
div.className = 'component-item';
div.dataset.componentId = componentData.enName;
div.dataset.componentType = componentType;
// 根据组件类型使用特定的创建函数
if (componentType === 'buttons') {
return createButtonItem(componentData);
} else if (componentType === 'inputs') {
return createInputItem(componentData);
} else if (componentType === 'toasts') {
return createToastItem(componentData);
} else {
// 通用组件项创建
return createCustomComponentItem(componentData, componentType);
}
}
// 创建自定义组件项
function createCustomComponentItem(componentData, componentType) {
const div = document.createElement('div');
div.className = 'component-item';
div.dataset.componentId = componentData.enName;
div.dataset.componentType = componentType;
// 组件预览区域
let previewHtml = '';
if (componentType === 'cards') {
previewHtml = `
<div style="
background-color: #FCFCFC;
padding: 13px;
margin: 8px 0;
border-radius: 8px;
width: fit-content;
height: fit-content;
">
<div class="card-preview" style="
background-color: ${componentData.backgroundColor};
border: 1px solid ${componentData.borderColor};
border-radius: ${componentData.borderRadius}px;
box-shadow: ${componentData.shadow};
width: 100px;
height: 60px;
">
${componentData.cnName}
</div>
</div>
`;
} else if (componentType === 'tagOrStatus') {
previewHtml = `
<div class="tagOrStatus-preview" style="
background-color: ${componentData.backgroundColor};
color: ${componentData.textColor};
border: 1px solid ${componentData.borderColor};
border-radius: ${componentData.borderRadius}px;
padding: 4px 8px;
margin: 8px 0;
display: inline-block;
font-size: 12px;
">
${componentData.cnName}
</div>
`;
} else {
previewHtml = `
<div class="generic-preview" style="
background-color: ${componentData.backgroundColor || '#F5F5F5'};
color: ${componentData.textColor || '#333'};
border: 1px solid ${componentData.borderColor || '#DDD'};
border-radius: ${componentData.borderRadius || 4}px;
padding-left: 10px;
padding-right: 10px;
padding-top: 5px;
padding-bottom: 5px;
margin: 8px 0;
width: fit-content;
height: fit-content;
">
${componentData.cnName}
</div>
`;
}
// 生成控制项
let controlsHtml = '';
// 为卡片组件和状态组件特殊处理:将主要属性排列在一行
if (componentType === 'cards') {
// 确保hoverBackgroundColor属性存在
if (!componentData.hoverBackgroundColor) {
componentData.hoverBackgroundColor = '#F0F0F0';
}
controlsHtml += `
<div class="component-control-group" style="grid-column: span 3;">
<div class="component-control-label">卡片属性</div>
<div style="display: flex; gap: 8px; margin-top: 4px; align-items: end;">
<div>
<label style="font-size: 8px; color: var(--text-secondary); display: block;">背景色</label>
<div style="display: flex; align-items: center; gap: 4px;">
<input type="color" class="spectrum-color-picker" value="${componentData.backgroundColor}" data-component-id="${componentData.enName}" data-component-type="${componentType}" data-property="backgroundColor" style="width: 30px; height: 20px;">
<span style="font-size: 9px;">${componentData.backgroundColor}</span>
</div>
</div>
<div>
<label style="font-size: 8px; color: var(--text-secondary); display: block;">边框色</label>
<div style="display: flex; align-items: center; gap: 4px;">
<input type="color" class="spectrum-color-picker" value="${componentData.borderColor}" data-component-id="${componentData.enName}" data-component-type="${componentType}" data-property="borderColor" style="width: 30px; height: 20px;">
<span style="font-size: 9px;">${componentData.borderColor}</span>
</div>
</div>
<div>
<label style="font-size: 8px; color: var(--text-secondary); display: block;">悬浮背景色</label>
<div style="display: flex; align-items: center; gap: 4px;">
<input type="color" class="spectrum-color-picker" value="${componentData.hoverBackgroundColor}" data-component-id="${componentData.enName}" data-component-type="${componentType}" data-property="hoverBackgroundColor" style="width: 30px; height: 20px;">
<span style="font-size: 9px;">${componentData.hoverBackgroundColor}</span>
</div>
</div>
<div>
<label style="font-size: 8px; color: var(--text-secondary); display: block;">圆角</label>
<input type="number" class="size-input" value="${componentData.borderRadius}" onchange="updateGenericComponentProperty('${componentData.enName}', '${componentType}', 'borderRadius', this.value)" min="0" max="50">
</div>
</div>
</div>
`;
// 处理其他属性(阴影等)
Object.keys(componentData).forEach(key => {
if (key !== 'id' && key !== 'name' && key !== 'usage' && key !== 'backgroundColor' && key !== 'borderColor' && key !== 'borderRadius' && key !== 'hoverBackgroundColor') {
const value = componentData[key];
if (key === 'shadow') {
// 阴影控制项
const shadowData = parseShadow(value);
controlsHtml += `
<div class="component-control-group" style="grid-column: span 3;">
<div class="component-control-label">阴影设置</div>
<div style="display: flex; gap: 4px; margin-top: 4px; align-items: end;">
<div>
<label style="font-size: 8px; color: var(--text-secondary); display: block;">水平</label>
<input type="number" class="size-input" value="${shadowData.offsetX}" onchange="updateShadowProperty('${componentData.enName}', '${componentType}', 'offsetX', this.value)">
</div>
<div>
<label style="font-size: 8px; color: var(--text-secondary); display: block;">垂直</label>
<input type="number" class="size-input" value="${shadowData.offsetY}" onchange="updateShadowProperty('${componentData.enName}', '${componentType}', 'offsetY', this.value)">
</div>
<div>
<label style="font-size: 8px; color: var(--text-secondary); display: block;">模糊</label>
<input type="number" class="size-input" value="${shadowData.blurRadius}" onchange="updateShadowProperty('${componentData.enName}', '${componentType}', 'blurRadius', this.value)" min="0">
</div>
<div>
<label style="font-size: 8px; color: var(--text-secondary); display: block;">扩展</label>
<input type="number" class="size-input" value="${shadowData.spreadRadius}" onchange="updateShadowProperty('${componentData.enName}', '${componentType}', 'spreadRadius', this.value)">
</div>
<div style="flex: 1;">
<label style="font-size: 8px; color: var(--text-secondary); display: block;">颜色</label>
<div style="display: flex; align-items: center; gap: 4px;">
<input type="color" class="spectrum-color-picker" value="${shadowData.color}" data-component-id="${componentData.enName}" data-component-type="${componentType}" data-property="shadowColor" style="width: 30px; height: 20px;">
<span style="font-size: 9px;">${shadowData.color}</span>
</div>
</div>
</div>
</div>
`;
}
}
});
} else if (componentType === 'tagOrStatus') {
// 确保borderRadius属性存在
if (!componentData.borderRadius) {
componentData.borderRadius = 4;
}
controlsHtml += `
<div class="component-control-group" style="grid-column: span 3;">
<div class="component-control-label">标签/状态属性</div>
<div style="display: flex; gap: 8px; margin-top: 4px; align-items: end;">
<div>
<label style="font-size: 8px; color: var(--text-secondary); display: block;">背景色</label>
<div style="display: flex; align-items: center; gap: 4px;">
<input type="color" class="spectrum-color-picker" value="${componentData.backgroundColor}" data-component-id="${componentData.enName}" data-component-type="${componentType}" data-property="backgroundColor" style="width: 30px; height: 20px;">
<span style="font-size: 9px;">${componentData.backgroundColor}</span>
</div>
</div>
<div>
<label style="font-size: 8px; color: var(--text-secondary); display: block;">文字色</label>
<div style="display: flex; align-items: center; gap: 4px;">
<input type="color" class="spectrum-color-picker" value="${componentData.textColor}" data-component-id="${componentData.enName}" data-component-type="${componentType}" data-property="textColor" style="width: 30px; height: 20px;">
<span style="font-size: 9px;">${componentData.textColor}</span>
</div>
</div>
<div>
<label style="font-size: 8px; color: var(--text-secondary); display: block;">边框色</label>
<div style="display: flex; align-items: center; gap: 4px;">
<input type="color" class="spectrum-color-picker" value="${componentData.borderColor}" data-component-id="${componentData.enName}" data-component-type="${componentType}" data-property="borderColor" style="width: 30px; height: 20px;">
<span style="font-size: 9px;">${componentData.borderColor}</span>
</div>
</div>
<div>
<label style="font-size: 8px; color: var(--text-secondary); display: block;">圆角</label>
<input type="number" class="size-input" value="${componentData.borderRadius}" onchange="updateGenericComponentProperty('${componentData.enName}', '${componentType}', 'borderRadius', this.value)" min="0" max="50">
</div>
</div>
</div>
`;
// 处理其他属性(如果有的话)
Object.keys(componentData).forEach(key => {
if (key !== 'id' && key !== 'name' && key !== 'usage' && key !== 'backgroundColor' && key !== 'textColor' && key !== 'borderColor' && key !== 'borderRadius') {
const value = componentData[key];
// 这里可以添加其他属性的处理逻辑
}
});
} else {
// 其他组件类型的通用处理
Object.keys(componentData).forEach(key => {
if (key !== 'id' && key !== 'name' && key !== 'usage') {
const value = componentData[key];
if (key === 'shadow') {
// 阴影控制项 - 解析CSS box-shadow
const shadowData = parseShadow(value);
controlsHtml += `
<div class="component-control-group" style="grid-column: span 3;">
<div class="component-control-label">阴影设置</div>
<div style="display: flex; gap: 4px; margin-top: 4px; align-items: end;">
<div>
<label style="font-size: 8px; color: var(--text-secondary); display: block;">水平</label>
<input type="number" class="size-input" value="${shadowData.offsetX}" onchange="updateShadowProperty('${componentData.enName}', '${componentType}', 'offsetX', this.value)">
</div>
<div>
<label style="font-size: 8px; color: var(--text-secondary); display: block;">垂直</label>
<input type="number" class="size-input" value="${shadowData.offsetY}" onchange="updateShadowProperty('${componentData.enName}', '${componentType}', 'offsetY', this.value)">
</div>
<div>
<label style="font-size: 8px; color: var(--text-secondary); display: block;">模糊</label>
<input type="number" class="size-input" value="${shadowData.blurRadius}" onchange="updateShadowProperty('${componentData.enName}', '${componentType}', 'blurRadius', this.value)" min="0">
</div>
<div>
<label style="font-size: 8px; color: var(--text-secondary); display: block;">扩展</label>
<input type="number" class="size-input" value="${shadowData.spreadRadius}" onchange="updateShadowProperty('${componentData.enName}', '${componentType}', 'spreadRadius', this.value)">
</div>
<div style="flex: 1;">
<label style="font-size: 8px; color: var(--text-secondary); display: block;">颜色</label>
<div style="display: flex; align-items: center; gap: 4px;">
<input type="color" class="spectrum-color-picker" value="${shadowData.color}" data-component-id="${componentData.enName}" data-component-type="${componentType}" data-property="shadowColor" style="width: 30px; height: 20px;">
<span style="font-size: 9px;">${shadowData.color}</span>
</div>
</div>
</div>
</div>
`;
} else if (typeof value === 'string' && (value.startsWith('#') || key.includes('Color'))) {
// 颜色控制项
controlsHtml += `
<div class="component-control-group">
<div class="component-control-label">${getPropertyDisplayName(key)}</div>
<div class="component-control-input">
<input type="color" class="spectrum-color-picker" value="${value}" data-component-id="${componentData.enName}" data-component-type="${componentType}" data-property="${key}">
<span>${value}</span>
</div>
</div>
`;
} else if (typeof value === 'number') {
// 数字控制项
controlsHtml += `
<div class="component-control-group">
<div class="component-control-label">${getPropertyDisplayName(key)}</div>
<div class="component-control-input">
<input type="number" class="size-input" value="${value}" onchange="updateGenericComponentProperty('${componentData.enName}', '${componentType}', '${key}', this.value)" min="0" max="100">
<span>px</span>
</div>
</div>
`;
}
}
});
}
div.innerHTML = `
<div class="component-title">
<div style="gap: 3px; margin-bottom: 8px;">
<input type="text" class="color-name transparent-auto-input" value="${componentData.enName}" onchange="updateGenericComponentEnName(this, '${componentData.enName}', '${componentType}')">
<input type="text" class="color-cn-name transparent-auto-input" value="${componentData.cnName}" onchange="updateGenericComponentCnName(this, '${componentData.enName}', '${componentType}')">
</div>
</div>
${previewHtml}
<div class="component-controls">
${controlsHtml}
</div>
<button class="delete-btn" onclick="deleteGenericComponent('${componentData.enName}', '${componentType}')" style="position: absolute; bottom: 8px; right: 8px;">删除</button>
`;
return div;
}
// 获取属性显示名称
function getPropertyDisplayName(key) {
const displayNames = {
'backgroundColor': '背景色',
'textColor': '文字色',
'borderColor': '边框色',
'borderRadius': '圆角',
'hoverBackgroundColor': '悬浮背景色',
'shadow': '阴影',
'minHeight': '最小高度',
'maxWidth': '最大宽度'
};
return displayNames[key] || key;
}
// 解析CSS box-shadow
function parseShadow(shadowValue) {
if (!shadowValue || shadowValue === 'none') {
return {
offsetX: 0,
offsetY: 0,
blurRadius: 0,
spreadRadius: 0,
color: '#000000'
};
}
// 移除多余空格并分割
const parts = shadowValue.trim().split(/\s+/);
const result = {
offsetX: 0,
offsetY: 0,
blurRadius: 0,
spreadRadius: 0,
color: '#000000'
};
let colorFound = false;
let numericIndex = 0;
parts.forEach(part => {
if (part.startsWith('#') || part.startsWith('rgb') || part.startsWith('rgba') || part.match(/^[a-zA-Z]+$/)) {
// 颜色值
result.color = part;
colorFound = true;
} else if (part.match(/^-?\d+(\.\d+)?(px|em|rem|%)?$/)) {
// 数值
const value = parseFloat(part);
switch (numericIndex) {
case 0:
result.offsetX = value;
break;
case 1:
result.offsetY = value;
break;
case 2:
result.blurRadius = value;
break;
case 3:
result.spreadRadius = value;
break;
}
numericIndex++;
}
});
return result;
}
// 更新阴影属性
function updateShadowProperty(componentId, componentType, property, value) {
if (uiDesignData.components[componentType]) {
const component = uiDesignData.components[componentType].find(c => c.enName === componentId);
if (component && component.shadow) {
const shadowData = parseShadow(component.shadow);
// 更新对应属性
switch (property) {
case 'offsetX':
shadowData.offsetX = parseInt(value) || 0;
break;
case 'offsetY':
shadowData.offsetY = parseInt(value) || 0;
break;
case 'blurRadius':
shadowData.blurRadius = parseInt(value) || 0;
break;
case 'spreadRadius':
shadowData.spreadRadius = parseInt(value) || 0;
break;
}
// 重新构建shadow字符串
component.shadow = `${shadowData.offsetX}px ${shadowData.offsetY}px ${shadowData.blurRadius}px ${shadowData.spreadRadius}px ${shadowData.color}`;
// 重新渲染组件
renderComponents();
generateDynamicCSS();
}
}
}
// 初始化组件颜色选择器
function initComponentColorPickers() {
const componentInputs = document.querySelectorAll('[data-component-id].spectrum-color-picker');
componentInputs.forEach(input => {
// 销毁已存在的实例
if ($(input).spectrum) {
$(input).spectrum('destroy');
}
const componentId = input.dataset.componentId;
const componentType = input.dataset.componentType;
const property = input.dataset.property;
// 将#RRGGBBAA格式转换为#AARRGGBB格式传给Spectrum
const colorValue = input.value;
let spectrumColor = colorValue;
if (colorValue.length === 9 && colorValue.startsWith('#')) {
// #RRGGBBAA -> #AARRGGBB
const rgb = colorValue.slice(1, 7);
const alpha = colorValue.slice(7, 9);
spectrumColor = '#' + alpha + rgb;
}
// 确保input元素有正确的样式
input.style.width = '30px';
input.style.height = '20px';
input.style.border = 'none';
input.style.borderRadius = '3px';
input.style.cursor = 'pointer';
$(input).spectrum({
color: spectrumColor,
showInput: true,
showAlpha: true,
showPalette: false,
showSelectionPalette: true,
maxSelectionSize: 10,
preferredFormat: "hex",
localStorageKey: "spectrum.ui-design",
clickoutFiresChange: false,
show: function () {
// 缓存原始颜色值
colorCache.originalValue = input.value;
colorCache.currentInput = input;
colorCache.isEditing = true;
},
move: function (color) {
// 实时预览颜色变化
let colorString;
if (color.getAlpha() < 1) {
const hex = color.toHex8String();
if (hex.length === 9 && hex.startsWith('#')) {
const alpha = hex.slice(1, 3);
const rgb = hex.slice(3, 9);
colorString = '#' + rgb + alpha;
} else {
colorString = hex;
}
} else {
colorString = color.toHexString();
}
updateComponentColorFromSpectrum(componentId, componentType, property, colorString, input);
},
change: function (color) {
// 确认选择颜色
let colorString;
if (color.getAlpha() < 1) {
const hex = color.toHex8String();
if (hex.length === 9 && hex.startsWith('#')) {
const alpha = hex.slice(1, 3);
const rgb = hex.slice(3, 9);
colorString = '#' + rgb + alpha;
} else {
colorString = hex;
}
} else {
colorString = color.toHexString();
}
updateComponentColorFromSpectrum(componentId, componentType, property, colorString, input);
// 清空缓存
colorCache.originalValue = null;
colorCache.currentInput = null;
colorCache.isEditing = false;
},
hide: function () {
// 如果是取消操作,恢复原始颜色
if (colorCache.isEditing && colorCache.originalValue && colorCache.currentInput) {
updateComponentColorFromSpectrum(componentId, componentType, property, colorCache.originalValue, colorCache.currentInput);
}
// 清空缓存
colorCache.originalValue = null;
colorCache.currentInput = null;
colorCache.isEditing = false;
}
});
});
}
// 从组件颜色选择器更新颜色
function updateComponentColorFromSpectrum(componentId, componentType, property, colorString, input) {
// 更新颜色值显示
const colorValue = input.parentElement.querySelector('span');
if (colorValue) {
colorValue.textContent = colorString.toUpperCase();
}
// 更新数据
const propertyPath = property.split('.');
if (componentType === 'button') {
const buttonData = uiDesignData.components.buttons.find(b => b.enName === componentId);
if (buttonData && propertyPath.length === 2) {
buttonData[propertyPath[0]][propertyPath[1]] = colorString;
}
} else if (componentType === 'input') {
const inputData = uiDesignData.components.inputs.find(i => i.enName === componentId);
if (inputData && propertyPath.length === 2) {
inputData[propertyPath[0]][propertyPath[1]] = colorString;
}
} else if (componentType === 'toast') {
const toastData = uiDesignData.components.toasts.find(t => t.enName === componentId);
if (toastData) {
toastData[property] = colorString;
}
} else {
// 处理通用组件类型
if (uiDesignData.components[componentType]) {
const componentData = uiDesignData.components[componentType].find(c => c.enName === componentId);
if (componentData) {
if (property === 'shadowColor') {
// 特殊处理阴影颜色
if (componentData.shadow) {
const shadowData = parseShadow(componentData.shadow);
shadowData.color = colorString;
componentData.shadow = `${shadowData.offsetX}px ${shadowData.offsetY}px ${shadowData.blurRadius}px ${shadowData.spreadRadius}px ${shadowData.color}`;
// 更新阴影颜色显示
const colorSpan = input.parentElement.querySelector('span');
if (colorSpan) {
colorSpan.textContent = colorString.toUpperCase();
}
// 实时更新预览
const componentItem = input.closest('.component-item');
const preview = componentItem.querySelector('.card-preview, .tagOrStatus-preview, .generic-preview');
if (preview) {
preview.style.boxShadow = componentData.shadow;
}
}
} else if (propertyPath.length === 2) {
// 嵌套属性(如 normal.backgroundColor)
if (!componentData[propertyPath[0]]) {
componentData[propertyPath[0]] = {};
}
componentData[propertyPath[0]][propertyPath[1]] = colorString;
} else {
// 直接属性(如 backgroundColor)
componentData[property] = colorString;
}
// 实时更新预览(非阴影颜色)
if (property !== 'shadowColor') {
const componentItem = input.closest('.component-item');
const preview = componentItem.querySelector('.card-preview, .tagOrStatus-preview, .generic-preview');
if (preview && property.includes('Color')) {
if (property === 'backgroundColor') {
preview.style.backgroundColor = colorString;
} else if (property === 'textColor') {
preview.style.color = colorString;
} else if (property === 'borderColor') {
preview.style.borderColor = colorString;
}
}
// 更新颜色值显示文本(针对卡片组件和状态组件)
if (componentType === 'cards' || componentType === 'tagOrStatus') {
const colorValueSpan = input.parentElement.querySelector('span');
if (colorValueSpan) {
colorValueSpan.textContent = colorString.toUpperCase();
}
}
}
}
}
}
generateDynamicCSS();
// 实时更新手机预览区域
updatePhonePreviewColors();
}
// 渲染按钮组件
function renderButtons() {
const container = document.getElementById('buttons-demo');
container.innerHTML = '';
uiDesignData.components.buttons.forEach(button => {
const buttonItem = createButtonItem(button);
container.appendChild(buttonItem);
});
// 初始化组件颜色选择器和自适应输入框
setTimeout(() => {
initComponentColorPickers();
initAutoResizeInputs();
}, 100);
}
// 创建按钮项
function createButtonItem(buttonData) {
const div = document.createElement('div');
div.className = 'component-item';
div.dataset.buttonId = buttonData.enName;
div.innerHTML = `
<div class="component-state-toggle">
<input type="checkbox" onchange="toggleButtonState(this, '${buttonData.enName}')">
<span>禁用</span>
</div>
<div class="component-title">
<div style="gap: 3px; margin-bottom: 8px;">
<input type="text" class="color-name transparent-auto-input" value="${buttonData.enName}" onchange="updateButtonEnName(this, '${buttonData.enName}')">
<input type="text" class="color-cn-name transparent-auto-input" value="${buttonData.cnName}" onchange="updateButtonCnName(this, '${buttonData.enName}')">
</div>
</div>
<button class="btn btn-${buttonData.enName}" id="btn-preview-${buttonData.enName}">
${buttonData.enName === 'iconButton' ? '<i class="fas fa-shopping-cart"></i>' : buttonData.cnName}
</button>
<div class="component-controls">
<div class="component-control-group">
<div class="component-control-label">背景色</div>
<div class="component-control-input">
<input type="color" class="spectrum-color-picker" value="${buttonData.normal.backgroundColor}" data-component-id="${buttonData.enName}" data-component-type="button" data-property="normal.backgroundColor">
<span>${buttonData.normal.backgroundColor}</span>
</div>
</div>
<div class="component-control-group">
<div class="component-control-label">文字色</div>
<div class="component-control-input">
<input type="color" class="spectrum-color-picker" value="${buttonData.normal.textColor}" data-component-id="${buttonData.enName}" data-component-type="button" data-property="normal.textColor">
<span>${buttonData.normal.textColor}</span>
</div>
</div>
<div class="component-control-group">
<div class="component-control-label">悬停背景色</div>
<div class="component-control-input">
<input type="color" class="spectrum-color-picker" value="${buttonData.normal.hoverBackgroundColor}" data-component-id="${buttonData.enName}" data-component-type="button" data-property="normal.hoverBackgroundColor">
<span>${buttonData.normal.hoverBackgroundColor}</span>
</div>
</div>
<div class="component-control-group">
<div class="component-control-label">禁用背景色</div>
<div class="component-control-input">
<input type="color" class="spectrum-color-picker" value="${buttonData.disabled.backgroundColor}" data-component-id="${buttonData.enName}" data-component-type="button" data-property="disabled.backgroundColor">
<span>${buttonData.disabled.backgroundColor}</span>
</div>
</div>
<div class="component-control-group">
<div class="component-control-label">禁用文字色</div>
<div class="component-control-input">
<input type="color" class="spectrum-color-picker" value="${buttonData.disabled.textColor}" data-component-id="${buttonData.enName}" data-component-type="button" data-property="disabled.textColor">
<span>${buttonData.disabled.textColor}</span>
</div>
</div>
</div>
<button class="delete-btn" onclick="deleteButtonComponent('${buttonData.enName}')" style="position: absolute; bottom: 8px; right: 8px;">删除</button>
</div>
`;
return div;
}
// 渲染输入框组件
function renderInputs() {
const container = document.getElementById('inputs-demo');
container.innerHTML = '';
uiDesignData.components.inputs.forEach(input => {
const inputItem = createInputItem(input);
container.appendChild(inputItem);
});
// 初始化组件颜色选择器和自适应输入框
setTimeout(() => {
initComponentColorPickers();
initAutoResizeInputs();
}, 100);
}
// 创建输入框项
function createInputItem(inputData) {
const div = document.createElement('div');
div.className = 'component-item';
div.dataset.inputId = inputData.enName;
// 确保disabled字段存在,如果不存在则使用默认值
if (!inputData.disabled) {
inputData.disabled = {
backgroundColor: '#F5F5F5',
borderColor: '#D9D9D9',
textColor: '#8C8C8C'
};
}
div.innerHTML = `
<div class="component-state-toggle">
<input type="checkbox" onchange="toggleInputState(this, '${inputData.enName}')">
<span>禁用</span>
</div>
<div class="component-title">
<div style="gap: 3px; margin-bottom: 8px;">
<input type="text" class="color-name transparent-auto-input" value="${inputData.enName}" onchange="updateInputEnName(this, '${inputData.enName}')">
<input type="text" class="color-cn-name transparent-auto-input" value="${inputData.cnName}" onchange="updateInputCnName(this, '${inputData.enName}')">
</div>
</div>
<div style="background-color: #F8F9FA; padding: 8px; border-radius: 8px; margin: 8px 0;">
<input type="text" class="input-demo input-${inputData.enName}" id="input-preview-${inputData.enName}" placeholder="请输入内容">
</div>
<div class="component-controls">
<div class="component-control-group">
<div class="component-control-label">背景色</div>
<div class="component-control-input">
<input type="color" class="spectrum-color-picker" value="${inputData.normal.backgroundColor}" data-component-id="${inputData.enName}" data-component-type="input" data-property="normal.backgroundColor">
<span>${inputData.normal.backgroundColor}</span>
</div>
</div>
<div class="component-control-group">
<div class="component-control-label">边框色</div>
<div class="component-control-input">
<input type="color" class="spectrum-color-picker" value="${inputData.normal.borderColor}" data-component-id="${inputData.enName}" data-component-type="input" data-property="normal.borderColor">
<span>${inputData.normal.borderColor}</span>
</div>
</div>
<div class="component-control-group">
<div class="component-control-label">文字色</div>
<div class="component-control-input">
<input type="color" class="spectrum-color-picker" value="${inputData.normal.textColor}" data-component-id="${inputData.enName}" data-component-type="input" data-property="normal.textColor">
<span>${inputData.normal.textColor}</span>
</div>
</div>
<div class="component-control-group">
<div class="component-control-label">提示文字色</div>
<div class="component-control-input">
<input type="color" class="spectrum-color-picker" value="${inputData.normal.placeholderColor}" data-component-id="${inputData.enName}" data-component-type="input" data-property="normal.placeholderColor">
<span>${inputData.normal.placeholderColor}</span>
</div>
</div>
<div class="component-control-group">
<div class="component-control-label">聚焦边框色</div>
<div class="component-control-input">
<input type="color" class="spectrum-color-picker" value="${inputData.normal.focusBorderColor}" data-component-id="${inputData.enName}" data-component-type="input" data-property="normal.focusBorderColor">
<span>${inputData.normal.focusBorderColor}</span>
</div>
</div>
<div class="component-control-group">
<div class="component-control-label">禁用背景色</div>
<div class="component-control-input">
<input type="color" class="spectrum-color-picker" value="${inputData.disabled.backgroundColor}" data-component-id="${inputData.enName}" data-component-type="input" data-property="disabled.backgroundColor">
<span>${inputData.disabled.backgroundColor}</span>
</div>
</div>
<div class="component-control-group">
<div class="component-control-label">禁用边框色</div>
<div class="component-control-input">
<input type="color" class="spectrum-color-picker" value="${inputData.disabled.borderColor}" data-component-id="${inputData.enName}" data-component-type="input" data-property="disabled.borderColor">
<span>${inputData.disabled.borderColor}</span>
</div>
</div>
<div class="component-control-group">
<div class="component-control-label">禁用文字色</div>
<div class="component-control-input">
<input type="color" class="spectrum-color-picker" value="${inputData.disabled.textColor}" data-component-id="${inputData.enName}" data-component-type="input" data-property="disabled.textColor">
<span>${inputData.disabled.textColor}</span>
</div>
</div>
</div>
<button class="delete-btn" onclick="deleteInputComponent('${inputData.enName}')" style="position: absolute; bottom: 8px; right: 8px;">删除</button>
</div>
`;
return div;
}
// 渲染Toast组件
function renderToasts() {
const container = document.getElementById('toasts-demo');
container.innerHTML = '';
uiDesignData.components.toasts.forEach(toast => {
const toastItem = createToastItem(toast);
container.appendChild(toastItem);
});
// 初始化组件颜色选择器和自适应输入框
setTimeout(() => {
initComponentColorPickers();
initAutoResizeInputs();
}, 100);
}
// 创建Toast项
function createToastItem(toastData) {
const div = document.createElement('div');
div.className = 'component-item';
div.dataset.toastId = toastData.enName;
// 检查是否是功能性Toast(成功、警告、错误),这些不显示背景色编辑项
const isFunctionalToast = ['success', 'warning', 'error'].includes(toastData.enName);
const backgroundColorControl = isFunctionalToast ? '' : `
<div class="component-control-group">
<div class="component-control-label">背景色</div>
<div class="component-control-input">
<input type="color" class="spectrum-color-picker" value="${toastData.backgroundColor}" data-component-id="${toastData.enName}" data-component-type="toast" data-property="backgroundColor">
<span>${toastData.backgroundColor}</span>
</div>
</div>`;
div.innerHTML = `
<div class="component-title">
<div style="gap: 3px; margin-bottom: 8px;">
<input type="text" class="color-name transparent-auto-input" value="${toastData.enName}" onchange="updateToastEnName(this, '${toastData.enName}')">
<input type="text" class="color-cn-name transparent-auto-input" value="${toastData.cnName}" onchange="updateToastCnName(this, '${toastData.enName}')">
</div>
</div>
<div class="toast-${toastData.enName}" id="toast-preview-${toastData.enName}">${toastData.cnName}</div>
<div class="component-controls">
${backgroundColorControl}
<div class="component-control-group">
<div class="component-control-label">文字色</div>
<div class="component-control-input">
<input type="color" class="spectrum-color-picker" value="${toastData.textColor}" data-component-id="${toastData.enName}" data-component-type="toast" data-property="textColor">
<span>${toastData.textColor}</span>
</div>
</div>
<div class="component-control-group">
<div class="component-control-label">圆角</div>
<div class="component-control-input">
<input type="number" class="size-input" value="${toastData.borderRadius}" onchange="updateToastProperty('${toastData.enName}', 'borderRadius', this.value)" min="0" max="50">
<span>px</span>
</div>
</div>
<div class="component-control-group">
<div class="component-control-label">最小高度</div>
<div class="component-control-input">
<input type="number" class="size-input" value="${toastData.minHeight}" onchange="updateToastProperty('${toastData.enName}', 'minHeight', this.value)" min="20" max="100">
<span>px</span>
</div>
</div>
<div class="component-control-group">
<div class="component-control-label">最大宽度</div>
<div class="component-control-input">
<input type="number" class="size-input" value="${toastData.maxWidth}" onchange="updateToastProperty('${toastData.enName}', 'maxWidth', this.value)" min="100" max="500">
<span>px</span>
</div>
</div>
</div>
<button class="delete-btn" onclick="deleteToastComponent('${toastData.enName}')" style="position: absolute; bottom: 8px; right: 8px;">删除</button>
`;
return div;
}
// 动态生成CSS样式
function generateDynamicCSS() {
// 移除旧的动态样式
const existingStyle = document.getElementById('dynamic-styles');
if (existingStyle) {
existingStyle.remove();
}
// 创建新的样式元素
const style = document.createElement('style');
style.enName = 'dynamic-styles';
let css = '';
// 生成按钮样式
uiDesignData.components.buttons.forEach(button => {
css += `
.btn-${button.enName} {
background-color: ${button.normal.backgroundColor};
color: ${button.normal.textColor};
border-radius: ${button.borderRadius}px;
border: none;
transition: all 0.3s ease;
${button.enName === 'iconButton' ? 'min-width: 48px; width: 48px; height: 48px; display: flex; align-items: center; justify-content: center; font-size: 16px; padding: 0;' : 'min-width: 120px; display: flex; align-items: center; justify-content: center;'}
}
.btn-${button.enName}:hover:not(:disabled) {
background-color: ${button.normal.hoverBackgroundColor};
}
.btn-${button.enName}:disabled {
background-color: ${button.disabled.backgroundColor};
color: ${button.disabled.textColor};
cursor: not-allowed;
}
`;
});
// 生成输入框样式
uiDesignData.components.inputs.forEach(input => {
// 确保disabled字段存在,如果不存在则使用默认值
if (!input.disabled) {
input.disabled = {
backgroundColor: '#F5F5F5',
borderColor: '#D9D9D9',
textColor: '#8C8C8C'
};
}
css += `
.input-${input.enName} {
background-color: ${input.normal.backgroundColor} !important;
border: 1px solid ${input.normal.borderColor} !important;
border-radius: ${input.borderRadius || 6}px !important;
color: ${input.normal.textColor} !important;
transition: all 0.3s ease;
box-sizing: border-box !important;
}
.input-${input.enName}:focus {
border: 1px solid #404040 !important;
outline: none;
box-sizing: border-box !important;
}
.input-${input.enName}:disabled {
background-color: ${input.disabled.backgroundColor} !important;
border-color: ${input.disabled.borderColor} !important;
color: ${input.disabled.textColor} !important;
cursor: not-allowed;
box-sizing: border-box !important;
}
.input-${input.enName}::placeholder {
color: ${input.normal.placeholderColor};
}
.input-${input.enName}-disabled {
background-color: ${input.disabled.backgroundColor} !important;
border: 1px solid ${input.disabled.borderColor} !important;
border-radius: ${input.borderRadius || 6}px !important;
color: ${input.disabled.textColor} !important;
cursor: not-allowed;
}
`;
});
// 生成Toast样式
uiDesignData.components.toasts.forEach(toast => {
css += `
.toast-${toast.enName} {
background-color: ${toast.backgroundColor};
color: ${toast.textColor};
border-radius: ${toast.borderRadius}px;
min-height: ${toast.minHeight}px;
max-width: ${toast.maxWidth}px;
display: inline-block;
padding: 8px 12px;
margin: 3px;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
}
`;
});
// 生成通用组件样式
Object.keys(uiDesignData.components).forEach(componentType => {
if (componentType !== 'buttons' && componentType !== 'inputs' && componentType !== 'toasts') {
const components = uiDesignData.components[componentType];
if (Array.isArray(components)) {
components.forEach(component => {
css += `
.${componentType}-${component.enName} {
${component.backgroundColor ? `background-color: ${component.backgroundColor};` : ''}
${component.textColor ? `color: ${component.textColor};` : ''}
${component.borderColor ? `border-color: ${component.borderColor};` : ''}
${component.borderRadius !== undefined ? `border-radius: ${component.borderRadius}px;` : ''}
${component.shadow ? `box-shadow: ${component.shadow};` : ''}
${component.minHeight !== undefined ? `min-height: ${component.minHeight}px;` : ''}
${component.maxWidth !== undefined ? `max-width: ${component.maxWidth}px;` : ''}
}
`;
});
}
}
});
// 生成字体样式
uiDesignData.typography.forEach((font, index) => {
css += `
.font-style-${font.enName} {
font-size: ${font.size}px;
font-weight: ${font.weight};
color: ${font.color};
}
`;
});
// 生成间距样式
uiDesignData.spacing.forEach(spacing => {
css += `
.spacing-${spacing.enName} {
width: ${spacing.value}px;
height: ${spacing.value}px;
}
.margin-${spacing.enName} {
margin: ${spacing.value}px;
}
.padding-${spacing.enName} {
padding: ${spacing.value}px;
}
`;
});
// 生成颜色样式
uiDesignData.colors.brand.forEach(color => {
css += `
.bg-${color.enName} {
background-color: ${color.value};
}
.text-${color.enName} {
color: ${color.value};
}
.border-${color.enName} {
border-color: ${color.value};
}
`;
});
// 更新header背景渐变色和文字颜色
const primaryColor = uiDesignData.colors.brand.find(c => c.enName === 'primary');
const secondaryColor = uiDesignData.colors.brand.find(c => c.enName === 'secondary');
const primaryHover = uiDesignData.colors.brand.find(c => c.enName === 'primaryHover');
const secondaryHover = uiDesignData.colors.brand.find(c => c.enName === 'secondaryHover');
const primaryBgText = uiDesignData.colors.brand.find(c => c.enName === 'primaryBgText');
const secondaryBgText = uiDesignData.colors.brand.find(c => c.enName === 'secondaryBgText');
if (primaryColor && secondaryColor && primaryBgText && secondaryBgText) {
css += `
.header {
background: linear-gradient(135deg, ${primaryColor.value}, ${secondaryColor.value}) !important;
transition: background 0.3s ease;
}
.header h1 {
color: ${primaryBgText.value} !important;
transition: color 0.3s ease;
}
.header p {
color: ${secondaryBgText.value} !important;
transition: color 0.3s ease;
}
.header:hover {
background: linear-gradient(135deg, ${primaryHover?.value || primaryColor.value}, ${secondaryHover?.value || secondaryColor.value}) !important;
}
.preview-header {
border-radius: 20px;
background: linear-gradient(135deg, ${primaryColor.value}, ${secondaryColor.value}) !important;
transition: background 0.3s ease;
}
.preview-title {
color: ${primaryBgText.value} !important;
transition: color 0.3s ease;
}
.preview-subtitle {
color: ${secondaryBgText.value} !important;
transition: color 0.3s ease;
}
.preview-header:hover {
background: linear-gradient(135deg, ${primaryHover?.value || primaryColor.value}, ${secondaryHover?.value || secondaryColor.value}) !important;
}
`;
}
uiDesignData.colors.functional.forEach(color => {
css += `
.bg-${color.enName} {
background-color: ${color.value};
}
.text-${color.enName} {
color: ${color.value};
}
.border-${color.enName} {
border-color: ${color.value};
}
`;
});
uiDesignData.colors.common.forEach(color => {
css += `
.bg-${color.enName} {
background-color: ${color.value};
}
.text-${color.enName} {
color: ${color.value};
}
.border-${color.enName} {
border-color: ${color.value};
}
`;
});
style.textContent = css;
document.head.appendChild(style);
}
// 渲染颜色
function renderColors() {
renderColorSection('brand', 'brand-colors');
renderColorSection('functional', 'functional-colors');
renderColorSection('common', 'common-colors');
}
// 渲染颜色分组
function renderColorSection(type, containerId) {
const container = document.getElementById(containerId);
container.innerHTML = '';
uiDesignData.colors[type].forEach(color => {
const colorItem = createColorItem(color, type);
container.appendChild(colorItem);
});
// 初始化颜色选择器和自适应输入框
setTimeout(() => {
initColorPickers(container);
initAutoResizeInputs();
}, 100);
}
// 初始化颜色选择器
function initColorPickers(container) {
const colorInputs = container.querySelectorAll('.spectrum-color-picker');
colorInputs.forEach(input => {
// 销毁已存在的实例
if ($(input).spectrum) {
$(input).spectrum('destroy');
}
const colorId = input.dataset.colorId;
const colorType = input.dataset.colorType;
// 如果没有colorId或colorType,跳过初始化
if (!colorId || !colorType) {
console.warn('颜色选择器缺少必要的数据属性:', input);
return;
}
// 将#RRGGBBAA格式转换为#AARRGGBB格式传给Spectrum
const colorValue = input.value;
let spectrumColor = colorValue;
if (colorValue.length === 9 && colorValue.startsWith('#')) {
// #RRGGBBAA -> #AARRGGBB
const rgb = colorValue.slice(1, 7);
const alpha = colorValue.slice(7, 9);
spectrumColor = '#' + alpha + rgb;
}
// 确保input元素有正确的样式
input.style.width = '32px';
input.style.height = '24px';
input.style.border = 'none';
input.style.borderRadius = '4px';
input.style.cursor = 'pointer';
$(input).spectrum({
color: spectrumColor,
showInput: true,
showAlpha: true,
showPalette: false,
showSelectionPalette: true,
maxSelectionSize: 10,
preferredFormat: "hex",
localStorageKey: "spectrum.ui-design",
clickoutFiresChange: false,
show: function () {
// 缓存原始颜色值
colorCache.originalValue = input.value;
colorCache.currentInput = input;
colorCache.isEditing = true;
},
move: function (color) {
// 实时预览颜色变化
let colorString;
if (color.getAlpha() < 1) {
const hex = color.toHex8String();
if (hex.length === 9 && hex.startsWith('#')) {
const alpha = hex.slice(1, 3);
const rgb = hex.slice(3, 9);
colorString = '#' + rgb + alpha;
} else {
colorString = hex;
}
} else {
colorString = color.toHexString();
}
updateColorFromSpectrum(colorId, colorType, colorString, input);
},
change: function (color) {
// 确认选择颜色
let colorString;
if (color.getAlpha() < 1) {
const hex = color.toHex8String();
if (hex.length === 9 && hex.startsWith('#')) {
const alpha = hex.slice(1, 3);
const rgb = hex.slice(3, 9);
colorString = '#' + rgb + alpha;
} else {
colorString = hex;
}
} else {
colorString = color.toHexString();
}
updateColorFromSpectrum(colorId, colorType, colorString, input);
// 清空缓存
colorCache.originalValue = null;
colorCache.currentInput = null;
colorCache.isEditing = false;
},
hide: function () {
// 如果是取消操作,恢复原始颜色
if (colorCache.isEditing && colorCache.originalValue && colorCache.currentInput) {
updateColorFromSpectrum(colorId, colorType, colorCache.originalValue, colorCache.currentInput);
}
// 清空缓存
colorCache.originalValue = null;
colorCache.currentInput = null;
colorCache.isEditing = false;
}
});
});
}
// 从Spectrum颜色选择器更新颜色
function updateColorFromSpectrum(colorId, colorType, colorString, input) {
// 更新颜色值显示
const colorValue = input.parentElement.querySelector('.color-value');
if (colorValue) {
colorValue.textContent = colorString.toUpperCase();
}
// 如果是功能色,更新预览背景色
if (colorType === 'functional') {
const colorItem = input.closest('.color-item');
const previewDiv = colorItem.querySelector('.color-name-preview');
if (previewDiv) {
previewDiv.style.backgroundColor = colorString;
}
}
// 更新数据
if (colorId && colorType) {
updateColorInData(colorId, colorString, input.closest('.color-item'));
updateRelatedComponents(colorId, colorString);
generateDynamicCSS();
// 实时更新手机预览区域
updatePhonePreviewColors();
}
}
// 初始化字体颜色选择器
function initFontColorPickers() {
const fontColorInputs = document.querySelectorAll('[data-font-id].spectrum-color-picker');
fontColorInputs.forEach(input => {
// 销毁已存在的实例
if ($(input).spectrum) {
$(input).spectrum('destroy');
}
const fontId = input.dataset.fontId;
const property = input.dataset.property;
// 确保input元素有正确的样式
input.style.width = '32px';
input.style.height = '24px';
input.style.border = 'none';
input.style.borderRadius = '4px';
input.style.cursor = 'pointer';
$(input).spectrum({
color: input.value,
showInput: true,
showAlpha: true,
showPalette: false,
showSelectionPalette: true,
maxSelectionSize: 10,
preferredFormat: "hex",
localStorageKey: "spectrum.ui-design",
clickoutFiresChange: false,
show: function () {
// 缓存原始颜色值
colorCache.originalValue = input.value;
colorCache.currentInput = input;
colorCache.isEditing = true;
},
move: function (color) {
// 实时预览颜色变化
const colorString = color.toHexString();
updateFontColorFromSpectrum(fontId, property, colorString, input);
},
change: function (color) {
// 确认选择颜色
const colorString = color.toHexString();
updateFontColorFromSpectrum(fontId, property, colorString, input);
// 清空缓存
colorCache.originalValue = null;
colorCache.currentInput = null;
colorCache.isEditing = false;
},
hide: function () {
// 如果是取消操作,恢复原始颜色
if (colorCache.isEditing && colorCache.originalValue && colorCache.currentInput) {
updateFontColorFromSpectrum(fontId, property, colorCache.originalValue, colorCache.currentInput);
}
// 清空缓存
colorCache.originalValue = null;
colorCache.currentInput = null;
colorCache.isEditing = false;
}
});
});
}
// 从字体颜色选择器更新颜色
function updateFontColorFromSpectrum(fontId, property, colorString, input) {
// 更新颜色值显示
const colorValue = input.parentElement.querySelector('.font-color-value');
if (colorValue) {
colorValue.textContent = colorString.toUpperCase();
}
// 更新字体预览颜色
const fontItem = input.closest('.font-item');
const preview = fontItem.querySelector('.font-preview');
preview.style.color = colorString;
// 更新数据
const font = uiDesignData.typography.find(f => f.enName === fontId);
if (font) {
font[property] = colorString;
generateDynamicCSS();
// 实时更新手机预览区域
updatePhonePreviewColors();
}
}
// 创建颜色项
function createColorItem(colorData, type) {
const div = document.createElement('div');
div.className = 'color-item';
div.dataset.colorId = colorData.enName;
// 为功能色系统添加颜色预览背景
const isFunctionalColor = type === 'functional';
const nameInputWrapper = isFunctionalColor ?
`<div class="color-name-preview" style="background-color: ${colorData.value}; padding: 8px; border-radius: 6px; margin-bottom: 6px;">
<input type="text" class="color-name" value="${colorData.enName}" onchange="updateColorEnName(this, '${colorData.enName}', '${type}')" style="background: transparent; border: none; outline: none; color: white; font-weight: 600; margin-right: 3px;">
<input type="text" class="color-cn-name" value="${colorData.cnName}" onchange="updateColorCnName(this, '${colorData.enName}', '${type}')" style="background: transparent; border: none; outline: none; color: white; font-weight: 600;">
</div>` :
`<div style="display: flex; gap: 3px; flex-wrap: nowrap; overflow: hidden;">
<input type="text" class="color-name" value="${colorData.enName}" onchange="updateColorEnName(this, '${colorData.enName}', '${type}')" style="background: transparent; border: none; outline: none; font-size: inherit; font-weight: inherit; color: inherit; width: auto; min-width: 30px; max-width: 150px; flex-shrink: 1;">
<input type="text" class="color-cn-name" value="${colorData.cnName}" onchange="updateColorCnName(this, '${colorData.enName}', '${type}')" style="background: transparent; border: none; outline: none; font-size: inherit; font-weight: inherit; color: inherit; width: auto; min-width: 30px; max-width: 100px; flex-shrink: 1;">
</div>`;
div.innerHTML = `
<div class="color-name-row">
${nameInputWrapper}
</div>
<div class="color-controls-row">
<input type="color" class="spectrum-color-picker" value="${colorData.value}" data-color-id="${colorData.enName}" data-color-type="${type}" style="width: 32px; height: 24px; border: none; border-radius: 4px; cursor: pointer;">
<div class="color-value">${colorData.value}</div>
<button class="delete-btn" onclick="deleteColorItem(this, '${colorData.enName}', '${type}')">删除</button>
</div>
<div class="color-usage-row">
<textarea class="color-usage" onchange="updateColorUsage(this, '${colorData.enName}', '${type}')" placeholder="用途描述">${colorData.usage}</textarea>
</div>
`;
return div;
}
// 渲染字体
function renderTypography() {
const container = document.getElementById('font-demo');
container.innerHTML = '';
uiDesignData.typography.forEach(font => {
const fontItem = createFontItem(font);
container.appendChild(fontItem);
});
// 初始化字体颜色选择器和自适应输入框
setTimeout(() => {
initFontColorPickers();
initAutoResizeInputs();
}, 100);
}
// 创建字体项
function createFontItem(fontData) {
const div = document.createElement('div');
div.className = 'font-item';
div.dataset.fontId = fontData.enName;
const isBold = fontData.weight >= 600;
div.innerHTML = `
<div class="font-preview" style="font-size: ${fontData.size}px; font-weight: ${fontData.weight}; color: ${fontData.color};">
<input type="text" value="${fontData.cnName}" onchange="updateFontCnName(this, '${fontData.enName}')" style="background: transparent; border: none; outline: none; font-size: inherit; font-weight: inherit; color: inherit; width: 100%;">
</div>
<div class="font-controls">
<input type="text" class="color-name" value="${fontData.enName}" onchange="updateFontEnName(this, '${fontData.enName}')" style="width: auto; min-width: 40px; max-width: 100px; padding: 3px 6px; border: 1px solid var(--border-color); border-radius: 3px; font-size: 11px; background: var(--input-bg); color: var(--text-primary); margin-right: 6px; outline: none;">
<input type="number" class="size-input" value="${fontData.size}" onchange="updateFontSize(this, '${fontData.enName}')" min="8" max="72">
<span>px</span>
<input type="color" class="spectrum-color-picker color-input" value="${fontData.color}" data-font-id="${fontData.enName}" data-property="color">
<span class="font-color-value">${fontData.color}</span>
<div class="font-bold-checkbox">
<input type="checkbox" ${isBold ? 'checked' : ''} onchange="updateFontWeight(this, '${fontData.enName}')">
<span>粗体</span>
</div>
<button class="delete-btn" onclick="deleteFontItem(this, '${fontData.enName}')">删除</button>
</div>
`;
return div;
}
// 渲染间距
function renderSpacing() {
const container = document.getElementById('spacing-demo');
container.innerHTML = '';
uiDesignData.spacing.forEach(spacing => {
const spacingItem = createSpacingItem(spacing);
container.appendChild(spacingItem);
});
// 初始化自适应输入框
setTimeout(() => {
initAutoResizeInputs();
}, 100);
}
// 创建间距项
function createSpacingItem(spacingData) {
const div = document.createElement('div');
div.className = 'spacing-item';
div.dataset.spacingId = spacingData.enName;
div.innerHTML = `
<div class="spacing-preview" style="width: ${spacingData.value}px;"></div>
<input type="text" class="color-name" value="${spacingData.enName}" onchange="updateSpacingEnName(this, '${spacingData.enName}')" style="background: transparent; border: none; outline: none; font-size: inherit; color: inherit; width: auto; margin-right: 3px;">
<input type="text" class="color-cn-name" value="${spacingData.cnName}" onchange="updateSpacingCnName(this, '${spacingData.enName}')" style="background: transparent; border: none; outline: none; font-size: inherit; color: inherit; width: auto;">:
<input type="number" class="editable-input" value="${spacingData.value}" onchange="updateSpacing(this, '${spacingData.enName}')" min="1" max="100">
<span>px</span>
<button class="delete-btn" onclick="deleteSpacingItem(this, '${spacingData.enName}')">删除</button>
`;
return div;
}
// 更新页面标题
function updatePageTitle() {
document.querySelector('.header h1').textContent = uiDesignData.cnName;
}
// 自动调整输入框宽度
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.style.padding = '0';
tempSpan.style.margin = '0';
tempSpan.style.border = 'none';
tempSpan.textContent = input.value || input.placeholder || 'W';
document.body.appendChild(tempSpan);
const textWidth = tempSpan.offsetWidth;
document.body.removeChild(tempSpan);
// 设置输入框宽度,加上一些padding
const minWidth = parseInt(window.getComputedStyle(input).minWidth) || 30;
const maxWidth = parseInt(window.getComputedStyle(input).maxWidth) || 200;
const newWidth = Math.max(minWidth, Math.min(maxWidth, textWidth + 20));
input.style.width = newWidth + 'px';
}
// 全局事件监听器标记
let globalInputListenerAdded = false;
// 为所有自适应输入框添加事件监听器
function initAutoResizeInputs() {
// 只添加一次全局事件监听器
if (!globalInputListenerAdded) {
document.addEventListener('input', function (e) {
if (e.target.tagName === 'INPUT' && e.target.type === 'text') {
autoResizeInput(e.target);
}
});
globalInputListenerAdded = true;
}
// 初始化时调整所有文本输入框
document.querySelectorAll('input[type="text"]').forEach(input => {
autoResizeInput(input);
});
}
// 添加颜色项
function addColorItem(type) {
const newColor = {
enName: 'custom-' + Date.now(),
cnName: '新颜色',
value: '#000000',
usage: '用途说明'
};
uiDesignData.colors[type].push(newColor);
renderColorSection(type, type + '-colors');
}
// 删除颜色项
async function deleteColorItem(button, colorId, type) {
const confirmed = await showCustomConfirm('确定要删除这个颜色项吗?此操作无法撤销。');
if (!confirmed) return;
const index = uiDesignData.colors[type].findIndex(c => c.enName === colorId);
if (index > -1) {
uiDesignData.colors[type].splice(index, 1);
renderColorSection(type, type + '-colors');
}
}
// 更新颜色英文名称
function updateColorEnName(input, colorId, type) {
const color = uiDesignData.colors[type].find(c => c.enName === colorId);
if (color) {
color.enName = input.value;
// 更新dataset
const colorItem = input.closest('.color-item');
colorItem.dataset.colorId = input.value;
}
}
// 更新颜色中文名称
function updateColorCnName(input, colorId, type) {
const color = uiDesignData.colors[type].find(c => c.enName === colorId);
if (color) {
color.cnName = input.value;
}
}
// 更新颜色用途
function updateColorUsage(input, colorId, type) {
const color = uiDesignData.colors[type].find(c => c.enName === colorId);
if (color) {
color.usage = input.value;
}
}
// 更新字体大小
function updateFontSize(input, fontId) {
const fontItem = input.closest('.font-item');
const preview = fontItem.querySelector('.font-preview');
preview.style.fontSize = input.value + 'px';
const font = uiDesignData.typography.find(f => f.enName === fontId);
if (font) {
font.size = parseInt(input.value);
// 重新生成CSS
generateDynamicCSS();
// 更新手机预览
updatePhonePreviewColors();
}
}
// 更新字体颜色
function updateFontColor(input, fontId) {
const fontItem = input.closest('.font-item');
const preview = fontItem.querySelector('.font-preview');
const colorValue = fontItem.querySelector('.font-color-value');
preview.style.color = input.value;
colorValue.textContent = input.value.toUpperCase();
const font = uiDesignData.typography.find(f => f.enName === fontId);
if (font) {
font.color = input.value;
// 重新生成CSS
generateDynamicCSS();
// 更新手机预览
updatePhonePreviewColors();
}
}
// 更新字体英文名称
function updateFontEnName(input, fontId) {
const font = uiDesignData.typography.find(f => f.enName === fontId);
if (font) {
font.enName = input.value;
// 更新dataset
const fontItem = input.closest('.font-item');
fontItem.dataset.fontId = input.value;
}
}
// 更新字体中文名称
function updateFontCnName(input, fontId) {
const font = uiDesignData.typography.find(f => f.enName === fontId);
if (font) {
font.cnName = input.value;
}
}
// 更新按钮英文名称
function updateButtonEnName(input, buttonId) {
const button = uiDesignData.components.buttons.find(b => b.enName === buttonId);
if (button) {
button.enName = input.value;
// 更新dataset
const buttonItem = input.closest('.component-item');
buttonItem.dataset.buttonId = input.value;
}
}
// 更新按钮中文名称
function updateButtonCnName(input, buttonId) {
const button = uiDesignData.components.buttons.find(b => b.enName === buttonId);
if (button) {
button.cnName = input.value;
// 更新按钮显示文字
const buttonPreview = document.getElementById(`btn-preview-${buttonId}`);
if (buttonPreview) {
// 对于图标按钮,保持显示图标,其他按钮显示文字
if (buttonId === 'iconButton') {
buttonPreview.innerHTML = '<i class="fas fa-shopping-cart"></i>';
} else {
buttonPreview.innerHTML = input.value;
}
} else {
// 如果找不到按钮元素,重新渲染按钮组件
renderButtons();
}
// 更新手机预览区域
updatePhonePreviewColors();
}
}
// 更新输入框英文名称
function updateInputEnName(input, inputId) {
const inputData = uiDesignData.components.inputs.find(i => i.enName === inputId);
if (inputData) {
inputData.enName = input.value;
// 更新dataset
const inputItem = input.closest('.component-item');
inputItem.dataset.inputId = input.value;
}
}
// 更新输入框中文名称
function updateInputCnName(input, inputId) {
const inputData = uiDesignData.components.inputs.find(i => i.enName === inputId);
if (inputData) {
inputData.cnName = input.value;
// 更新手机预览区域
updatePhonePreviewColors();
}
}
// 更新Toast英文名称
function updateToastEnName(input, toastId) {
const toast = uiDesignData.components.toasts.find(t => t.enName === toastId);
if (toast) {
toast.enName = input.value;
// 更新dataset
const toastItem = input.closest('.component-item');
toastItem.dataset.toastId = input.value;
}
}
// 更新Toast中文名称
function updateToastCnName(input, toastId) {
const toast = uiDesignData.components.toasts.find(t => t.enName === toastId);
if (toast) {
toast.cnName = input.value;
// 更新Toast显示文字
const toastPreview = document.getElementById(`toast-preview-${toastId}`);
if (toastPreview) {
toastPreview.textContent = input.value;
}
// 更新手机预览区域
updatePhonePreviewColors();
}
}
// 更新间距英文名称
function updateSpacingEnName(input, spacingId) {
const spacing = uiDesignData.spacing.find(s => s.enName === spacingId);
if (spacing) {
spacing.enName = input.value;
// 更新dataset
const spacingItem = input.closest('.spacing-item');
spacingItem.dataset.spacingId = input.value;
}
}
// 更新间距中文名称
function updateSpacingCnName(input, spacingId) {
const spacing = uiDesignData.spacing.find(s => s.enName === spacingId);
if (spacing) {
spacing.cnName = input.value;
}
}
// 更新字体粗细
function updateFontWeight(input, fontId) {
const fontItem = input.closest('.font-item');
const preview = fontItem.querySelector('.font-preview');
const newWeight = input.checked ? 700 : 400;
preview.style.fontWeight = newWeight;
const font = uiDesignData.typography.find(f => f.enName === fontId);
if (font) {
font.weight = newWeight;
// 重新生成CSS
generateDynamicCSS();
// 更新手机预览
updatePhonePreviewColors();
}
}
// 添加字体项
function addFontItem() {
const newFont = {
enName: 'custom-font-' + Date.now(),
cnName: '新字体样式',
size: 16,
weight: 400,
color: '#1C1C1E',
usage: '自定义字体样式'
};
uiDesignData.typography.push(newFont);
renderTypography();
}
// 删除字体项
async function deleteFontItem(button, fontId) {
const confirmed = await showCustomConfirm('确定要删除这个字体项吗?此操作无法撤销。');
if (!confirmed) return;
const index = uiDesignData.typography.findIndex(f => f.enName === fontId);
if (index > -1) {
uiDesignData.typography.splice(index, 1);
renderTypography();
}
}
// 按钮状态切换
function toggleButtonState(checkbox, buttonId) {
const button = document.getElementById(`btn-preview-${buttonId}`);
button.disabled = checkbox.checked;
}
// 更新按钮属性
function updateButtonProperty(buttonId, state, property, value) {
const button = uiDesignData.components.buttons.find(b => b.enName === buttonId);
if (button) {
button[state][property] = value;
// 更新颜色值显示
const colorSpan = event.target.nextElementSibling;
if (colorSpan) {
colorSpan.textContent = value.toUpperCase();
}
generateDynamicCSS();
}
}
// 输入框状态切换
function toggleInputState(checkbox, inputId) {
const input = document.getElementById(`input-preview-${inputId}`);
input.disabled = checkbox.checked;
}
// 更新输入框属性
function updateInputProperty(inputId, state, property, value) {
const input = uiDesignData.components.inputs.find(i => i.enName === inputId);
if (input) {
input[state][property] = value;
// 更新颜色值显示
const colorSpan = event.target.nextElementSibling;
if (colorSpan) {
colorSpan.textContent = value.toUpperCase();
}
generateDynamicCSS();
}
}
// 更新Toast属性
function updateToastProperty(toastId, property, value) {
const toast = uiDesignData.components.toasts.find(t => t.enName === toastId);
if (toast) {
// 对数字类型属性进行类型转换
if (property === 'borderRadius' || property === 'minHeight' || property === 'maxWidth') {
toast[property] = parseInt(value);
} else {
toast[property] = value;
}
// 只对颜色属性更新显示值
if (property === 'backgroundColor' || property === 'textColor') {
const colorSpan = event.target.nextElementSibling;
if (colorSpan) {
colorSpan.textContent = value.toUpperCase();
}
}
generateDynamicCSS();
}
}
// 添加按钮组件
function addButtonComponent() {
const newButton = {
enName: 'button-' + Date.now(),
cnName: '新按钮',
normal: {
backgroundColor: '#007AFF',
textColor: '#FFFFFF',
hoverBackgroundColor: '#0056CC'
},
disabled: {
backgroundColor: '#E5E5EA',
textColor: '#8E8E93'
},
borderRadius: 8
};
uiDesignData.components.buttons.push(newButton);
renderButtons();
generateDynamicCSS();
}
// 删除按钮组件
async function deleteButtonComponent(buttonId) {
const confirmed = await showCustomConfirm('确定要删除这个按钮组件吗?此操作无法撤销。');
if (!confirmed) return;
const index = uiDesignData.components.buttons.findIndex(b => b.enName === buttonId);
if (index > -1) {
uiDesignData.components.buttons.splice(index, 1);
renderButtons();
generateDynamicCSS();
}
}
// 添加输入框组件
function addInputComponent() {
const newInput = {
enName: 'input-' + Date.now(),
cnName: '新输入框',
normal: {
backgroundColor: '#FFFFFF',
borderColor: '#E5E5EA',
textColor: '#1C1C1E',
placeholderColor: '#8E8E93',
focusBorderColor: '#007AFF'
},
disabled: {
backgroundColor: '#F2F2F7',
borderColor: '#E5E5EA',
textColor: '#8E8E93'
},
borderRadius: 8
};
uiDesignData.components.inputs.push(newInput);
renderInputs();
generateDynamicCSS();
}
// 删除输入框组件
async function deleteInputComponent(inputId) {
const confirmed = await showCustomConfirm('确定要删除这个输入框组件吗?此操作无法撤销。');
if (!confirmed) return;
const index = uiDesignData.components.inputs.findIndex(i => i.enName === inputId);
if (index > -1) {
uiDesignData.components.inputs.splice(index, 1);
renderInputs();
generateDynamicCSS();
}
}
// 添加Toast组件
function addToastComponent() {
const newToast = {
enName: 'toast-' + Date.now(),
cnName: '新提示',
backgroundColor: '#000000CF',
textColor: '#FFFFFF',
borderRadius: 20,
minHeight: 45,
maxWidth: 200
};
uiDesignData.components.toasts.push(newToast);
renderToasts();
generateDynamicCSS();
}
// 删除Toast组件
async function deleteToastComponent(toastId) {
const confirmed = await showCustomConfirm('确定要删除这个Toast组件吗?此操作无法撤销。');
if (!confirmed) return;
const index = uiDesignData.components.toasts.findIndex(t => t.enName === toastId);
if (index > -1) {
uiDesignData.components.toasts.splice(index, 1);
renderToasts();
generateDynamicCSS();
}
}
// 添加通用组件
function addGenericComponent(componentType) {
// 确保组件类型数组存在
if (!uiDesignData.components[componentType]) {
uiDesignData.components[componentType] = [];
}
let newComponent;
if (componentType === 'buttons') {
addButtonComponent();
return;
} else if (componentType === 'inputs') {
addInputComponent();
return;
} else if (componentType === 'toasts') {
addToastComponent();
return;
} else if (componentType === 'cards') {
newComponent = {
enName: 'card-' + Date.now(),
cnName: '新卡片',
backgroundColor: '#FFFFFF',
borderColor: '#F0F0F0',
borderRadius: 8,
hoverBackgroundColor: '#F8F8F8',
shadow: '0 2px 8px #00000015',
usage: '自定义卡片组件'
};
} else if (componentType === 'tagOrStatus') {
newComponent = {
enName: 'tagOrStatus-' + Date.now(),
cnName: '新标签或状态',
backgroundColor: '#F5F5F5',
textColor: '#333333',
borderColor: '#DDDDDD',
borderRadius: 4,
usage: '自定义状态组件'
};
} else {
// 通用组件模板
newComponent = {
enName: componentType + '-' + Date.now(),
cnName: '新组件',
backgroundColor: '#F5F5F5',
textColor: '#333333',
borderColor: '#DDDDDD',
borderRadius: 4,
usage: '自定义组件'
};
}
uiDesignData.components[componentType].push(newComponent);
renderComponents();
generateDynamicCSS();
}
// 删除通用组件
async function deleteGenericComponent(componentId, componentType) {
const confirmed = await showCustomConfirm(`确定要删除这个${componentType}组件吗?此操作无法撤销。`);
if (!confirmed) return;
if (uiDesignData.components[componentType]) {
const index = uiDesignData.components[componentType].findIndex(c => c.enName === componentId);
if (index > -1) {
uiDesignData.components[componentType].splice(index, 1);
renderComponents();
generateDynamicCSS();
}
}
}
// 更新通用组件名称
function updateGenericComponentName(input, componentId, componentType) {
if (uiDesignData.components[componentType]) {
const component = uiDesignData.components[componentType].find(c => c.enName === componentId);
if (component) {
component.cnName = input.value;
// 更新预览显示
const componentItem = input.closest('.component-item');
const preview = componentItem.querySelector('.card-preview, .tagOrStatus-preview, .generic-preview');
if (preview) {
preview.textContent = input.value;
}
}
}
}
// 更新通用组件英文名称
function updateGenericComponentEnName(input, componentId, componentType) {
if (uiDesignData.components[componentType]) {
const component = uiDesignData.components[componentType].find(c => c.enName === componentId);
if (component) {
const oldEnName = component.enName;
component.enName = input.value;
// 更新组件项的data属性
const componentItem = input.closest('.component-item');
componentItem.dataset.componentId = input.value;
}
}
}
// 更新通用组件中文名称
function updateGenericComponentCnName(input, componentId, componentType) {
if (uiDesignData.components[componentType]) {
const component = uiDesignData.components[componentType].find(c => c.enName === componentId);
if (component) {
component.cnName = input.value;
// 更新预览显示
const componentItem = input.closest('.component-item');
const preview = componentItem.querySelector('.card-preview, .tagOrStatus-preview, .generic-preview');
if (preview) {
preview.textContent = input.value;
}
// 更新手机预览区域
updatePhonePreviewColors();
}
}
}
// 更新通用组件属性
function updateGenericComponentProperty(componentId, componentType, property, value) {
if (uiDesignData.components[componentType]) {
const component = uiDesignData.components[componentType].find(c => c.enName === componentId);
if (component) {
// 对数字类型属性进行类型转换
if (typeof component[property] === 'number') {
component[property] = parseInt(value) || 0;
} else {
component[property] = value;
}
// 重新渲染该组件
renderComponents();
generateDynamicCSS();
}
}
}
// 更新间距
function updateSpacing(input, spacingId) {
const spacingItem = input.closest('.spacing-item');
const preview = spacingItem.querySelector('.spacing-preview');
preview.style.width = input.value + 'px';
const spacing = uiDesignData.spacing.find(s => s.enName === spacingId);
if (spacing) {
spacing.value = parseInt(input.value);
// 重新生成CSS
generateDynamicCSS();
// 实时更新手机预览区域
updatePhonePreviewColors();
}
}
// 添加间距项
function addSpacingItem() {
const newSpacing = {
enName: 'custom-spacing-' + Date.now(),
cnName: '自定义间距',
value: 16,
usage: '自定义间距说明'
};
uiDesignData.spacing.push(newSpacing);
renderSpacing();
}
// 删除间距项
async function deleteSpacingItem(button, spacingId) {
const confirmed = await showCustomConfirm('确定要删除这个间距项吗?此操作无法撤销。');
if (!confirmed) return;
const index = uiDesignData.spacing.findIndex(s => s.enName === spacingId);
if (index > -1) {
uiDesignData.spacing.splice(index, 1);
renderSpacing();
}
}
// 导出数据
function exportData() {
const dataStr = JSON.stringify(uiDesignData, 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 = 'ui-design-config.json';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
// 配置已导出为 ui-design-config.json 文件 (静默导出)
}
// 从字符串导入数据并初始化UI设计规范页面
function importDataFromString(jsonString) {
try {
const importedData = JSON.parse(jsonString);
// 验证数据结构
if (validateDataStructure(importedData)) {
uiDesignData = importedData;
renderAll();
console.log('UI设计规范数据导入成功');
return true;
} else {
console.error('导入失败:JSON格式不正确');
return false;
}
} catch (error) {
console.error('导入失败:', error.message);
return false;
}
}
// 导入数据
function importData(event) {
const file = event.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = function (e) {
try {
const importedData = JSON.parse(e.target.result);
// 验证数据结构
if (validateDataStructure(importedData)) {
uiDesignData = importedData;
renderAll();
// 配置导入成功 (静默导入)
} else {
console.error('导入失败:JSON格式不正确');
}
} catch (error) {
console.error('导入失败:', error.message);
}
};
reader.readAsText(file);
// 清空文件输入
event.target.value = '';
}
// 验证数据结构
function validateDataStructure(data) {
return data &&
data.colors &&
data.colors.brand &&
data.colors.functional &&
data.typography &&
data.spacing &&
data.components;
}
// 重置为默认
function resetToDefault() {
// 直接重置为默认配置 (静默重置)
location.reload();
}
// 保存配置功能
function saveConfig() {
try {
const dataStr = JSON.stringify(uiDesignData, null, 2);
console.log('💾 [uiDesign.html] 准备保存配置数据到VSCode');
// 通过VSCode webview API发送数据
if (vsCodeApi) {
vsCodeApi.postMessage({
command: 'saveConfig',
data: dataStr
});
console.log('✅ [uiDesign.html] 配置数据已发送到VSCode');
} else {
// 如果不在VSCode环境中,则下载文件作为备用方案
console.log('⚠️ [uiDesign.html] 不在VSCode环境中,使用下载备用方案');
const dataBlob = new Blob([dataStr], { type: 'application/json' });
const url = URL.createObjectURL(dataBlob);
const link = document.createElement('a');
link.href = url;
link.download = 'ui-design-config.json';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
}
// 显示保存成功提示
const saveButton = document.querySelector('.save-button');
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('.save-button');
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);
}
}
// 主题切换功能
function toggleTheme() {
const body = document.body;
const themeText = document.getElementById('theme-text');
const themeIcon = document.querySelector('.theme-toggle i');
if (body.getAttribute('data-theme') === 'light') {
// 切换到暗色主题
body.removeAttribute('data-theme');
themeText.textContent = '亮色模式';
themeIcon.className = 'fas fa-sun';
localStorage.setItem('theme', 'dark');
} else {
// 切换到亮色主题
body.setAttribute('data-theme', 'light');
themeText.textContent = '暗色模式';
themeIcon.className = 'fas fa-moon';
localStorage.setItem('theme', 'light');
}
}
// 初始化主题
function initTheme() {
const savedTheme = localStorage.getItem('theme');
const themeText = document.getElementById('theme-text');
const themeIcon = document.querySelector('.theme-toggle i');
if (savedTheme === 'light') {
document.body.setAttribute('data-theme', 'light');
themeText.textContent = '暗色模式';
themeIcon.className = 'fas fa-moon';
} else {
themeText.textContent = '亮色模式';
themeIcon.className = 'fas fa-sun';
}
}
// 颜色选择器缓存
let colorCache = {
originalValue: null,
currentInput: null,
isEditing: false
};
// 自定义确认删除弹框
let customConfirmCallback = null;
function showCustomConfirm(message) {
return new Promise((resolve) => {
customConfirmCallback = resolve;
document.getElementById('customConfirmMessage').textContent = message;
const overlay = document.getElementById('customConfirmOverlay');
// 动态应用UI规范数据的样式
updateConfirmDialogStyles();
overlay.classList.add('show');
});
}
function hideCustomConfirm(result) {
const overlay = document.getElementById('customConfirmOverlay');
overlay.classList.remove('show');
if (customConfirmCallback) {
customConfirmCallback(result);
customConfirmCallback = null;
}
}
// 更新确认弹框样式,使用当前UI规范数据
function updateConfirmDialogStyles() {
if (!uiDesignData.colors || !uiDesignData.components) return;
const overlay = document.getElementById('customConfirmOverlay');
const cancelBtn = overlay.querySelector('.custom-confirm-btn-cancel');
const confirmBtn = overlay.querySelector('.custom-confirm-btn-confirm');
// 应用遮罩颜色
const maskColor = uiDesignData.colors.common?.find(c => c.enName === 'mask')?.value || 'rgba(0, 0, 0, 0.5)';
overlay.style.backgroundColor = maskColor;
// 应用主按钮样式(确认按钮)
const primaryButton = uiDesignData.components.buttons?.find(b => b.enName === 'primary');
if (primaryButton && confirmBtn) {
confirmBtn.style.backgroundColor = primaryButton.normal.backgroundColor;
confirmBtn.style.color = primaryButton.normal.textColor;
confirmBtn.style.borderRadius = primaryButton.borderRadius + 'px';
confirmBtn.onmouseenter = () => {
confirmBtn.style.backgroundColor = primaryButton.normal.hoverBackgroundColor;
};
confirmBtn.onmouseleave = () => {
confirmBtn.style.backgroundColor = primaryButton.normal.backgroundColor;
};
}
// 应用次要按钮样式(取消按钮)
const secondaryButton = uiDesignData.components.buttons?.find(b => b.enName === 'secondary');
if (secondaryButton && cancelBtn) {
cancelBtn.style.backgroundColor = secondaryButton.normal.backgroundColor;
cancelBtn.style.color = secondaryButton.normal.textColor;
cancelBtn.style.borderRadius = secondaryButton.borderRadius + 'px';
cancelBtn.onmouseenter = () => {
cancelBtn.style.backgroundColor = secondaryButton.normal.hoverBackgroundColor;
};
cancelBtn.onmouseleave = () => {
cancelBtn.style.backgroundColor = secondaryButton.normal.backgroundColor;
};
}
}
// 同步功能色系统颜色到Toast组件背景色
function syncFunctionalColorsToToasts() {
if (!uiDesignData.colors || !uiDesignData.colors.functional || !uiDesignData.components || !uiDesignData.components.toasts) return;
// 同步成功色
const successColor = uiDesignData.colors.functional.find(c => c.enName === 'success');
const successToast = uiDesignData.components.toasts.find(t => t.enName === 'success');
if (successColor && successToast) {
successToast.backgroundColor = successColor.value;
}
// 同步警告色
const warningColor = uiDesignData.colors.functional.find(c => c.enName === 'warning');
const warningToast = uiDesignData.components.toasts.find(t => t.enName === 'warning');
if (warningColor && warningToast) {
warningToast.backgroundColor = warningColor.value;
}
// 同步错误色
const errorColor = uiDesignData.colors.functional.find(c => c.enName === 'error');
const errorToast = uiDesignData.components.toasts.find(t => t.enName === 'error');
if (errorColor && errorToast) {
errorToast.backgroundColor = errorColor.value;
}
}
// 手机预览功能
let isPhonePreviewVisible = false;
function togglePhonePreview() {
const overlay = document.getElementById('phonePreviewOverlay');
isPhonePreviewVisible = !isPhonePreviewVisible;
if (isPhonePreviewVisible) {
renderPhonePreview();
overlay.classList.add('show');
// 重置容器位置到初始状态
overlay.style.top = '0';
overlay.style.left = '';
overlay.style.right = '0';
} else {
overlay.classList.remove('show');
// 关闭时也重置位置,确保下次打开时从右边弹出
overlay.style.top = '0';
overlay.style.left = '';
overlay.style.right = '-280px';
}
}
function renderPhonePreview() {
const phoneContent = document.getElementById('phoneContent');
// 获取常用色系统的页面背景色
const pageBackgroundColor = uiDesignData.colors.common?.find(c => c.enName === 'page-bg')?.value || '#FFFFFF';
// 获取间距规范的页面左右间距
const pageSpacing = uiDesignData.spacing?.find(s => s.enName === 'medium')?.value || 16;
// 设置手机屏幕背景色
const phoneScreen = document.querySelector('.phone-screen');
phoneScreen.style.backgroundColor = pageBackgroundColor;
// 设置内容区域的左右间距
phoneContent.style.paddingLeft = pageSpacing + 'px';
phoneContent.style.paddingRight = pageSpacing + 'px';
// 获取第一个字体样式的颜色(正文内容颜色)
const bodyTextColor = uiDesignData.typography?.[0]?.color || '#1C1C1E';
let previewHTML = `
<div class="preview-header">
<div class="preview-title" style="color: ${bodyTextColor};">UI规范预览</div>
<div class="preview-subtitle" style="color: ${bodyTextColor};">展示设计规范的实际效果</div>
</div>
<div class="preview-section preview-card">
<div class="preview-section-title" style="color: ${bodyTextColor};">字体规范</div>`;
// 渲染字体规范
uiDesignData.typography.forEach(font => {
previewHTML += `
<div class="preview-typography" style="font-size: ${font.size}px; font-weight: ${font.weight}; color: ${font.color}; margin: 4px 0;">
${font.cnName} - ${font.size}px
</div>`;
});
previewHTML += `</div>`;
// 渲染间距规范
if (uiDesignData.spacing && uiDesignData.spacing.length > 0) {
previewHTML += `
<div class="preview-section preview-card">
<div class="preview-section-title" style="color: ${bodyTextColor};">间距规范</div>`;
uiDesignData.spacing.forEach(spacing => {
previewHTML += `
<div style="margin: 8px 0; display: flex; align-items: center;">
<div style="width: ${spacing.value}px; height: 20px; background-color: #FF6B35; margin-right: 8px;"></div>
<span style="color: ${bodyTextColor};">${spacing.cnName}: ${spacing.value}px</span>
</div>`;
});
previewHTML += `</div>`;
}
// 渲染按钮组件(包括常态和禁用状态)
if (uiDesignData.components.buttons.length > 0) {
previewHTML += `
<div class="preview-section preview-card">
<div class="preview-section-title" style="color: ${bodyTextColor};">按钮组件</div>
<div class="preview-buttons">`;
uiDesignData.components.buttons.forEach(button => {
// 将常态和禁用态按钮放在同一行
previewHTML += `
<div style="display: flex; gap: 8px; margin: 4px 0; justify-content: center;">
<button class="preview-button-demo"
style="background-color: ${button.normal.backgroundColor};
color: ${button.normal.textColor};
border-radius: ${button.borderRadius}px;
margin: 0;"
onclick="showPreviewDialog()">
${button.enName === 'iconButton' ? '<i class="fas fa-shopping-cart"></i>' : button.cnName}
</button>
<button class="preview-button-demo disabled"
style="background-color: ${button.disabled.backgroundColor};
color: ${button.disabled.textColor};
border-radius: ${button.borderRadius}px;
margin: 0;
opacity: 0.6;
cursor: not-allowed;"
disabled>
${button.enName === 'iconButton' ? '<i class="fas fa-shopping-cart"></i>' : button.cnName}
</button>
</div>`;
});
previewHTML += `</div></div>`;
}
// 渲染输入框组件(包括常态和禁用状态)
if (uiDesignData.components.inputs.length > 0) {
previewHTML += `
<div class="preview-section preview-card">
<div class="preview-section-title" style="color: ${bodyTextColor};">输入框组件</div>
<div class="preview-inputs">`;
uiDesignData.components.inputs.forEach(input => {
// 常态输入框
previewHTML += `
<input class="preview-input-demo input-${input.enName}"
style="margin: 4px;
padding: 8px;
width: calc(100% - 20px);"
placeholder="${input.cnName}示例">
<br>`;
// 禁用状态输入框
previewHTML += `
<input class="preview-input-demo input-${input.enName}-disabled"
style="margin: 4px;
padding: 8px;
width: calc(100% - 20px);"
placeholder="${input.cnName}示例(禁用)"
disabled>
<br>`;
});
previewHTML += `</div></div>`;
}
// 渲染Toast组件
if (uiDesignData.components.toasts.length > 0) {
previewHTML += `
<div class="preview-section preview-card">
<div class="preview-section-title" style="color: ${bodyTextColor};">Toast组件</div>
<div class="preview-toasts">`;
uiDesignData.components.toasts.forEach(toast => {
previewHTML += `
<div class="preview-toast-demo toast-${toast.enName}"
style="margin: 4px;
padding: 8px;
min-height: ${toast.minHeight}px;
max-width: ${toast.maxWidth}px;">
${toast.cnName}
</div>`;
});
previewHTML += `</div></div>`;
}
// 渲染卡片组件
if (uiDesignData.components.cards && uiDesignData.components.cards.length > 0) {
previewHTML += `
<div class="preview-section preview-card">
<div class="preview-section-title" style="color: ${bodyTextColor};">卡片组件</div>
<div class="preview-cards">`;
uiDesignData.components.cards.forEach(card => {
previewHTML += `
<div class="preview-card-demo card-${card.enName}"
style="margin: 8px 0;
padding: 16px;
background-color: ${card.backgroundColor};
border: 1px solid ${card.borderColor};
border-radius: ${card.borderRadius}px;
box-shadow: ${card.shadow};
transition: all 0.3s ease;
cursor: pointer;">
<div style="font-weight: 600; margin-bottom: 8px; color: ${bodyTextColor};">${card.cnName}</div>
<div style="color: ${bodyTextColor}; opacity: 0.8;">${card.usage}</div>
</div>`;
});
previewHTML += `</div></div>`;
}
// 渲染标签/状态组件
if (uiDesignData.components.tagOrStatus && uiDesignData.components.tagOrStatus.length > 0) {
previewHTML += `
<div class="preview-section preview-card">
<div class="preview-section-title" style="color: ${bodyTextColor};">标签/状态组件</div>
<div class="preview-tags" style="display: flex; flex-wrap: wrap; gap: 8px;">`;
uiDesignData.components.tagOrStatus.forEach(tag => {
previewHTML += `
<div class="preview-tag-demo tag-${tag.enName}"
style="display: inline-block;
padding: 4px 8px;
background-color: ${tag.backgroundColor};
color: ${tag.textColor};
border: 1px solid ${tag.borderColor};
border-radius: ${tag.borderRadius}px;
font-size: 12px;
white-space: nowrap;">
${tag.cnName}
</div>`;
});
previewHTML += `</div></div>`;
}
phoneContent.innerHTML = previewHTML;
// 更新预览对话框样式
updatePreviewDialogStyles();
// 初始化时应用当前颜色
setTimeout(() => {
updatePhonePreviewColors();
}, 100);
}
function showPreviewDialog() {
const dialogOverlay = document.getElementById('previewDialogOverlay');
dialogOverlay.classList.add('show');
}
function hidePreviewDialog() {
const dialogOverlay = document.getElementById('previewDialogOverlay');
dialogOverlay.classList.remove('show');
}
function updatePreviewDialogStyles() {
const dialogOverlay = document.getElementById('previewDialogOverlay');
if (!dialogOverlay) return;
const dialog = dialogOverlay.querySelector('.preview-dialog');
const dialogButton = dialogOverlay.querySelector('.preview-dialog-button');
// 应用遮罩颜色(在手机预览内部使用)
const maskColor = uiDesignData.colors.common?.find(c => c.enName === 'mask')?.value || 'rgba(0, 0, 0, 0.5)';
dialogOverlay.style.backgroundColor = maskColor;
// 应用卡片背景色到弹框
const cardBackgroundColor = uiDesignData.colors.common?.find(c => c.enName === 'card-bg')?.value || '#FFFFFF';
if (dialog) {
dialog.style.backgroundColor = cardBackgroundColor;
}
// 应用正文内容颜色到弹框文字
const bodyTextColor = uiDesignData.typography?.[0]?.color || '#1C1C1E';
const dialogTitle = dialogOverlay.querySelector('.preview-dialog-title');
const dialogMessage = dialogOverlay.querySelector('.preview-dialog-message');
if (dialogTitle) {
dialogTitle.style.color = bodyTextColor;
}
if (dialogMessage) {
dialogMessage.style.color = bodyTextColor;
}
// 应用主按钮样式
const primaryButton = uiDesignData.components.buttons?.find(b => b.enName === 'primary');
if (primaryButton && dialogButton) {
dialogButton.style.backgroundColor = primaryButton.normal.backgroundColor;
dialogButton.style.color = primaryButton.normal.textColor;
dialogButton.style.borderRadius = primaryButton.borderRadius + 'px';
// 添加悬停效果
dialogButton.onmouseenter = function () {
this.style.backgroundColor = primaryButton.normal.hoverBackgroundColor;
};
dialogButton.onmouseleave = function () {
this.style.backgroundColor = primaryButton.normal.backgroundColor;
};
}
}
// 实时更新手机预览区域的颜色
function updatePhonePreviewColors() {
// 只有在手机预览显示时才更新
if (!isPhonePreviewVisible) return;
const phoneContent = document.getElementById('phoneContent');
if (!phoneContent) return;
// 更新页面背景色
const pageBackgroundColor = uiDesignData.colors.common?.find(c => c.enName === 'page-bg')?.value || '#FFFFFF';
const phoneScreen = document.querySelector('.phone-screen');
if (phoneScreen) {
phoneScreen.style.backgroundColor = pageBackgroundColor;
}
// 更新页面左右间距
const pageLeftRightSpacing = uiDesignData.spacing?.find(s => s.cnName === '页面左右间距' || s.enName === 'page-horizontal-padding')?.value || 16;
phoneContent.style.paddingLeft = pageLeftRightSpacing + 'px';
phoneContent.style.paddingRight = pageLeftRightSpacing + 'px';
// 更新页面上下间距(如果存在)
const pageVerticalSpacing = uiDesignData.spacing?.find(s => s.cnName === '页面上下间距' || s.enName === 'page-vertical-padding')?.value || 16;
phoneContent.style.paddingTop = pageVerticalSpacing + 'px';
phoneContent.style.paddingBottom = pageVerticalSpacing + 'px';
// 更新预览header的背景色和文字颜色(品牌色渐变)
const primaryColor = uiDesignData.colors.brand?.find(c => c.enName === 'primary');
const secondaryColor = uiDesignData.colors.brand?.find(c => c.enName === 'secondary');
const primaryHover = uiDesignData.colors.brand?.find(c => c.enName === 'primaryHover');
const secondaryHover = uiDesignData.colors.brand?.find(c => c.enName === 'secondaryHover');
const primaryBgText = uiDesignData.colors.brand?.find(c => c.enName === 'primaryBgText');
const secondaryBgText = uiDesignData.colors.brand?.find(c => c.enName === 'secondaryBgText');
const previewHeader = phoneContent.querySelector('.preview-header');
if (previewHeader && primaryColor && secondaryColor && primaryBgText && secondaryBgText) {
previewHeader.style.background = `linear-gradient(135deg, ${primaryColor.value}, ${secondaryColor.value})`;
previewHeader.style.transition = 'background 0.3s ease';
// 更新header文字颜色
const previewTitle = previewHeader.querySelector('.preview-title');
const previewSubtitle = previewHeader.querySelector('.preview-subtitle');
if (previewTitle) {
previewTitle.style.color = primaryBgText.value;
previewTitle.style.transition = 'color 0.3s ease';
}
if (previewSubtitle) {
previewSubtitle.style.color = secondaryBgText.value;
previewSubtitle.style.transition = 'color 0.3s ease';
}
// 添加hover效果
previewHeader.onmouseenter = function () {
this.style.background = `linear-gradient(135deg, ${primaryHover?.value || primaryColor.value}, ${secondaryHover?.value || secondaryColor.value})`;
};
previewHeader.onmouseleave = function () {
this.style.background = `linear-gradient(135deg, ${primaryColor.value}, ${secondaryColor.value})`;
};
}
// 更新所有div文本颜色(除了preview-typography)
const bodyTextColor = uiDesignData.typography?.[0]?.color || '#1C1C1E';
const textElements = phoneContent.querySelectorAll('.preview-title, .preview-subtitle, .preview-section-title, span');
textElements.forEach(element => {
if (!element.classList.contains('preview-typography')) {
element.style.color = bodyTextColor;
}
});
// 更新弹框文字颜色
const dialogTitle = phoneContent.querySelector('.preview-dialog-title');
const dialogMessage = phoneContent.querySelector('.preview-dialog-message');
if (dialogTitle) {
dialogTitle.style.color = bodyTextColor;
}
if (dialogMessage) {
dialogMessage.style.color = bodyTextColor;
}
// 更新字体颜色和样式
const typographyElements = phoneContent.querySelectorAll('.preview-typography');
typographyElements.forEach((element, index) => {
if (uiDesignData.typography[index]) {
const fontData = uiDesignData.typography[index];
element.style.color = fontData.color;
element.style.fontSize = fontData.size + 'px';
element.style.fontWeight = fontData.weight;
// 更新显示的文字内容
element.textContent = `${fontData.cnName} - ${fontData.size}px`;
}
});
// 更新按钮颜色(包括常态和禁用状态)
const buttonElements = phoneContent.querySelectorAll('.preview-button-demo');
buttonElements.forEach((element, index) => {
// 每两个按钮为一组(常态+禁用),计算对应的按钮数据索引
const buttonDataIndex = Math.floor(index / 2);
if (uiDesignData.components.buttons[buttonDataIndex]) {
const buttonData = uiDesignData.components.buttons[buttonDataIndex];
if (element.classList.contains('disabled')) {
// 禁用状态
element.style.backgroundColor = buttonData.disabled.backgroundColor;
element.style.color = buttonData.disabled.textColor;
element.style.cursor = 'not-allowed';
element.style.opacity = '0.6';
} else {
// 常态
element.style.backgroundColor = buttonData.normal.backgroundColor;
element.style.color = buttonData.normal.textColor;
element.style.cursor = 'pointer';
element.style.opacity = '1';
// 添加悬停效果
element.onmouseenter = function () {
this.style.backgroundColor = buttonData.normal.hoverBackgroundColor;
};
element.onmouseleave = function () {
this.style.backgroundColor = buttonData.normal.backgroundColor;
};
}
element.style.borderRadius = buttonData.borderRadius + 'px';
element.style.transition = 'all 0.3s ease';
// 为图标按钮添加特殊样式
if (buttonData.enName === 'iconButton') {
element.style.minWidth = '48px';
element.style.width = '48px';
element.style.height = '48px';
element.style.display = 'flex';
element.style.alignItems = 'center';
element.style.justifyContent = 'center';
element.style.fontSize = '16px';
element.style.padding = '0';
} else {
element.style.minWidth = '120px';
element.style.display = 'flex';
element.style.alignItems = 'center';
element.style.justifyContent = 'center';
}
}
});
// 更新输入框颜色(包括常态、聚焦和禁用状态)
const inputElements = phoneContent.querySelectorAll('.preview-input-demo');
inputElements.forEach((element, index) => {
if (uiDesignData.components.inputs[Math.floor(index / 2)]) {
const inputData = uiDesignData.components.inputs[Math.floor(index / 2)];
// 确保disabled字段存在,如果不存在则使用默认值
if (!inputData.disabled) {
inputData.disabled = {
backgroundColor: '#F5F5F5',
borderColor: '#D9D9D9',
textColor: '#8C8C8C'
};
}
if (element.hasAttribute('disabled')) {
// 禁用状态
element.style.backgroundColor = inputData.disabled.backgroundColor;
element.style.borderColor = inputData.disabled.borderColor;
element.style.color = inputData.disabled.textColor;
element.style.cursor = 'not-allowed';
} else {
// 常态
element.style.backgroundColor = inputData.normal.backgroundColor;
element.style.borderColor = inputData.normal.borderColor;
element.style.color = inputData.normal.textColor;
element.style.cursor = 'text';
element.disabled = false;
// 添加聚焦效果
element.onfocus = function () {
this.style.borderColor = '#404040';
};
element.onblur = function () {
this.style.borderColor = inputData.normal.borderColor;
};
}
element.style.borderRadius = inputData.borderRadius + 'px';
element.style.transition = 'all 0.3s ease';
element.style.border = '1px solid';
}
});
// 更新Toast颜色
const toastElements = phoneContent.querySelectorAll('.preview-toast-demo');
toastElements.forEach((element, index) => {
if (uiDesignData.components.toasts[index]) {
const toastData = uiDesignData.components.toasts[index];
element.style.backgroundColor = toastData.backgroundColor;
element.style.color = toastData.textColor;
element.style.borderRadius = toastData.borderRadius + 'px';
element.style.minHeight = toastData.minHeight + 'px';
element.style.maxWidth = toastData.maxWidth + 'px';
}
});
// 更新卡片背景色(预览区域的卡片容器)
const cardElements = phoneContent.querySelectorAll('.preview-card');
const cardBackgroundColor = uiDesignData.colors.common?.find(c => c.enName === 'card-bg')?.value || '#FFFFFF';
cardElements.forEach(element => {
element.style.backgroundColor = cardBackgroundColor;
});
// 更新卡片组件样式
const cardDemoElements = phoneContent.querySelectorAll('.preview-card-demo');
cardDemoElements.forEach((element, index) => {
if (uiDesignData.components.cards && uiDesignData.components.cards[index]) {
const cardData = uiDesignData.components.cards[index];
element.style.backgroundColor = cardData.backgroundColor;
element.style.borderColor = cardData.borderColor;
element.style.borderRadius = cardData.borderRadius + 'px';
element.style.boxShadow = cardData.shadow;
// 添加悬停效果
element.onmouseenter = function () {
this.style.backgroundColor = cardData.hoverBackgroundColor;
};
element.onmouseleave = function () {
this.style.backgroundColor = cardData.backgroundColor;
};
// 更新卡片内文字颜色
const cardTitle = element.querySelector('div:first-child');
const cardDesc = element.querySelector('div:last-child');
if (cardTitle) cardTitle.style.color = bodyTextColor;
if (cardDesc) cardDesc.style.color = bodyTextColor;
}
});
// 更新标签/状态组件样式
const tagDemoElements = phoneContent.querySelectorAll('.preview-tag-demo');
tagDemoElements.forEach((element, index) => {
if (uiDesignData.components.tagOrStatus && uiDesignData.components.tagOrStatus[index]) {
const tagData = uiDesignData.components.tagOrStatus[index];
element.style.backgroundColor = tagData.backgroundColor;
element.style.color = tagData.textColor;
element.style.borderColor = tagData.borderColor;
element.style.borderRadius = tagData.borderRadius + 'px';
element.textContent = tagData.cnName;
}
});
// 更新预览对话框样式(使用卡片背景色)
updatePreviewDialogStyles();
}
// 手机预览拖拽功能
let isDragging = false;
let dragStartX = 0;
let dragStartY = 0;
let phoneStartX = 0;
let phoneStartY = 0;
function initPhoneDrag() {
const phoneFrame = document.querySelector('.phone-frame');
const container = document.getElementById('phonePreviewOverlay');
if (!phoneFrame || !container) return;
phoneFrame.addEventListener('mousedown', function (e) {
// 避免在关闭按钮上触发拖拽
if (e.target.classList.contains('phone-close-btn')) return;
isDragging = true;
container.classList.add('dragging');
dragStartX = e.clientX;
dragStartY = e.clientY;
// 获取容器的当前位置
const containerRect = container.getBoundingClientRect();
phoneStartX = containerRect.left;
phoneStartY = containerRect.top;
e.preventDefault();
});
document.addEventListener('mousemove', function (e) {
if (!isDragging) return;
const deltaX = e.clientX - dragStartX;
const deltaY = e.clientY - dragStartY;
const newX = phoneStartX + deltaX;
const newY = phoneStartY + deltaY;
// 直接移动整个容器
container.style.left = newX + 'px';
container.style.top = newY + 'px';
});
document.addEventListener('mouseup', function () {
if (isDragging) {
isDragging = false;
container.classList.remove('dragging');
}
});
}
// 页面加载完成后的初始化
document.addEventListener('DOMContentLoaded', function () {
console.log('UI设计规范页面加载完成');
initTheme();
syncFunctionalColorsToToasts();
renderAll();
// 初始化手机拖拽功能
setTimeout(() => {
initPhoneDrag();
}, 500);
});
// 全局VSCode API实例
let vsCodeApi = null;
// VSCode webview消息监听器
if (typeof acquireVsCodeApi !== 'undefined') {
vsCodeApi = acquireVsCodeApi();
window.addEventListener('message', event => {
const message = event.data;
console.log('📨 [uiDesign.html] 收到VSCode消息:', message);
switch (message.command) {
case 'importUiDesignData':
try {
console.log('📥 [uiDesign.html] 开始导入配置数据');
if (typeof importDataFromString === 'function' && message.data) {
importDataFromString(message.data);
console.log('✅ [uiDesign.html] 配置数据导入成功');
} else {
console.warn('⚠️ [uiDesign.html] importDataFromString方法不存在或数据为空');
}
} catch (error) {
console.error('❌ [uiDesign.html] 导入配置数据失败:', error);
}
break;
}
});
}
</script>
</body>
</html>