|
@@ -2,10 +2,29 @@
|
|
|
<div class="template-design-container">
|
|
<div class="template-design-container">
|
|
|
<!-- 左侧工具栏 -->
|
|
<!-- 左侧工具栏 -->
|
|
|
<div class="toolbar">
|
|
<div class="toolbar">
|
|
|
|
|
+ <!-- 主菜单 -->
|
|
|
|
|
+ <div class="main-menu">
|
|
|
|
|
+ <el-menu
|
|
|
|
|
+ :default-active="currentView"
|
|
|
|
|
+ class="main-menu"
|
|
|
|
|
+ mode="vertical"
|
|
|
|
|
+ @select="handleMenuSelect"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-menu-item index="design">
|
|
|
|
|
+ <el-icon><Document /></el-icon>
|
|
|
|
|
+ <span>设计模板</span>
|
|
|
|
|
+ </el-menu-item>
|
|
|
|
|
+ <el-menu-item index="library">
|
|
|
|
|
+ <el-icon><Picture /></el-icon>
|
|
|
|
|
+ <span>模板库</span>
|
|
|
|
|
+ </el-menu-item>
|
|
|
|
|
+ </el-menu>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
<!-- 标签页 + 可滚动内容(保存按钮固定在底部) -->
|
|
<!-- 标签页 + 可滚动内容(保存按钮固定在底部) -->
|
|
|
- <div class="toolbar-tabs">
|
|
|
|
|
|
|
+ <div class="toolbar-tabs" v-if="currentView === 'design'">
|
|
|
<el-tabs v-model="activeTab" type="card" @tab-click="handleTabClick">
|
|
<el-tabs v-model="activeTab" type="card" @tab-click="handleTabClick">
|
|
|
- <el-tab-pane label="模版设计" name="design">
|
|
|
|
|
|
|
+ <el-tab-pane label="模版设计" name="design">
|
|
|
<el-upload
|
|
<el-upload
|
|
|
class="custom-upload"
|
|
class="custom-upload"
|
|
|
drag
|
|
drag
|
|
@@ -314,75 +333,121 @@
|
|
|
</el-tabs>
|
|
</el-tabs>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- <el-divider class="save-template-divider" />
|
|
|
|
|
|
|
+ <el-divider class="save-template-divider" v-if="currentView === 'design'" />
|
|
|
<el-button
|
|
<el-button
|
|
|
type="primary"
|
|
type="primary"
|
|
|
class="save-template-btn"
|
|
class="save-template-btn"
|
|
|
@click="saveTemplate"
|
|
@click="saveTemplate"
|
|
|
|
|
+ v-if="currentView === 'design'"
|
|
|
>
|
|
>
|
|
|
生成模版
|
|
生成模版
|
|
|
</el-button>
|
|
</el-button>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- <!-- 中间画布区域 -->
|
|
|
|
|
- <div class="canvas-area" ref="canvasAreaRef">
|
|
|
|
|
- <div class="canvas-wrapper">
|
|
|
|
|
- <div
|
|
|
|
|
- ref="canvasRef"
|
|
|
|
|
- class="canvas"
|
|
|
|
|
- :style="{
|
|
|
|
|
- width: canvasWidth + 'px',
|
|
|
|
|
- height: canvasHeight + 'px'
|
|
|
|
|
- }"
|
|
|
|
|
- @mousedown="handleCanvasMouseDown"
|
|
|
|
|
- @mousemove="handleCanvasMouseMove"
|
|
|
|
|
- @mouseup="handleCanvasMouseUp"
|
|
|
|
|
- @mouseleave="handleCanvasMouseUp"
|
|
|
|
|
- @wheel="handleCanvasWheel"
|
|
|
|
|
- >
|
|
|
|
|
- <div
|
|
|
|
|
- v-for="(layer, index) in layers"
|
|
|
|
|
- :key="layer.id"
|
|
|
|
|
- class="layer"
|
|
|
|
|
- :class="{
|
|
|
|
|
- 'selected': selectedLayerId === layer.id,
|
|
|
|
|
- 'text-layer': layer.type === 'text'
|
|
|
|
|
|
|
+ <!-- 右侧内容区域 -->
|
|
|
|
|
+ <div class="content-area">
|
|
|
|
|
+ <!-- 中间画布区域 -->
|
|
|
|
|
+ <div class="canvas-area" ref="canvasAreaRef" v-if="currentView === 'design'">
|
|
|
|
|
+ <div class="canvas-wrapper">
|
|
|
|
|
+ <div
|
|
|
|
|
+ ref="canvasRef"
|
|
|
|
|
+ class="canvas"
|
|
|
|
|
+ :style="{
|
|
|
|
|
+ width: canvasWidth + 'px',
|
|
|
|
|
+ height: canvasHeight + 'px'
|
|
|
}"
|
|
}"
|
|
|
- :style="getLayerStyle(layer)"
|
|
|
|
|
- @mousedown.stop="handleLayerMouseDown($event, layer)"
|
|
|
|
|
- @dblclick.stop="handleLayerDblClick($event, layer)"
|
|
|
|
|
|
|
+ @mousedown="handleCanvasMouseDown"
|
|
|
|
|
+ @mousemove="handleCanvasMouseMove"
|
|
|
|
|
+ @mouseup="handleCanvasMouseUp"
|
|
|
|
|
+ @mouseleave="handleCanvasMouseUp"
|
|
|
|
|
+ @wheel="handleCanvasWheel"
|
|
|
>
|
|
>
|
|
|
- <!-- 图片图层 -->
|
|
|
|
|
- <template v-if="layer.type !== 'text'">
|
|
|
|
|
- <img :src="layer.url" :alt="layer.name" draggable="false" />
|
|
|
|
|
- </template>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 文字图层 -->
|
|
|
|
|
- <template v-else>
|
|
|
|
|
- <div
|
|
|
|
|
- class="text-content"
|
|
|
|
|
- :style="getTextStyle(layer)"
|
|
|
|
|
- contenteditable="false"
|
|
|
|
|
- >
|
|
|
|
|
- {{ layer.text }}
|
|
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-for="(layer, index) in layers"
|
|
|
|
|
+ :key="layer.id"
|
|
|
|
|
+ class="layer"
|
|
|
|
|
+ :class="{
|
|
|
|
|
+ 'selected': selectedLayerId === layer.id,
|
|
|
|
|
+ 'text-layer': layer.type === 'text'
|
|
|
|
|
+ }"
|
|
|
|
|
+ :style="getLayerStyle(layer)"
|
|
|
|
|
+ @mousedown.stop="handleLayerMouseDown($event, layer)"
|
|
|
|
|
+ @dblclick.stop="handleLayerDblClick($event, layer)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <!-- 图片图层 -->
|
|
|
|
|
+ <template v-if="layer.type !== 'text'">
|
|
|
|
|
+ <img :src="layer.url" :alt="layer.name" draggable="false" />
|
|
|
|
|
+ </template>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 文字图层 -->
|
|
|
|
|
+ <template v-else>
|
|
|
|
|
+ <div
|
|
|
|
|
+ class="text-content"
|
|
|
|
|
+ :style="getTextStyle(layer)"
|
|
|
|
|
+ contenteditable="false"
|
|
|
|
|
+ >
|
|
|
|
|
+ {{ layer.text }}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 选中状态的调整手柄 -->
|
|
|
|
|
+ <template v-if="selectedLayerId === layer.id">
|
|
|
|
|
+ <div class="resize-handle nw" @mousedown.stop="startResize($event, 'nw')"></div>
|
|
|
|
|
+ <div class="resize-handle ne" @mousedown.stop="startResize($event, 'ne')"></div>
|
|
|
|
|
+ <div class="resize-handle sw" @mousedown.stop="startResize($event, 'sw')"></div>
|
|
|
|
|
+ <div class="resize-handle se" @mousedown.stop="startResize($event, 'se')"></div>
|
|
|
|
|
+ <div class="rotate-handle" @mousedown.stop="startRotate($event)"></div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 模板库视图 -->
|
|
|
|
|
+ <div class="template-library-view" v-if="currentView === 'library'">
|
|
|
|
|
+ <div class="library-header">
|
|
|
|
|
+ <h3>模板库</h3>
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="templateSearch"
|
|
|
|
|
+ size="small"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ :prefix-icon="Search"
|
|
|
|
|
+ placeholder="搜索模板"
|
|
|
|
|
+ @input="handleTemplateSearch"
|
|
|
|
|
+ style="width: 300px;"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <el-divider />
|
|
|
|
|
+
|
|
|
|
|
+ <div class="template-list">
|
|
|
|
|
+ <el-skeleton v-if="templatesLoading" :rows="8" animated />
|
|
|
|
|
+ <div v-else-if="templates.length === 0" class="empty-tip">
|
|
|
|
|
+ 暂无模板
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div v-else class="template-grid">
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-for="template in templates"
|
|
|
|
|
+ :key="template.id"
|
|
|
|
|
+ class="template-item"
|
|
|
|
|
+ @click="useTemplate(template)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <div class="template-preview">
|
|
|
|
|
+ <img :src="template.template_image_url" :alt="template.template_name" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="template-info">
|
|
|
|
|
+ <span class="template-name">{{ template.template_name }}</span>
|
|
|
|
|
+ <el-button size="small" type="primary" @click.stop="useTemplate(template)">
|
|
|
|
|
+ 使用模板
|
|
|
|
|
+ </el-button>
|
|
|
</div>
|
|
</div>
|
|
|
- </template>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 选中状态的调整手柄 -->
|
|
|
|
|
- <template v-if="selectedLayerId === layer.id">
|
|
|
|
|
- <div class="resize-handle nw" @mousedown.stop="startResize($event, 'nw')"></div>
|
|
|
|
|
- <div class="resize-handle ne" @mousedown.stop="startResize($event, 'ne')"></div>
|
|
|
|
|
- <div class="resize-handle sw" @mousedown.stop="startResize($event, 'sw')"></div>
|
|
|
|
|
- <div class="resize-handle se" @mousedown.stop="startResize($event, 'se')"></div>
|
|
|
|
|
- <div class="rotate-handle" @mousedown.stop="startRotate($event)"></div>
|
|
|
|
|
- </template>
|
|
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 右侧图层面板 -->
|
|
|
|
|
- <div class="layer-panel">
|
|
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 右侧图层面板 -->
|
|
|
|
|
+ <div class="layer-panel" v-if="currentView === 'design'">
|
|
|
<h3>图层</h3>
|
|
<h3>图层</h3>
|
|
|
<div class="layer-actions">
|
|
<div class="layer-actions">
|
|
|
<el-button size="small" @click="moveLayerUp" :disabled="!canMoveUp">
|
|
<el-button size="small" @click="moveLayerUp" :disabled="!canMoveUp">
|
|
@@ -445,6 +510,7 @@
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
@@ -452,18 +518,24 @@
|
|
|
import { ref, computed, reactive, onMounted, onUnmounted } from 'vue'
|
|
import { ref, computed, reactive, onMounted, onUnmounted } from 'vue'
|
|
|
import { ElMessage } from 'element-plus'
|
|
import { ElMessage } from 'element-plus'
|
|
|
import { Pointer, Rank, ArrowUp, ArrowDown, Delete, View, Hide, Lock, Unlock, Plus, Document, Picture, Refresh, Upload, Search, ArrowLeft } from '@element-plus/icons-vue'
|
|
import { Pointer, Rank, ArrowUp, ArrowDown, Delete, View, Hide, Lock, Unlock, Plus, Document, Picture, Refresh, Upload, Search, ArrowLeft } from '@element-plus/icons-vue'
|
|
|
-import { Material_List, Template_Material_Add } from '@/api/mes/job'
|
|
|
|
|
|
|
+import { Material_List, Template_Material_Add, product_template } from '@/api/mes/job'
|
|
|
|
|
|
|
|
const canvasRef = ref(null)
|
|
const canvasRef = ref(null)
|
|
|
const canvasAreaRef = ref(null)
|
|
const canvasAreaRef = ref(null)
|
|
|
const layerListRef = ref(null)
|
|
const layerListRef = ref(null)
|
|
|
const currentTool = ref('select')
|
|
const currentTool = ref('select')
|
|
|
const activeTab = ref('design') // 'design' 或 'material'
|
|
const activeTab = ref('design') // 'design' 或 'material'
|
|
|
|
|
+const currentView = ref('design') // 'design' 或 'library'
|
|
|
const canvasWidth = ref(600)
|
|
const canvasWidth = ref(600)
|
|
|
const canvasHeight = ref(450)
|
|
const canvasHeight = ref(450)
|
|
|
const canvasRatio = ref('4:3')
|
|
const canvasRatio = ref('4:3')
|
|
|
const zoomLevel = ref(100)
|
|
const zoomLevel = ref(100)
|
|
|
|
|
|
|
|
|
|
+// 模板库状态
|
|
|
|
|
+const templates = ref([])
|
|
|
|
|
+const templatesLoading = ref(false)
|
|
|
|
|
+const templateSearch = ref('')
|
|
|
|
|
+
|
|
|
const layers = ref([])
|
|
const layers = ref([])
|
|
|
const selectedLayerId = ref(null)
|
|
const selectedLayerId = ref(null)
|
|
|
const maintainAspectRatio = ref(false) // 默认不锁定宽高比例,宽高可自由调整
|
|
const maintainAspectRatio = ref(false) // 默认不锁定宽高比例,宽高可自由调整
|
|
@@ -799,6 +871,48 @@ const handleTabClick = (tab) => {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// 菜单选择事件处理
|
|
|
|
|
+const handleMenuSelect = (key) => {
|
|
|
|
|
+ currentView.value = key
|
|
|
|
|
+ if (key === 'library') {
|
|
|
|
|
+ fetchTemplates()
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 获取模板库数据
|
|
|
|
|
+const fetchTemplates = async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ templatesLoading.value = true
|
|
|
|
|
+ // 调用实际的模板库API
|
|
|
|
|
+ const response = await product_template()
|
|
|
|
|
+ const data = response
|
|
|
|
|
+ if (data.code === 0) {
|
|
|
|
|
+ templates.value = data.data
|
|
|
|
|
+ } else {
|
|
|
|
|
+ ElMessage.error('获取模板库失败')
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('获取模板库失败:', error)
|
|
|
|
|
+ ElMessage.error('获取模板库失败')
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ templatesLoading.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 搜索模板
|
|
|
|
|
+const handleTemplateSearch = () => {
|
|
|
|
|
+ // 这里应该根据搜索词过滤模板
|
|
|
|
|
+ // 暂时不实现具体逻辑
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 使用模板
|
|
|
|
|
+const useTemplate = (template) => {
|
|
|
|
|
+ // 这里应该根据模板数据初始化画布
|
|
|
|
|
+ ElMessage.success(`使用模板: ${template.template_name}`)
|
|
|
|
|
+ // 切换回设计视图
|
|
|
|
|
+ currentView.value = 'design'
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
// 点击素材添加到画布
|
|
// 点击素材添加到画布
|
|
|
const addMaterialToCanvas = (material) => {
|
|
const addMaterialToCanvas = (material) => {
|
|
|
const img = new Image()
|
|
const img = new Image()
|
|
@@ -1614,8 +1728,206 @@ const handleCanvasWheel = (e) => {
|
|
|
.property-item span {
|
|
.property-item span {
|
|
|
width: 40px;
|
|
width: 40px;
|
|
|
font-size: 11px;
|
|
font-size: 11px;
|
|
|
- color: #666;
|
|
|
|
|
- flex-shrink: 0;
|
|
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 主菜单样式 */
|
|
|
|
|
+.main-menu {
|
|
|
|
|
+ margin-bottom: 12px;
|
|
|
|
|
+ border-bottom: 1px solid #e5e5e5;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.main-menu :deep(.el-menu) {
|
|
|
|
|
+ border-bottom: none;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.main-menu :deep(.el-menu-item) {
|
|
|
|
|
+ height: 40px;
|
|
|
|
|
+ line-height: 40px;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.main-menu :deep(.el-menu-item.is-active) {
|
|
|
|
|
+ color: #409eff;
|
|
|
|
|
+ background-color: #ecf5ff;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 模板库样式 */
|
|
|
|
|
+.template-library {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ min-height: 0;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.library-header {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ margin-bottom: 8px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.library-header h3 {
|
|
|
|
|
+ margin: 0;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ color: #333;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.template-list {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ min-height: 0;
|
|
|
|
|
+ overflow-y: auto;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.template-grid {
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ grid-template-columns: repeat(2, 1fr);
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+ padding: 8px 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.template-item {
|
|
|
|
|
+ background-color: #f9f9f9;
|
|
|
|
|
+ border: 1px solid #e5e5e5;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ transition: all 0.2s;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.template-item:hover {
|
|
|
|
|
+ border-color: #409eff;
|
|
|
|
|
+ transform: translateY(-2px);
|
|
|
|
|
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.template-preview {
|
|
|
|
|
+ height: 120px;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.template-preview img {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ object-fit: cover;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.template-info {
|
|
|
|
|
+ padding: 8px;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ gap: 6px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.template-name {
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ color: #333;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ text-overflow: ellipsis;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.template-info .el-button {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ padding: 4px 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 右侧内容区域 */
|
|
|
|
|
+.content-area {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ background-color: #f5f5f5;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.content-area:has(.template-library-view) {
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 模板库视图 */
|
|
|
|
|
+.template-library-view {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ padding: 20px;
|
|
|
|
|
+ overflow-y: auto;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.template-library-view .library-header {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ margin-bottom: 16px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.template-library-view .library-header h3 {
|
|
|
|
|
+ margin: 0;
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
+ color: #333;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.template-list {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.template-grid {
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
|
|
|
|
+ gap: 20px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.template-item {
|
|
|
|
|
+ background-color: #fff;
|
|
|
|
|
+ border: 1px solid #e5e5e5;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ transition: all 0.2s;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.template-item:hover {
|
|
|
|
|
+ border-color: #409eff;
|
|
|
|
|
+ transform: translateY(-2px);
|
|
|
|
|
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.template-preview {
|
|
|
|
|
+ height: 180px;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.template-preview img {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ object-fit: cover;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.template-info {
|
|
|
|
|
+ padding: 12px;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.template-name {
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+ color: #333;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ text-overflow: ellipsis;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.template-meta {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ color: #999;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.template-style, .template-type {
|
|
|
|
|
+ background-color: #f5f5f5;
|
|
|
|
|
+ padding: 2px 8px;
|
|
|
|
|
+ border-radius: 12px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.property-item .el-input-number {
|
|
.property-item .el-input-number {
|