文章目录[隐藏]
实战教程:开发网站内嵌的在线Logo设计与名片制作工具
摘要
在当今数字化时代,企业网站的功能需求日益多样化。本文将详细介绍如何通过WordPress程序的代码二次开发,实现网站内嵌的在线Logo设计与名片制作工具。我们将从需求分析、技术选型、开发流程到最终部署,全面解析这一实用功能的实现过程,帮助网站管理员和技术开发者扩展网站功能,提升用户体验。
一、项目背景与需求分析
1.1 为什么需要内嵌设计工具
随着中小企业数字化转型加速,越来越多的企业主希望在自己的网站上直接完成品牌视觉元素的创建。传统方式下,用户需要离开网站使用第三方设计工具,这不仅打断了用户体验流程,还可能导致潜在客户的流失。
内嵌设计工具的优势:
- 提升用户停留时间:用户在网站内完成设计,增加互动时长
- 增强品牌专业性:展示网站功能的全面性
- 降低使用门槛:无需安装额外软件或注册新平台
- 数据自主控制:所有设计数据保留在自有服务器
1.2 功能需求定义
基于实际应用场景,我们需要实现以下核心功能:
Logo设计工具需求:
- 基础形状库(圆形、方形、三角形等几何图形)
- 图标库(分类图标资源)
- 文本编辑功能(字体、大小、颜色、间距调整)
- 图层管理系统
- 颜色选择器与调色板
- 导出功能(支持PNG、SVG格式)
名片制作工具需求:
- 标准名片尺寸模板(90×54mm)
- 企业信息字段(姓名、职位、联系方式等)
- 双面设计支持
- 印刷出血线提示
- 多种设计风格模板
- 导出打印质量PDF
通用需求:
- 响应式设计,适配各种设备
- 用户作品保存与加载
- 设计分享功能
- 水印添加选项(免费版)
二、技术架构与开发环境
2.1 技术选型
前端技术栈:
- HTML5 Canvas:核心绘图引擎
- Fabric.js:强大的Canvas操作库
- React/Vue.js:可选前端框架(本教程使用原生JavaScript)
- Bootstrap 5:响应式UI框架
- Coloris:轻量级颜色选择器
- FileSaver.js:客户端文件保存
后端技术栈:
- WordPress核心:内容管理系统
- PHP 7.4+:服务器端逻辑
- MySQL 5.7+:数据存储
- WordPress REST API:前后端通信
- GD库/ImageMagick:图像处理
2.2 开发环境搭建
-
本地WordPress安装
# 使用Docker快速搭建WordPress环境 docker-compose up -d -
开发工具准备
- 代码编辑器:VS Code
- 浏览器开发者工具
- Git版本控制
- 本地测试服务器
-
WordPress主题结构
/wp-content/themes/your-theme/ ├── assets/ │ ├── css/ │ ├── js/ │ └── images/ ├── template-parts/ ├── functions.php └── style.css
三、Logo设计工具开发实战
3.1 基础HTML结构
首先创建Logo设计工具的前端界面:
<!-- logo-designer.php -->
<div class="logo-designer-container">
<div class="designer-header">
<h2>在线Logo设计工具</h2>
<div class="toolbar">
<button id="save-logo" class="btn btn-primary">保存设计</button>
<button id="export-png" class="btn btn-success">导出PNG</button>
<button id="export-svg" class="btn btn-info">导出SVG</button>
</div>
</div>
<div class="designer-body">
<!-- 左侧工具栏 -->
<div class="sidebar-left">
<div class="tool-section">
<h4>形状</h4>
<div class="shape-list">
<button class="shape-btn" data-type="rectangle">矩形</button>
<button class="shape-btn" data-type="circle">圆形</button>
<button class="shape-btn" data-type="triangle">三角形</button>
</div>
</div>
<div class="tool-section">
<h4>图标库</h4>
<div class="icon-grid" id="icon-library">
<!-- 图标将通过AJAX加载 -->
</div>
</div>
</div>
<!-- 中央画布区域 -->
<div class="canvas-area">
<canvas id="logo-canvas" width="800" height="600"></canvas>
<div class="canvas-controls">
<button id="clear-canvas">清空画布</button>
<button id="undo-action">撤销</button>
<button id="redo-action">重做</button>
</div>
</div>
<!-- 右侧属性面板 -->
<div class="sidebar-right">
<div class="properties-panel">
<h4>属性设置</h4>
<div class="property-group">
<label>填充颜色</label>
<input type="color" id="fill-color" value="#3498db">
</div>
<div class="property-group">
<label>边框颜色</label>
<input type="color" id="stroke-color" value="#2c3e50">
</div>
<div class="property-group">
<label>字体</label>
<select id="font-family">
<option value="Arial">Arial</option>
<option value="Helvetica">Helvetica</option>
<option value="Times New Roman">Times New Roman</option>
</select>
</div>
</div>
<div class="layers-panel">
<h4>图层管理</h4>
<ul id="layers-list">
<!-- 图层列表动态生成 -->
</ul>
</div>
</div>
</div>
</div>
3.2 Canvas初始化与Fabric.js集成
// assets/js/logo-designer.js
document.addEventListener('DOMContentLoaded', function() {
// 初始化Canvas
const canvas = new fabric.Canvas('logo-canvas', {
backgroundColor: '#ffffff',
preserveObjectStacking: true
});
// 设置画布尺寸
canvas.setDimensions({
width: 800,
height: 600
});
// 形状添加功能
document.querySelectorAll('.shape-btn').forEach(button => {
button.addEventListener('click', function() {
const shapeType = this.getAttribute('data-type');
addShapeToCanvas(shapeType);
});
});
// 添加形状函数
function addShapeToCanvas(type) {
let shape;
const fillColor = document.getElementById('fill-color').value;
const strokeColor = document.getElementById('stroke-color').value;
switch(type) {
case 'rectangle':
shape = new fabric.Rect({
left: 100,
top: 100,
fill: fillColor,
width: 100,
height: 100,
stroke: strokeColor,
strokeWidth: 2
});
break;
case 'circle':
shape = new fabric.Circle({
left: 100,
top: 100,
radius: 50,
fill: fillColor,
stroke: strokeColor,
strokeWidth: 2
});
break;
case 'triangle':
shape = new fabric.Triangle({
left: 100,
top: 100,
fill: fillColor,
width: 100,
height: 100,
stroke: strokeColor,
strokeWidth: 2
});
break;
}
if(shape) {
canvas.add(shape);
updateLayersList();
}
}
// 文本添加功能
document.getElementById('add-text').addEventListener('click', function() {
const text = new fabric.IText('编辑文本', {
left: 150,
top: 150,
fontFamily: document.getElementById('font-family').value,
fill: document.getElementById('fill-color').value,
fontSize: 40
});
canvas.add(text);
canvas.setActiveObject(text);
updateLayersList();
});
// 更新图层列表
function updateLayersList() {
const layersList = document.getElementById('layers-list');
layersList.innerHTML = '';
canvas.getObjects().forEach((obj, index) => {
const li = document.createElement('li');
li.className = 'layer-item';
li.innerHTML = `
<span>${obj.type || '对象'}</span>
<div class="layer-actions">
<button class="btn-layer-up" data-index="${index}">上移</button>
<button class="btn-layer-down" data-index="${index}">下移</button>
<button class="btn-layer-delete" data-index="${index}">删除</button>
</div>
`;
layersList.appendChild(li);
});
}
// 导出PNG功能
document.getElementById('export-png').addEventListener('click', function() {
const dataURL = canvas.toDataURL({
format: 'png',
quality: 1
});
const link = document.createElement('a');
link.download = 'logo-design.png';
link.href = dataURL;
link.click();
});
// 初始化图标库
loadIconLibrary();
function loadIconLibrary() {
// 通过AJAX从服务器获取图标
fetch('/wp-json/logo-designer/v1/icons')
.then(response => response.json())
.then(icons => {
const iconGrid = document.getElementById('icon-library');
icons.forEach(icon => {
const iconElement = document.createElement('div');
iconElement.className = 'icon-item';
iconElement.innerHTML = `<i class="${icon.class}"></i>`;
iconElement.addEventListener('click', () => addIconToCanvas(icon));
iconGrid.appendChild(iconElement);
});
});
}
function addIconToCanvas(icon) {
// 添加图标到画布的逻辑
// 这里可以使用fabric.Path或fabric.Group来处理SVG图标
}
});
3.3 后端API开发
<?php
// functions.php - WordPress主题函数文件
// 注册REST API端点
add_action('rest_api_init', function() {
// 获取图标库
register_rest_route('logo-designer/v1', '/icons', array(
'methods' => 'GET',
'callback' => 'get_icon_library',
'permission_callback' => '__return_true'
));
// 保存设计
register_rest_route('logo-designer/v1', '/save', array(
'methods' => 'POST',
'callback' => 'save_logo_design',
'permission_callback' => function() {
return current_user_can('edit_posts');
}
));
});
// 获取图标库函数
function get_icon_library() {
$icons = array(
array('id' => 1, 'name' => '星星', 'class' => 'fas fa-star'),
array('id' => 2, 'name' => '爱心', 'class' => 'fas fa-heart'),
array('id' => 3, 'name' => '云朵', 'class' => 'fas fa-cloud'),
array('id' => 4, 'name' => '树叶', 'class' => 'fas fa-leaf'),
array('id' => 5, 'name' => '火焰', 'class' => 'fas fa-fire'),
array('id' => 6, 'name' => '水滴', 'class' => 'fas fa-tint'),
);
return rest_ensure_response($icons);
}
// 保存设计函数
function save_logo_design($request) {
$user_id = get_current_user_id();
$design_data = $request->get_param('design_data');
$design_name = $request->get_param('design_name');
if(empty($design_data) || empty($design_name)) {
return new WP_Error('invalid_data', '设计数据或名称为空', array('status' => 400));
}
// 保存到用户meta
$saved_designs = get_user_meta($user_id, 'logo_designs', true);
if(empty($saved_designs)) {
$saved_designs = array();
}
$new_design = array(
'id' => uniqid(),
'name' => sanitize_text_field($design_name),
'data' => $design_data,
'created_at' => current_time('mysql'),
'updated_at' => current_time('mysql')
);
$saved_designs[] = $new_design;
update_user_meta($user_id, 'logo_designs', $saved_designs);
return rest_ensure_response(array(
'success' => true,
'message' => '设计已保存',
'design_id' => $new_design['id']
));
}
// 添加快捷码支持
add_shortcode('logo_designer', 'render_logo_designer');
function render_logo_designer($atts) {
ob_start();
include get_template_directory() . '/template-parts/logo-designer.php';
return ob_get_clean();
}
?>
四、名片制作工具开发实战
4.1 名片设计器界面
<!-- business-card-designer.php -->
<div class="card-designer-container">
<div class="designer-header">
<h2>在线名片设计工具</h2>
<div class="template-selector">
<h4>选择模板</h4>
<div class="template-grid">
<div class="template-item" data-template="modern">
<div class="template-preview modern-template"></div>
<span>现代风格</span>
</div>
<div class="template-item" data-template="classic">
<div class="template-preview classic-template"></div>
<span>经典风格</span>
</div>
<div class="template-item" data-template="minimal">
<div class="template-preview minimal-template"></div>
<span>极简风格</span>
</div>
</div>
</div>
</div>
<div class="card-design-area">
<!-- 正面设计 -->
<div class="card-side front-side">
<div class="card-canvas-container">
<canvas id="card-front-canvas" width="1050" height="600"></canvas>
<div class="bleed-marks"></div>
</div>
<div class="side-label">正面</div>
</div>
<!-- 背面设计 -->
<div class="card-side back-side">
<div class="card-canvas-container">
<canvas id="card-back-canvas" width="1050" height="600"></canvas>
<div class="bleed-marks"></div>
</div>
<div class="side-label">背面</div>
</div>
</div>
<div class="card-controls">
<div class="info-fields">
<h4>企业信息</h4>
<div class="field-group">
<input type="text" id="company-name" placeholder="公司名称">
<input type="text" id="person-name" placeholder="姓名">
<input type="text" id="person-title" placeholder="职位">
</div>
<div class="field-group">
<input type="tel" id="phone-number" placeholder="电话">
<input type="email" id="email-address" placeholder="邮箱">
<input type="text" id="website-url" placeholder="网址">
</div>
<div class="field-group">
<textarea id="company-address" placeholder="公司地址"></textarea>
</div>
<button id="apply-info" class="btn btn-primary">应用到名片</button>
</div>
<div class="export-options">
<h4>导出选项</h4>
<button id="export-card-pdf" class="btn btn-success">导出打印PDF</button>
<button id="export-card-image" class="btn btn-info">导出图片</button>
<div class="quality-options">
<label>打印质量:</label>
<select id="print-quality">
<option value="standard">标准(300DPI)</option>
<option value="high">高质量(600DPI)</option>
</select>
</div>
</div>
</div>
</div>
4.2 名片设计逻辑实现
// assets/js/business-card-designer.js
class BusinessCardDesigner {
constructor() {
this.frontCanvas = new fabric.Canvas('card-front-canvas');
this.backCanvas = new fabric.Canvas('card-back-canvas');
this.currentTemplate = 'modern';
this.cardSize = { width: 90, height: 54 }; // 毫米
this.bleedSize = 3; // 出血尺寸
this.init();
}
init() {
this.setupCanvas();
this.bindEvents();
this.loadTemplate(this.currentTemplate);
}
setupCanvas() {
// 设置画布尺寸(考虑出血)
const scale = 10; // 放大10倍以便设计
const bleedPx = this.bleedSize * scale;
this.frontCanvas.setDimensions({
width: (this.cardSize.width + this.bleedSize * 2) * scale,
height: (this.cardSize.height + this.bleedSize * 2) * scale
});
this.backCanvas.setDimensions({
width: (this.cardSize.width + this.bleedSize * 2) * scale,
});
// 添加出血线标记
this.addBleedMarks(this.frontCanvas);
this.addBleedMarks(this.backCanvas);
}
addBleedMarks(canvas) {
const width = canvas.width;
const height = canvas.height;
const bleedPx = this.bleedSize * 10;
// 创建出血线
const bleedLines = new fabric.Group([
// 外框线(出血边界)
new fabric.Rect({
left: 0,
top: 0,
width: width,
height: height,
fill: 'transparent',
stroke: '#ff0000',
strokeWidth: 1,
strokeDashArray: [5, 5]
}),
// 内框线(裁切边界)
new fabric.Rect({
left: bleedPx,
top: bleedPx,
width: width - bleedPx * 2,
height: height - bleedPx * 2,
fill: 'transparent',
stroke: '#0000ff',
strokeWidth: 1
})
], {
selectable: false,
evented: false
});
canvas.add(bleedLines);
canvas.sendToBack(bleedLines);
}
bindEvents() {
// 模板选择
document.querySelectorAll('.template-item').forEach(item => {
item.addEventListener('click', () => {
const template = item.getAttribute('data-template');
this.loadTemplate(template);
});
});
// 应用企业信息
document.getElementById('apply-info').addEventListener('click', () => {
this.applyBusinessInfo();
});
// 导出PDF
document.getElementById('export-card-pdf').addEventListener('click', () => {
this.exportToPDF();
});
// 导出图片
document.getElementById('export-card-image').addEventListener('click', () => {
this.exportToImage();
});
}
loadTemplate(templateName) {
this.currentTemplate = templateName;
// 清空画布但保留出血线
const frontObjects = this.frontCanvas.getObjects();
const backObjects = this.backCanvas.getObjects();
frontObjects.forEach(obj => {
if (!obj.isType('group') || !obj.getObjects().some(o => o.stroke === '#ff0000')) {
this.frontCanvas.remove(obj);
}
});
backObjects.forEach(obj => {
if (!obj.isType('group') || !obj.getObjects().some(o => o.stroke === '#ff0000')) {
this.backCanvas.remove(obj);
}
});
// 加载模板
switch(templateName) {
case 'modern':
this.loadModernTemplate();
break;
case 'classic':
this.loadClassicTemplate();
break;
case 'minimal':
this.loadMinimalTemplate();
break;
}
}
loadModernTemplate() {
// 正面设计
const frontBackground = new fabric.Rect({
left: this.bleedSize * 10,
top: this.bleedSize * 10,
width: this.cardSize.width * 10,
height: this.cardSize.height * 10,
fill: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
selectable: false
});
const companyName = new fabric.Text('公司名称', {
left: 150,
top: 100,
fontSize: 32,
fontFamily: 'Arial',
fill: '#ffffff',
fontWeight: 'bold'
});
this.frontCanvas.add(frontBackground, companyName);
// 背面设计
const backBackground = new fabric.Rect({
left: this.bleedSize * 10,
top: this.bleedSize * 10,
width: this.cardSize.width * 10,
height: this.cardSize.height * 10,
fill: '#f8f9fa',
selectable: false
});
const contactInfo = new fabric.Text('联系方式', {
left: 150,
top: 150,
fontSize: 24,
fontFamily: 'Arial',
fill: '#333333'
});
this.backCanvas.add(backBackground, contactInfo);
}
applyBusinessInfo() {
const companyName = document.getElementById('company-name').value;
const personName = document.getElementById('person-name').value;
const personTitle = document.getElementById('person-title').value;
const phoneNumber = document.getElementById('phone-number').value;
const emailAddress = document.getElementById('email-address').value;
const websiteUrl = document.getElementById('website-url').value;
const companyAddress = document.getElementById('company-address').value;
// 更新正面信息
this.updateFrontCard(companyName, personName, personTitle);
// 更新背面信息
this.updateBackCard(phoneNumber, emailAddress, websiteUrl, companyAddress);
}
updateFrontCard(company, name, title) {
// 查找并更新正面文本元素
const frontObjects = this.frontCanvas.getObjects();
frontObjects.forEach(obj => {
if (obj.isType('text')) {
if (obj.text === '公司名称') {
obj.set('text', company || '公司名称');
}
}
});
// 添加姓名和职位
const nameText = new fabric.Text(name || '姓名', {
left: 150,
top: 200,
fontSize: 24,
fontFamily: 'Arial',
fill: '#ffffff'
});
const titleText = new fabric.Text(title || '职位', {
left: 150,
top: 240,
fontSize: 18,
fontFamily: 'Arial',
fill: '#ffffff',
fontStyle: 'italic'
});
this.frontCanvas.add(nameText, titleText);
this.frontCanvas.renderAll();
}
updateBackCard(phone, email, website, address) {
// 更新背面联系方式
const contactText = `电话: ${phone || '未填写'}n邮箱: ${email || '未填写'}n网址: ${website || '未填写'}n地址: ${address || '未填写'}`;
const backObjects = this.backCanvas.getObjects();
backObjects.forEach(obj => {
if (obj.isType('text') && obj.text === '联系方式') {
obj.set('text', contactText);
}
});
this.backCanvas.renderAll();
}
async exportToPDF() {
// 创建PDF文档
const { jsPDF } = window.jspdf;
const pdf = new jsPDF({
orientation: 'landscape',
unit: 'mm',
format: 'a4'
});
// 获取画布数据
const frontDataURL = this.frontCanvas.toDataURL({
format: 'jpeg',
quality: 1
});
const backDataURL = this.backCanvas.toDataURL({
format: 'jpeg',
quality: 1
});
// 添加正面到PDF
pdf.addImage(frontDataURL, 'JPEG', 10, 10, this.cardSize.width, this.cardSize.height);
// 添加背面到PDF(同一页面右侧)
pdf.addImage(backDataURL, 'JPEG', 110, 10, this.cardSize.width, this.cardSize.height);
// 添加裁切标记
this.addCuttingMarksToPDF(pdf);
// 保存PDF
pdf.save('business-card.pdf');
}
addCuttingMarksToPDF(pdf) {
// 添加裁切标记
pdf.setDrawColor(0, 0, 0);
pdf.setLineWidth(0.1);
// 正面裁切标记
const frontX = 10;
const frontY = 10;
// 添加角标记
this.drawCuttingMark(pdf, frontX, frontY);
this.drawCuttingMark(pdf, frontX + this.cardSize.width, frontY);
this.drawCuttingMark(pdf, frontX, frontY + this.cardSize.height);
this.drawCuttingMark(pdf, frontX + this.cardSize.width, frontY + this.cardSize.height);
// 背面裁切标记
const backX = 110;
const backY = 10;
this.drawCuttingMark(pdf, backX, backY);
this.drawCuttingMark(pdf, backX + this.cardSize.width, backY);
this.drawCuttingMark(pdf, backX, backY + this.cardSize.height);
this.drawCuttingMark(pdf, backX + this.cardSize.width, backY + this.cardSize.height);
}
drawCuttingMark(pdf, x, y) {
const markLength = 3;
// 水平线
pdf.line(x - markLength, y, x + markLength, y);
// 垂直线
pdf.line(x, y - markLength, x, y + markLength);
}
exportToImage() {
// 导出为图片
const frontDataURL = this.frontCanvas.toDataURL({
format: 'png',
quality: 1
});
const link = document.createElement('a');
link.download = 'business-card-front.png';
link.href = frontDataURL;
link.click();
}
}
// 初始化名片设计器
document.addEventListener('DOMContentLoaded', function() {
window.cardDesigner = new BusinessCardDesigner();
});
### 4.3 名片设计器后端支持
<?php
// functions.php - 添加名片设计器功能
// 注册名片设计器短代码
add_shortcode('business_card_designer', 'render_business_card_designer');
function render_business_card_designer($atts) {
ob_start();
include get_template_directory() . '/template-parts/business-card-designer.php';
return ob_get_clean();
}
// 添加名片设计保存功能
add_action('rest_api_init', function() {
// 保存名片设计
register_rest_route('card-designer/v1', '/save', array(
'methods' => 'POST',
'callback' => 'save_card_design',
'permission_callback' => function() {
return current_user_can('edit_posts');
}
));
// 获取用户保存的名片设计
register_rest_route('card-designer/v1', '/designs', array(
'methods' => 'GET',
'callback' => 'get_user_card_designs',
'permission_callback' => function() {
return is_user_logged_in();
}
));
});
function save_card_design($request) {
$user_id = get_current_user_id();
$design_data = $request->get_param('design_data');
$design_name = $request->get_param('design_name');
$card_type = $request->get_param('card_type'); // 'front' or 'back'
if(empty($design_data) || empty($design_name)) {
return new WP_Error('invalid_data', '设计数据或名称为空', array('status' => 400));
}
// 生成预览图
$preview_data = generate_card_preview($design_data);
// 保存到数据库
global $wpdb;
$table_name = $wpdb->prefix . 'card_designs';
$result = $wpdb->insert(
$table_name,
array(
'user_id' => $user_id,
'design_name' => sanitize_text_field($design_name),
'design_data' => json_encode($design_data),
'card_type' => $card_type,
'preview_data' => $preview_data,
'created_at' => current_time('mysql')
),
array('%d', '%s', '%s', '%s', '%s', '%s')
);
if($result === false) {
return new WP_Error('db_error', '保存失败', array('status' => 500));
}
return rest_ensure_response(array(
'success' => true,
'message' => '名片设计已保存',
'design_id' => $wpdb->insert_id
));
}
function generate_card_preview($design_data) {
// 这里可以添加生成预览图的逻辑
// 可以使用GD库或ImageMagick生成缩略图
return ''; // 返回base64编码的预览图
}
// 创建名片设计数据库表
register_activation_hook(__FILE__, 'create_card_designs_table');
function create_card_designs_table() {
global $wpdb;
$table_name = $wpdb->prefix . 'card_designs';
$charset_collate = $wpdb->get_charset_collate();
$sql = "CREATE TABLE IF NOT EXISTS $table_name (
id mediumint(9) NOT NULL AUTO_INCREMENT,
user_id bigint(20) NOT NULL,
design_name varchar(255) NOT NULL,
design_data longtext NOT NULL,
card_type varchar(50) NOT NULL,
preview_data longtext,
created_at datetime DEFAULT '0000-00-00 00:00:00' NOT NULL,
PRIMARY KEY (id),
KEY user_id (user_id)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql);
}
?>
---
## 五、样式设计与用户体验优化
### 5.1 响应式CSS设计
/ assets/css/design-tools.css /
/ Logo设计器样式 /
.logo-designer-container {
display: flex;
flex-direction: column;
height: 80vh;
background: #f5f7fa;
border-radius: 10px;
overflow: hidden;
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
}
.designer-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 15px 20px;
display: flex;
justify-content: space-between;
align-items: center;
}
.designer-body {
display: flex;
flex: 1;
overflow: hidden;
}
.sidebar-left, .sidebar-right {
width: 250px;
background: white;
padding: 15px;
overflow-y: auto;
border-right: 1px solid #e0e0e0;
}
.sidebar-right {
border-right: none;
border-left: 1px solid #e0e0e0;
}
.canvas-area {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 20px;
background: #f8f9fa;
}
logo-canvas {
background: white;
border: 1px solid #ddd;
border-radius: 5px;
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
}
.canvas-controls {
margin-top: 15px;
display: flex;
gap: 10px;
}
.tool-section {
margin-bottom: 25px;
}
.tool-section h4 {
margin-bottom: 10px;
color: #333;
font-size: 14px;
text-transform: uppercase;
letter-spacing: 1px;
}
.shape-list {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
}
.shape-btn {
padding: 8px;
border: 1px solid #ddd;
background: white;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s ease;
}
.shape-btn:hover {
background: #f0f0f0;
border-color: #667eea;
}
.icon-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
}
.icon-item {
padding: 10px;
border: 1px solid #e0e0e0;
border-radius: 4px;
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
}
.icon-item:hover {
background: #f0f7ff;
border-color: #4dabf7;
transform: translateY(-2px);
}
.icon-item i {
font-size: 20px;
color: #495057;
}
.property-group {
margin-bottom: 15px;
}
.property-group label {
display: block;
margin-bottom: 5px;
font-size: 12px;
color: #666;
font-weight: 500;
}
.property-group input,
.property-group select {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
.layers-panel ul {
list-style: none;
padding: 0;
margin: 0;
}
.layer-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 10px;
border-bottom: 1px solid #eee;
font-size: 13px;
}
.layer-item:hover {
background: #f8f9fa;
}
.layer-actions {
display: flex;
gap: 5px;
}
.layer-actions button {
padding: 2px 6px;
font-size: 11px;
border: 1px solid #ddd;
background: white;
border-radius: 3px;
cursor: pointer;
}
/ 名片设计器样式 /
.card-designer-container {
background: white;
border-radius: 10px;
overflow: hidden;
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
}
.template-selector {
padding: 15px;
background: #f8f9fa;
border-bottom: 1px solid #e0e0
