|
|
@@ -0,0 +1,5683 @@
|
|
|
+<template>
|
|
|
+ <div class="parameter-settings">
|
|
|
+ <!-- 顶部二级菜单 -->
|
|
|
+ <div class="sub-tabs">
|
|
|
+ <div
|
|
|
+ v-for="tab in subTabs"
|
|
|
+ :key="tab.value"
|
|
|
+ :class="['sub-tab-item', { active: activeSubTab === tab.value }]"
|
|
|
+ @click="activeSubTab = tab.value"
|
|
|
+ >
|
|
|
+ {{ tab.label }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 内容区域 -->
|
|
|
+ <div class="content-body">
|
|
|
+ <!-- 搜索栏编辑区 -->
|
|
|
+ <div v-if="activeSubTab === 'search'" class="search-editor">
|
|
|
+ <!-- 实时预览区 (参照图1) -->
|
|
|
+ <div class="preview-section">
|
|
|
+ <div class="preview-title">实时预览</div>
|
|
|
+ <div class="live-preview-box">
|
|
|
+ <div class="search-bar-mockup">
|
|
|
+ <!-- 左侧标题 -->
|
|
|
+ <div class="mockup-left">
|
|
|
+ <div class="main-title" :style="{ color: form.themeColor }">{{ form.mainTitle }}</div>
|
|
|
+ <div class="sub-title">{{ form.subTitle }}</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 中间搜索框 -->
|
|
|
+ <div class="mockup-center" :style="{ '--theme-color': form.themeColor }">
|
|
|
+ <div class="search-input-wrapper" :style="{ borderColor: form.themeColor }">
|
|
|
+ <div class="placeholder-scroll">
|
|
|
+ <transition-group name="list-scroll" tag="div" class="scroll-container">
|
|
|
+ <div v-for="(text, index) in searchPlaceholderList" :key="text" v-show="index === currentPlaceholderIndex" class="scroll-item">
|
|
|
+ {{ text }}
|
|
|
+ </div>
|
|
|
+ </transition-group>
|
|
|
+ </div>
|
|
|
+ <div class="search-btn" :style="{ backgroundColor: form.themeColor }">搜 索</div>
|
|
|
+ </div>
|
|
|
+ <div class="hot-words">
|
|
|
+ <span v-for="item in form.hotWordsList" :key="item.name" class="hot-word">{{ item.name }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 右侧功能按钮 (可配置) -->
|
|
|
+ <div class="mockup-right">
|
|
|
+ <div class="cart-btn" :style="{ borderColor: form.themeColor, color: form.themeColor }">
|
|
|
+ <img v-if="form.rightBtnIcon" :src="form.rightBtnIcon" style="width: 16px; height: 16px; margin-right: 4px; object-fit: contain" />
|
|
|
+ <span v-if="form.rightBtnText">{{ form.rightBtnText }}</span>
|
|
|
+ <span v-else-if="!form.rightBtnIcon">购物车</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 配置项表单 -->
|
|
|
+ <div class="settings-section">
|
|
|
+ <el-form :model="form" label-width="120px" label-position="left">
|
|
|
+ <el-form-item label="主标题:">
|
|
|
+ <el-input v-model="form.mainTitle" placeholder="如:优易企业购" class="settings-input" />
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item label="副标题:">
|
|
|
+ <el-input v-model="form.subTitle" placeholder="如:省钱 · 省心 · 省时间" class="settings-input" />
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item label="搜索框文字:">
|
|
|
+ <el-input v-model="form.placeholderText" placeholder="支持输入多个,请用英文逗号(,)隔开实现上下滚动效果" class="settings-input" />
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item label="搜索热词:">
|
|
|
+ <div class="hot-words-config">
|
|
|
+ <div v-for="(item, index) in form.hotWordsList" :key="index" class="hot-word-row">
|
|
|
+ <el-input v-model="item.name" placeholder="热词名称" class="hot-word-input-name" />
|
|
|
+ <WebLinkInput v-model="item.link" placeholder="跳转地址" class="hot-word-input-link"/>
|
|
|
+ <el-button type="danger" icon="Delete" circle plain size="small" @click="removeHotWord(index)" />
|
|
|
+ </div>
|
|
|
+ <el-button type="primary" icon="Plus" link @click="addHotWord" class="add-hotword-btn">添加热词</el-button>
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item label="按钮图标:">
|
|
|
+ <UploadImage v-model="form.rightBtnIcon" :limit="1" width="32px" height="32px" />
|
|
|
+ <div class="field-tip">建议尺寸: 32*32, 格式: PNG/SVG</div>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item label="按钮文字:">
|
|
|
+ <el-input v-model="form.rightBtnText" placeholder="如:购物车" maxlength="6" show-word-limit class="settings-input" />
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item label="跳转地址:">
|
|
|
+ <WebLinkInput v-model="form.rightBtnLink" placeholder="请输入跳转地址" class="settings-input"/>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item label="主题色:">
|
|
|
+ <div class="color-picker-wrap">
|
|
|
+ <el-color-picker v-model="form.themeColor" />
|
|
|
+ <span class="color-val">{{ form.themeColor }}</span>
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 广告图编辑区 -->
|
|
|
+ <div v-else-if="activeSubTab === 'carousel'" class="carousel-editor">
|
|
|
+ <!-- 模块一:左侧广告设置 -->
|
|
|
+ <div class="editor-section">
|
|
|
+ <div class="section-header">
|
|
|
+ <span class="section-title">模块一:左侧广告设置</span>
|
|
|
+ <span class="section-desc">尺寸要求:790 * 460,支持上传本地图片并设置跳转链接</span>
|
|
|
+ </div>
|
|
|
+ <div class="left-ad-container">
|
|
|
+ <div
|
|
|
+ class="left-ad-preview-wrapper"
|
|
|
+ :class="{ expanded: leftAdHover }"
|
|
|
+ @mouseenter="leftAdHover = true"
|
|
|
+ @mouseleave="leftAdHover = false"
|
|
|
+ >
|
|
|
+ <UploadImage
|
|
|
+ v-model="leftAdForm.leftAdImage"
|
|
|
+ :limit="1"
|
|
|
+ width="790px"
|
|
|
+ height="460px"
|
|
|
+ imageText="上传广告图"
|
|
|
+ @change="onLeftAdImageChange"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="left-ad-settings" v-if="leftAdForm.leftAdImage">
|
|
|
+ <el-form label-width="90px" label-position="left">
|
|
|
+ <el-form-item label="跳转地址:">
|
|
|
+ <WebLinkInput v-model="leftAdForm.leftAdLink" placeholder="请输入以 http:// 或 https:// 开头的地址" class="settings-input-ad"/>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <div class="left-ad-tip">
|
|
|
+ <el-icon><InfoFilled /></el-icon>
|
|
|
+ <span>鼠标悬停查看全图预览效果,点击在新窗口打开</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 模块二:轮播图管理 -->
|
|
|
+ <div class="editor-section">
|
|
|
+ <div class="section-header">
|
|
|
+ <span class="section-title">模块二:轮播图设置</span>
|
|
|
+ <span class="section-desc">尺寸要求:552 * 190,支持拖拽排序与实时状态切换</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 实时预览区 -->
|
|
|
+ <div class="carousel-preview-box">
|
|
|
+ <div class="preview-mockup">
|
|
|
+ <el-carousel height="190px" trigger="click" indicator-position="inside" arrow="never">
|
|
|
+ <el-carousel-item v-for="item in activeCarouselList" :key="item.id">
|
|
|
+ <div class="carousel-slide">
|
|
|
+ <img :src="item.image" alt="轮播图" />
|
|
|
+ </div>
|
|
|
+ </el-carousel-item>
|
|
|
+ <div v-if="activeCarouselList.length === 0" class="carousel-empty">
|
|
|
+ <el-icon :size="40"><Picture /></el-icon>
|
|
|
+ <p>暂无启用的轮播图预览</p>
|
|
|
+ </div>
|
|
|
+ </el-carousel>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 列表管理区 -->
|
|
|
+ <div class="carousel-list-box">
|
|
|
+ <div class="list-toolbar">
|
|
|
+ <el-button type="primary" icon="Plus" @click="handleAddCarousel" class="btn-add-carousel">新增轮播图</el-button>
|
|
|
+ <span class="drag-tip">提示:列表支持状态切换及拖拽排序管理</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <el-table :data="carouselList" style="width: 100%" row-key="id" border header-cell-class-name="table-header-custom">
|
|
|
+ <el-table-column label="排序" width="70" align="center">
|
|
|
+ <template #default="{ $index }">
|
|
|
+ <div class="rank-box">
|
|
|
+ <el-icon v-if="$index > 0" class="rank-icon" @click="moveRow($index, -1)"><CaretTop /></el-icon>
|
|
|
+ <el-icon v-if="$index < carouselList.length - 1" class="rank-icon" @click="moveRow($index, 1)"><CaretBottom /></el-icon>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="序号" type="index" width="60" align="center" />
|
|
|
+ <el-table-column label="图片" width="180">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-image :src="scope.row.image" :preview-src-list="[scope.row.image]" fit="cover" class="table-img" preview-teleported />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="link" label="跳转地址" show-overflow-tooltip />
|
|
|
+ <!-- <el-table-column label="打开方式" width="120" align="center">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-tag :type="scope.row.target === '_blank' ? 'success' : 'info'" size="small">
|
|
|
+ {{ scope.row.target === '_blank' ? '新窗口' : '当前窗口' }}
|
|
|
+ </el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column> -->
|
|
|
+ <el-table-column label="状态" width="100" align="center">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-switch
|
|
|
+ v-model="scope.row.status"
|
|
|
+ :active-value="1"
|
|
|
+ :inactive-value="0"
|
|
|
+ @change="handleCarouselStatusChange(scope.row)"
|
|
|
+ active-color="#13ce66"
|
|
|
+ />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="操作" width="150" align="center">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-button link type="primary" @click="handleEditCarousel(scope.row)">修改</el-button>
|
|
|
+ <el-button link type="danger" @click="handleRemoveCarousel(scope.$index)">删除</el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 快捷入口编辑区 -->
|
|
|
+ <div v-else-if="activeSubTab === 'quick-entry'" class="quick-entry-editor">
|
|
|
+ <div class="preview-section">
|
|
|
+ <div class="preview-title">快捷入口实时预览 (尺寸: 230 * 167)</div>
|
|
|
+ <div class="quick-entry-preview-outer">
|
|
|
+ <!-- 仿真预览模块 (230*167) -->
|
|
|
+ <div class="qe-mockup-card">
|
|
|
+ <div class="qe-card-header">
|
|
|
+ <span class="qe-card-title">{{ quickEntrySettings.moduleName }}</span>
|
|
|
+ <el-icon class="qe-header-arrow"><ArrowRight /></el-icon>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="qe-grid-container">
|
|
|
+ <div class="qe-grid-wrapper" :style="{ transform: `translateX(-${qePageIndex * 198}px)` }">
|
|
|
+ <!-- 网格页 -->
|
|
|
+ <div v-for="pageIdx in qePageCount" :key="pageIdx" class="qe-grid-page">
|
|
|
+ <div v-for="(item, idx) in getPageItems(pageIdx - 1)" :key="idx" class="qe-item">
|
|
|
+ <div class="qe-icon-wrap">
|
|
|
+ <img v-if="item.icon" :src="item.icon" class="qe-icon-img" />
|
|
|
+ <el-icon v-else class="qe-icon-placeholder"><Menu /></el-icon>
|
|
|
+ <div v-if="item.tag" class="qe-tag-bubble">{{ item.tag }}</div>
|
|
|
+ </div>
|
|
|
+ <span class="qe-name">{{ item.name }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 左右翻页按钮 (仅在多于8项时显示) -->
|
|
|
+ <div v-if="quickEntryList.length > 8" class="qe-nav-btns">
|
|
|
+ <div v-if="qePageIndex > 0" class="qe-nav-btn prev" @click="qePageIndex--">
|
|
|
+ <el-icon><ArrowLeft /></el-icon>
|
|
|
+ </div>
|
|
|
+ <div v-if="qePageIndex < qePageCount - 1" class="qe-nav-btn next" @click="qePageIndex++">
|
|
|
+ <el-icon><ArrowRight /></el-icon>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 列表设置 -->
|
|
|
+ <div class="editor-section">
|
|
|
+ <div class="section-header">
|
|
|
+ <span class="section-title">模块基础配置</span>
|
|
|
+ </div>
|
|
|
+ <div class="config-form-inline">
|
|
|
+ <el-form :inline="true" :model="quickEntrySettings" label-width="100px">
|
|
|
+ <el-form-item label="模块名称:">
|
|
|
+ <el-input v-model="quickEntrySettings.moduleName" placeholder="如:企业工作台" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="跳转地址:">
|
|
|
+ <WebLinkInput v-model="quickEntrySettings.jumpLink" placeholder="标题点击跳转地址" style="width: 300px"/>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="section-header m-t-20">
|
|
|
+ <span class="section-title">入口项管理列表</span>
|
|
|
+ </div>
|
|
|
+ <div class="list-toolbar">
|
|
|
+ <div class="drag-tip">提示:一页显示8个,超过8个将自动启用预览区右滑功能</div>
|
|
|
+ <el-button type="primary" icon="Plus" @click="handleAddQuickEntry">新增入口</el-button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <el-table :data="quickEntryList" border style="width: 100%" header-cell-class-name="table-header-custom">
|
|
|
+ <el-table-column label="排序" width="80" align="center">
|
|
|
+ <template #default="{ $index }">
|
|
|
+ <div class="rank-box">
|
|
|
+ <el-icon v-if="$index > 0" class="rank-icon" @click="moveQE($index, -1)"><CaretTop /></el-icon>
|
|
|
+ <el-icon v-if="$index < quickEntryList.length - 1" class="rank-icon" @click="moveQE($index, 1)"><CaretBottom /></el-icon>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="图标" width="100" align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <div class="table-icon-cell">
|
|
|
+ <img v-if="row.icon" :src="row.icon" style="width: 24px; height: 24px" />
|
|
|
+ <el-icon v-else style="font-size: 20px; color: #ccc"><Menu /></el-icon>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="name" label="名称" width="150" />
|
|
|
+ <el-table-column label="标签" width="120" align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-tag v-if="row.tag" type="danger" size="small" effect="plain">{{ row.tag }}</el-tag>
|
|
|
+ <span v-else>-</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="link" label="跳转地址" show-overflow-tooltip />
|
|
|
+ <el-table-column label="状态" width="100" align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-switch v-model="row.status" :active-value="1" :inactive-value="0" @change="handleQuickEntryStatusChange(row)" />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="操作" width="150" fixed="right" align="center">
|
|
|
+ <template #default="{ row, $index }">
|
|
|
+ <el-button type="primary" link @click="handleEditQuickEntry(row, $index)">编辑</el-button>
|
|
|
+ <el-button type="danger" link @click="handleDeleteQuickEntry($index)">删除</el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <!-- 分类设置编辑区 -->
|
|
|
+ <div v-else-if="activeSubTab === 'category'" class="category-editor">
|
|
|
+ <!-- 模块一:实时预览 (高度还原图1、2) -->
|
|
|
+ <div class="editor-section">
|
|
|
+ <div class="section-header">
|
|
|
+ <span class="section-title">分类实时预览</span>
|
|
|
+ <span class="section-desc">尺寸要求:280 * 398,悬停可查看右滑面板效果 (图1、图2)</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="category-preview-container">
|
|
|
+ <!-- 图1: 分类菜单 -->
|
|
|
+ <div class="category-menu-mockup">
|
|
|
+ <div v-for="item in categoryList.filter((c) => c.status === 1).slice(0, 10)" :key="item.id" class="menu-item">
|
|
|
+ <div class="menu-icon">
|
|
|
+ <img v-if="item.icon" :src="item.icon" alt="" />
|
|
|
+ <el-icon v-else><Menu /></el-icon>
|
|
|
+ </div>
|
|
|
+ <div class="menu-name">{{ item.name }}</div>
|
|
|
+
|
|
|
+ <!-- 图2: 悬停右滑面板 - CSS 控制显示 -->
|
|
|
+ <div class="category-panel-mockup">
|
|
|
+ <div class="panel-header-line"></div>
|
|
|
+ <div class="panel-content">
|
|
|
+ <!-- 顶部标签栏 -->
|
|
|
+ <div class="panel-tabs">
|
|
|
+ <span v-for="tag in item.tags" :key="tag.name" class="panel-tab-item">{{ tag.name }}</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 品牌位 (图3) - 绝对定位至右上角 -->
|
|
|
+ <div class="brand-box">
|
|
|
+ <div class="brand-main-title">
|
|
|
+ {{ item.panelData.mainTitle }}<span class="brand-strong">{{ item.panelData.subTitle }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="brand-notes">
|
|
|
+ <template v-for="(note, nIdx) in item.panelData.notes" :key="nIdx">
|
|
|
+ <span class="note-item">{{ note.name }}</span>
|
|
|
+ <span v-if="nIdx < item.panelData.notes.length - 1" class="note-sep">|</span>
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="panel-body">
|
|
|
+ <!-- 左侧分类列表 -->
|
|
|
+ <div class="panel-main">
|
|
|
+ <div
|
|
|
+ v-for="group in getSubCategoryGroups(item.syncCategoryId).length
|
|
|
+ ? getSubCategoryGroups(item.syncCategoryId)
|
|
|
+ : item.panelData.groups"
|
|
|
+ :key="group.title"
|
|
|
+ class="category-group"
|
|
|
+ >
|
|
|
+ <div class="group-title">{{ group.title }}</div>
|
|
|
+ <div class="group-items">
|
|
|
+ <span v-for="sub in group.items" :key="sub" class="group-item">{{ sub }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 模块二:列表设置 -->
|
|
|
+ <div class="editor-section">
|
|
|
+ <div class="section-header">
|
|
|
+ <span class="section-title">分类列表设置</span>
|
|
|
+ <span class="section-desc">管理首页分类菜单的显示内容、图标、标签及关联分类</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="list-toolbar">
|
|
|
+ <div class="theme-color-setting-pro">
|
|
|
+ <span class="label">主题色:</span>
|
|
|
+ <el-color-picker v-model="categoryThemeColor" />
|
|
|
+ <span class="value">{{ categoryThemeColor }}</span>
|
|
|
+ </div>
|
|
|
+ <el-button type="primary" icon="Plus" @click="handleAddCategory">新增分类</el-button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <el-table :data="categoryList" border style="width: 100%" header-cell-class-name="table-header-custom">
|
|
|
+ <el-table-column label="排序" width="70" align="center">
|
|
|
+ <template #default="{ $index }">
|
|
|
+ <div class="rank-box">
|
|
|
+ <el-icon v-if="$index > 0" class="rank-icon" @click="moveCategory($index, -1)"><CaretTop /></el-icon>
|
|
|
+ <el-icon v-if="$index < categoryList.length - 1" class="rank-icon" @click="moveCategory($index, 1)"><CaretBottom /></el-icon>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="菜单名称" prop="name" min-width="200" show-overflow-tooltip />
|
|
|
+ <el-table-column label="图标" width="80" align="center">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-image v-if="scope.row.icon" :src="scope.row.icon" fit="contain" class="table-icon-preview" />
|
|
|
+ <span v-else class="text-gray">-</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="同步分类" prop="syncCategory" width="120" />
|
|
|
+ <el-table-column label="标签" min-width="180">
|
|
|
+ <template #default="scope">
|
|
|
+ <div class="tag-wrap">
|
|
|
+ <el-tag v-for="tag in scope.row.tags" :key="tag.name" size="small" class="m-r-5">{{ tag.name }}</el-tag>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="状态" width="100" align="center">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-switch
|
|
|
+ v-model="scope.row.status"
|
|
|
+ :active-value="1"
|
|
|
+ :inactive-value="0"
|
|
|
+ @change="handleCategoryStatusChange(scope.row)"
|
|
|
+ active-color="#13ce66"
|
|
|
+ />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="操作" width="150" align="center" fixed="right">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-button link type="primary" @click="handleEditCategory(scope.row)">编辑</el-button>
|
|
|
+ <el-button link type="danger" @click="handleRemoveCategory(scope.$index)">删除</el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 头部分类编辑区 -->
|
|
|
+ <div v-else-if="activeSubTab === 'headerCategory'" class="header-category-editor">
|
|
|
+ <!-- 实时预览区 (1350*60) -->
|
|
|
+ <div class="preview-section">
|
|
|
+ <div class="preview-title">头部分类实时预览</div>
|
|
|
+ <div class="header-preview-outer">
|
|
|
+ <div class="header-preview-box">
|
|
|
+ <!-- 左侧箭头 -->
|
|
|
+ <div v-show="showLeftArrow" class="nav-arrow left-arrow" @click="scrollHeaderNav('left')">
|
|
|
+ <el-icon><ArrowLeft /></el-icon>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="header-nav-scroll" ref="headerNavScrollRef" @scroll="updateNavArrows">
|
|
|
+ <div class="header-nav-list">
|
|
|
+ <div v-for="item in headerCategoryList.filter((i) => i.status === 1)" :key="item.id" class="header-nav-item">
|
|
|
+ <div class="item-icon">
|
|
|
+ <img v-if="item.icon" :src="item.icon" alt="" />
|
|
|
+ <el-icon v-else :style="{ color: headerThemeColor }"><Menu /></el-icon>
|
|
|
+ </div>
|
|
|
+ <span class="item-text" :style="{ '--hover-color': headerThemeColor }">{{ item.title }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 右侧箭头 -->
|
|
|
+ <div v-show="showRightArrow" class="nav-arrow right-arrow" @click="scrollHeaderNav('right')">
|
|
|
+ <el-icon><ArrowRight /></el-icon>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="preview-desc">尺寸要求:1350 * 60,导航项横向排列,支持图标与主题色联动</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 列表设置 -->
|
|
|
+ <div class="editor-section">
|
|
|
+ <div class="section-header">
|
|
|
+ <span class="section-title">分类列表设置管理</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="list-toolbar">
|
|
|
+ <div class="theme-color-setting-pro">
|
|
|
+ <span class="label">主题色设置:</span>
|
|
|
+ <el-color-picker v-model="headerThemeColor" />
|
|
|
+ <span class="value">{{ headerThemeColor }}</span>
|
|
|
+ </div>
|
|
|
+ <el-button type="primary" icon="Plus" @click="handleAddHeaderCategory">新增分类</el-button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <el-table :data="headerCategoryList" border style="width: 100%" header-cell-class-name="table-header-custom">
|
|
|
+ <el-table-column label="排序" width="80" align="center">
|
|
|
+ <template #default="{ $index }">
|
|
|
+ <div class="rank-box">
|
|
|
+ <el-icon v-if="$index > 0" class="rank-icon" @click="moveHeader($index, -1)"><CaretTop /></el-icon>
|
|
|
+ <el-icon v-if="$index < headerCategoryList.length - 1" class="rank-icon" @click="moveHeader($index, 1)"><CaretBottom /></el-icon>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="title" label="分类标题名称" min-width="150" />
|
|
|
+ <el-table-column label="图标" width="100" align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <img v-if="row.icon" :src="row.icon" class="table-icon-preview" style="width: 22px; height: 22px" />
|
|
|
+ <span v-else>-</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="link" label="跳转地址" show-overflow-tooltip />
|
|
|
+ <el-table-column label="打开方式" width="120" align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-tag :type="row.openMode === 'new' ? 'success' : 'info'" size="small">
|
|
|
+ {{ row.openMode === 'new' ? '新窗口' : '当前页' }}
|
|
|
+ </el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="状态" width="100" align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-switch
|
|
|
+ v-model="row.status"
|
|
|
+ :active-value="1"
|
|
|
+ :inactive-value="0"
|
|
|
+ @change="handleHeaderCategoryStatusChange(row)"
|
|
|
+ active-color="#13ce66"
|
|
|
+ />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="操作" width="150" fixed="right" align="center">
|
|
|
+ <template #default="{ row, $index }">
|
|
|
+ <el-button type="primary" link @click="handleEditHeaderCategory(row, $index)">编辑</el-button>
|
|
|
+ <el-button type="danger" link @click="handleDeleteHeaderCategory($index)">删除</el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 广告模块编辑区 -->
|
|
|
+ <div v-else-if="activeSubTab === 'ad-module'" class="ad-module-editor">
|
|
|
+ <div class="preview-section">
|
|
|
+ <div class="preview-title">广告模块实时预览</div>
|
|
|
+ <div class="ad-preview-grid">
|
|
|
+ <!-- 广告一: 企业购x百亿补贴 (484*190) -->
|
|
|
+ <div class="ad-item ad-subsidy" :style="{ width: '484px', height: '190px' }">
|
|
|
+ <div class="ad-header">
|
|
|
+ <div class="ad-title-main" :style="getAdTitleStyle(0, 'main')">{{ getAdTitleText(0, 'main') }}</div>
|
|
|
+ <div class="ad-title-sub" :style="getAdTitleStyle(0, 'sub')">{{ getAdTitleText(0, 'sub') }}</div>
|
|
|
+ </div>
|
|
|
+ <div class="ad-products-subsidy">
|
|
|
+ <div v-for="item in adModules[0].items" :key="item.id" class="product-item">
|
|
|
+ <div class="product-img"><img :src="item.imageUrl" alt="" /></div>
|
|
|
+ <div class="product-price">¥{{ item.price }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="ad-hover-mask">
|
|
|
+ <el-button type="primary" size="small" @click="handleEditAd(0)">设置</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 广告二: 企采榜单 (253*196) -->
|
|
|
+ <div class="ad-item ad-ranking" :style="{ width: '253px', height: '196px' }">
|
|
|
+ <div class="ad-header">
|
|
|
+ <div class="ad-title-main" :style="getAdTitleStyle(1, 'main')">{{ getAdTitleText(1, 'main') }}</div>
|
|
|
+ <div class="ad-title-sub" :style="getAdTitleStyle(1, 'sub')">{{ getAdTitleText(1, 'sub') }}</div>
|
|
|
+ </div>
|
|
|
+ <div class="ad-products-ranking">
|
|
|
+ <div v-for="item in adModules[1].items" :key="item.id" class="ranking-item">
|
|
|
+ <div class="ranking-badge">{{ item.tagText || '排行榜' }} ></div>
|
|
|
+ <div class="product-img"><img :src="item.imageUrl" alt="" /></div>
|
|
|
+ <div class="ranking-footer">已售{{ item.salesCount }}件</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="ad-hover-mask">
|
|
|
+ <el-button type="primary" size="small" @click="handleEditAd(1)">设置</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 广告三: 品牌好店 (253*196) -->
|
|
|
+ <div class="ad-item ad-brand" :style="{ width: '253px', height: '196px' }">
|
|
|
+ <div class="ad-header">
|
|
|
+ <div class="ad-title-main" :style="getAdTitleStyle(2, 'main')">{{ getAdTitleText(2, 'main') }}</div>
|
|
|
+ <div class="ad-title-sub" :style="getAdTitleStyle(2, 'sub')">{{ getAdTitleText(2, 'sub') }}</div>
|
|
|
+ </div>
|
|
|
+ <div class="ad-brands-content">
|
|
|
+ <div v-for="item in adModules[2].items" :key="item.id" class="brand-item">
|
|
|
+ <div class="brand-logo"><img :src="item.imageUrl" alt="" /></div>
|
|
|
+ <div class="brand-name">{{ item.tagLink || item.productName }}</div>
|
|
|
+ <div class="brand-tag-btn">{{ item.tagText || '品质保障' }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="ad-hover-mask">
|
|
|
+ <el-button type="primary" size="small" @click="handleEditAd(2)">设置</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 广告四: 企业精选 (253*196) -->
|
|
|
+ <div class="ad-item ad-selection" :style="{ width: '253px', height: '196px' }">
|
|
|
+ <div class="ad-header">
|
|
|
+ <div class="ad-title-main" :style="getAdTitleStyle(3, 'main')">{{ getAdTitleText(3, 'main') }}</div>
|
|
|
+ <div class="ad-title-sub" :style="getAdTitleStyle(3, 'sub')">{{ getAdTitleText(3, 'sub') }}</div>
|
|
|
+ </div>
|
|
|
+ <div class="ad-products-selection">
|
|
|
+ <div v-for="item in adModules[3].items" :key="item.id" class="selection-item">
|
|
|
+ <div class="product-img"><img :src="item.imageUrl" alt="" /></div>
|
|
|
+ <div class="product-price-row">
|
|
|
+ <span class="p-unit">¥</span>
|
|
|
+ <span class="p-val">{{ item.price }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="ad-hover-mask">
|
|
|
+ <el-button type="primary" size="small" @click="handleEditAd(3)">设置</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 广告五: 企业购x京东新品 (253*196) -->
|
|
|
+ <div class="ad-item ad-new" :style="{ width: '253px', height: '196px' }">
|
|
|
+ <div class="ad-header">
|
|
|
+ <div class="ad-title-main" :style="getAdTitleStyle(4, 'main')">{{ getAdTitleText(4, 'main') }}</div>
|
|
|
+ <div class="ad-title-sub" :style="getAdTitleStyle(4, 'sub')">{{ getAdTitleText(4, 'sub') }}</div>
|
|
|
+ </div>
|
|
|
+ <div class="ad-products-selection">
|
|
|
+ <div v-for="item in adModules[4].items" :key="item.id" class="selection-item">
|
|
|
+ <div class="product-img"><img :src="item.imageUrl" alt="" /></div>
|
|
|
+ <div class="product-price-row center">
|
|
|
+ <span class="p-unit">¥</span>
|
|
|
+ <span class="p-val">{{ item.price }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="ad-hover-mask">
|
|
|
+ <el-button type="primary" size="small" @click="handleEditAd(4)">设置</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="preview-desc">提示:支持 5 个独立广告模块配置,悬停模块显示“设置”按钮进行内容管理</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 场景方案编辑区 -->
|
|
|
+ <!-- 场景方案编辑区 -->
|
|
|
+ <div v-else-if="activeSubTab === 'scenario'" class="scenario-editor-container">
|
|
|
+ <!-- 实时预览区 -->
|
|
|
+ <div class="preview-section-standard">
|
|
|
+ <div class="section-title-standard">场景方案实时预览</div>
|
|
|
+ <div class="scenario-preview-outer">
|
|
|
+ <div class="scenario-preview-box-clean" :style="{ '--s-theme-color': scenarioSettings.themeColor }">
|
|
|
+ <!-- 左侧标题区 -->
|
|
|
+ <div class="scenario-header-left">
|
|
|
+ <div class="s-title-group">
|
|
|
+ <span class="s-main-title">{{ scenarioSettings.mainTitle }}</span>
|
|
|
+ <span class="s-sub-title-inline">{{ scenarioSettings.subTitle }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="s-btn-wrap">
|
|
|
+ <div class="s-btn-premium">
|
|
|
+ {{ scenarioSettings.btnText }}
|
|
|
+ <el-icon class="m-l-5"><CaretRight /></el-icon>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 右侧方案卡片 -->
|
|
|
+ <div class="scenario-cards-wrap">
|
|
|
+ <div
|
|
|
+ v-for="(item, idx) in scenarioList"
|
|
|
+ :key="item.id"
|
|
|
+ class="scenario-card-premium"
|
|
|
+ :class="{ 'hidden-card-fourth': idx === 3 }"
|
|
|
+ :style="{ backgroundColor: hexToRgba(item.bgColor, item.opacity) }"
|
|
|
+ >
|
|
|
+ <div class="card-top-header">
|
|
|
+ <div class="card-titles-group">
|
|
|
+ <span class="card-main-title" :style="{ color: item.titleColor }">{{ item.title }}</span>
|
|
|
+ <span class="card-sub-title" :style="{ color: item.subTitleColor }">{{ item.subTitle }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="card-arrow-icon" :style="{ backgroundColor: item.titleColor }">
|
|
|
+ <el-icon><ArrowRight /></el-icon>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="card-image-content">
|
|
|
+ <img :src="item.image || defaultPlaceholder" alt="" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="preview-tip">尺寸要求:1600 * 158,支持响应式隐藏及主题色联动</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 全局配置 -->
|
|
|
+ <div class="config-section-standard">
|
|
|
+ <div class="section-title-standard">场景解决方案全局设置</div>
|
|
|
+ <div class="settings-form-standard">
|
|
|
+ <el-form :model="scenarioSettings" label-width="120px" label-position="left">
|
|
|
+ <el-form-item label="场景主标题:">
|
|
|
+ <el-input v-model="scenarioSettings.mainTitle" placeholder="请输入主标题" style="width: 400px" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="场景副标题:">
|
|
|
+ <el-input v-model="scenarioSettings.subTitle" placeholder="请输入副标题" style="width: 400px" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="按钮文字:">
|
|
|
+ <el-input v-model="scenarioSettings.btnText" placeholder="请输入按钮文字" style="width: 400px" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="跳转链接:">
|
|
|
+ <WebLinkInput v-model="scenarioSettings.jumpLink" placeholder="请输入跳转地址" style="width: 400px"/>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="主题背景色:">
|
|
|
+ <div class="theme-color-setting-pro">
|
|
|
+ <el-color-picker v-model="scenarioSettings.themeColor" />
|
|
|
+ <span class="value">{{ scenarioSettings.themeColor }}</span>
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 列表管理 -->
|
|
|
+ <div class="config-section-standard">
|
|
|
+ <div class="section-title-standard">方案卡片设置管理</div>
|
|
|
+ <el-table :data="scenarioList" border style="width: 100%" header-cell-class-name="table-header-custom" class="standard-table">
|
|
|
+ <el-table-column label="排序" width="80" align="center">
|
|
|
+ <template #default="{ $index }">
|
|
|
+ <div class="rank-action-btns">
|
|
|
+ <el-icon v-if="$index > 0" class="rank-btn-mini" @click="moveScenario($index, -1)"><ArrowUp /></el-icon>
|
|
|
+ <el-icon v-if="$index < scenarioList.length - 1" class="rank-btn-mini" @click="moveScenario($index, 1)"><ArrowDown /></el-icon>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="分类方案名称" min-width="250">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <div class="flex-column">
|
|
|
+ <span :style="{ color: row.titleColor, fontSize: '14px', fontWeight: 'bold' }">{{ row.title }}</span>
|
|
|
+ <span class="text-gray" style="font-size: 12px; margin-top: 4px">{{ row.subTitle }}</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="图标/封面图" width="200" align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-image :src="row.image" fit="cover" style="width: 160px; height: 48px; border-radius: 4px" />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="link" label="跳转链接" min-width="300" show-overflow-tooltip />
|
|
|
+ <el-table-column label="操作" width="120" align="center" fixed="right">
|
|
|
+ <template #default="{ row, $index }">
|
|
|
+ <el-button type="primary" link @click="handleEditScenario(row, $index)">编辑内容</el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 推荐设置编辑区 -->
|
|
|
+ <div v-else-if="activeSubTab === 'recommend'" class="recommend-editor-container">
|
|
|
+ <!-- 实时预览区 -->
|
|
|
+ <div class="preview-section-standard">
|
|
|
+ <div class="section-title-standard">为你推荐实时预览</div>
|
|
|
+ <div class="recommend-preview-outer">
|
|
|
+ <div class="recommend-preview-container">
|
|
|
+ <!-- 左侧箭头 -->
|
|
|
+ <div v-if="recShowLeft" class="recommend-nav-btn prev" @click="scrollRecommend('left')">
|
|
|
+ <el-icon><CaretLeft /></el-icon>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div
|
|
|
+ ref="recommendScrollRef"
|
|
|
+ class="recommend-preview-box"
|
|
|
+ :style="{ '--r-theme-color': recommendThemeColor }"
|
|
|
+ @scroll="updateRecArrows"
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ v-for="item in activeRecommendList"
|
|
|
+ :key="item.id"
|
|
|
+ :class="['recommend-item', { active: recommendActiveId === item.id }]"
|
|
|
+ @click="recommendActiveId = item.id"
|
|
|
+ >
|
|
|
+ <div class="recommend-icon">
|
|
|
+ <img :src="item.icon || defaultPlaceholder" alt="" />
|
|
|
+ </div>
|
|
|
+ <div class="recommend-text">
|
|
|
+ <div class="r-main-title" :style="{ color: recommendActiveId === item.id ? recommendThemeColor : '#333' }">{{ item.name }}</div>
|
|
|
+ <div
|
|
|
+ class="r-sub-title"
|
|
|
+ :style="{
|
|
|
+ color: recommendActiveId === item.id ? recommendThemeColor : '#999',
|
|
|
+ opacity: recommendActiveId === item.id ? 0.8 : 1
|
|
|
+ }"
|
|
|
+ >
|
|
|
+ {{ item.subTitle }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 右侧箭头 -->
|
|
|
+ <div v-if="recShowRight" class="recommend-nav-btn next" @click="scrollRecommend('right')">
|
|
|
+ <el-icon><CaretRight /></el-icon>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="preview-desc">尺寸要求:1600 * 88,分类图标 32 * 32,支持响应式平滑滚动及选中态色值联动</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 配置表单区域 -->
|
|
|
+ <div class="config-section-standard">
|
|
|
+ <div class="section-title-standard">为你推荐配置设置</div>
|
|
|
+ <el-form label-width="120px" inline class="m-b-20">
|
|
|
+ <el-form-item label="主题选中色调:">
|
|
|
+ <div class="theme-color-setting-pro">
|
|
|
+ <el-color-picker v-model="recommendThemeColor" />
|
|
|
+ <span class="value">{{ recommendThemeColor }}</span>
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="商品列表主题色:">
|
|
|
+ <div class="theme-color-setting-pro">
|
|
|
+ <el-color-picker v-model="recommendProductThemeColor" />
|
|
|
+ <span class="value">{{ recommendProductThemeColor }}</span>
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item>
|
|
|
+ <el-button type="primary" icon="Plus" @click="handleAddRecommend">新增推荐分类</el-button>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+
|
|
|
+ <el-table :data="recommendList" border style="width: 100%" header-cell-class-name="table-header-custom" class="standard-table">
|
|
|
+ <el-table-column label="位置" width="80" align="center">
|
|
|
+ <template #default="{ $index }">
|
|
|
+ <div class="rank-action-btns">
|
|
|
+ <el-icon v-if="$index > 0" class="rank-btn-mini" @click="moveRecommend($index, -1)"><ArrowUp /></el-icon>
|
|
|
+ <el-icon v-if="$index < recommendList.length - 1" class="rank-btn-mini" @click="moveRecommend($index, 1)"><ArrowDown /></el-icon>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="分类名称" min-width="120">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <span style="font-weight: bold; color: #333">{{ row.name }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="subTitle" label="副标题" min-width="150" />
|
|
|
+ <el-table-column label="图标" width="100" align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <div class="table-icon-preview">
|
|
|
+ <img :src="row.icon || defaultPlaceholder" class="row-icon" />
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="数据类型" width="120" align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-tag :type="row.type === 'category' ? 'success' : 'warning'">
|
|
|
+ {{ row.type === 'category' ? '分类映射' : '商品自选' }}
|
|
|
+ </el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="关联类目/商品数" min-width="180">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <div v-if="row.type === 'category'" class="text-gray">{{ row.categoryLabel || '未设置' }}</div>
|
|
|
+ <div v-else class="text-gray">已选 {{ row.selectedProducts?.length || 0 }} 个商品</div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="启用状态" width="100" align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-switch
|
|
|
+ v-model="row.status"
|
|
|
+ :active-value="1"
|
|
|
+ :inactive-value="0"
|
|
|
+ @change="handleRecommendStatusChange(row)"
|
|
|
+ active-color="#13ce66"
|
|
|
+ />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="操作" width="220" align="center" fixed="right">
|
|
|
+ <template #default="{ row, $index }">
|
|
|
+ <el-button type="primary" link @click="handleEditRecommend(row, $index)">编辑</el-button>
|
|
|
+ <el-button v-if="row.type === 'select'" type="primary" link @click="openRecommendProductSelect($index)">选商品</el-button>
|
|
|
+ <el-button type="danger" link @click="handleDeleteRecommend($index)">删除</el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 广告模块设置弹窗 -->
|
|
|
+ <el-dialog
|
|
|
+ v-model="adDialogVisible"
|
|
|
+ :title="`设置广告模块 - ${adModules[currentAdIdx]?.title}`"
|
|
|
+ width="1050px"
|
|
|
+ custom-class="ad-setup-dialog"
|
|
|
+ destroy-on-close
|
|
|
+ >
|
|
|
+ <el-form :model="adForm" label-width="100px" class="dialog-form-inner">
|
|
|
+ <el-form-item label="模块标题:">
|
|
|
+ <div class="theme-color-setting-pro">
|
|
|
+ <el-input v-model="adForm.title" placeholder="请输入主标题" style="width: 300px" />
|
|
|
+ <el-color-picker v-model="adForm.titleColor" />
|
|
|
+ <span class="value">{{ adForm.titleColor }}</span>
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="副标题:">
|
|
|
+ <div class="theme-color-setting-pro">
|
|
|
+ <el-input v-model="adForm.subTitle" placeholder="请输入副标题" style="width: 300px" />
|
|
|
+ <el-color-picker v-model="adForm.subTitleColor" />
|
|
|
+ <span class="value">{{ adForm.subTitleColor }}</span>
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <div class="config-subtitle">商品/品牌列表 (固定 {{ adForm.items.length }} 项)</div>
|
|
|
+ <el-table :data="adForm.items" border style="width: 100%" class="m-t-10">
|
|
|
+ <el-table-column label="位置" width="60" align="center">
|
|
|
+ <template #default="scope">{{ scope.$index + 1 }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="图片" width="100" align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <img :src="row.imageUrl" style="width: 50px; height: 50px; object-fit: contain" />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <!-- 通用商品信息列 (百亿补贴、企采精选、京东新品) -->
|
|
|
+ <el-table-column v-if="currentAdIdx === 0 || currentAdIdx === 3 || currentAdIdx === 4" label="商品信息" min-width="180">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <div class="table-info-cell">
|
|
|
+ <div class="info-name">{{ row.productName || '未选择' }}</div>
|
|
|
+ <div class="info-price">价格:¥{{ row.price }}</div>
|
|
|
+ <div class="info-id">ID: {{ row.id || '-' }}</div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ <!-- 企采榜单 专属列 -->
|
|
|
+ <template v-if="currentAdIdx === 1">
|
|
|
+ <el-table-column label="商品信息" min-width="150">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <div class="info-name">{{ row.productName || '未选择' }}</div>
|
|
|
+ <div class="info-id">ID: {{ row.id || '-' }}</div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="排行标签" width="220">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-input v-model="row.tagText" placeholder="标签文字" size="small" class="m-b-5" />
|
|
|
+ <WebLinkInput v-model="row.tagLink" placeholder="跳转链接" size="small"/>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="销量数据" width="130">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-input v-model="row.salesCount" placeholder="销量" size="small">
|
|
|
+ <template #prepend>已售</template>
|
|
|
+ </el-input>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <!-- 品牌好店 专属列 -->
|
|
|
+ <template v-if="currentAdIdx === 2">
|
|
|
+ <el-table-column label="品牌名称" min-width="180">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <div class="brand-name-display">{{ row.productName || '未选择' }}</div>
|
|
|
+ <div class="info-id m-t-5">ID: {{ row.id || '-' }}</div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="品牌标签" width="300">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <div class="flex-column gap-10">
|
|
|
+ <el-input v-model="row.tagText" placeholder="标签文字 (如: 品质保障)" />
|
|
|
+ <el-input v-model="row.tagLink" placeholder="描述文字 (控制预览品牌名称)" />
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <el-table-column label="操作" width="110" align="center" fixed="right">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-button type="primary" link @click="openProductSelect(scope.$index)">
|
|
|
+ <el-icon class="m-r-5"><Edit /></el-icon>
|
|
|
+ {{ scope.row?.id ? '修改' : '选择' }}
|
|
|
+ </el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <el-button @click="adDialogVisible = false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="submitAdForm">保存设置</el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <!-- 选择商品/品牌抽屉 (加宽版) -->
|
|
|
+ <el-drawer v-model="selectDialogVisible" :title="currentAdIdx === 2 ? '选择品牌' : '选择商品'" size="850px" append-to-body>
|
|
|
+ <div class="select-dialog-content">
|
|
|
+ <el-input
|
|
|
+ v-model="productQueryParams.itemName"
|
|
|
+ :placeholder="currentAdIdx === 2 ? '输入品牌名称搜索' : '输入商品名称或ID搜索'"
|
|
|
+ prefix-icon="Search"
|
|
|
+ clearable
|
|
|
+ style="margin-bottom: 20px"
|
|
|
+ @input="getProductList"
|
|
|
+ />
|
|
|
+ <div class="select-list">
|
|
|
+ <div
|
|
|
+ v-for="item in pagedSelectList"
|
|
|
+ :key="item.id"
|
|
|
+ class="select-item-row"
|
|
|
+ :class="{ active: selectedTempId === item.id }"
|
|
|
+ @click="selectedTempId = item.id"
|
|
|
+ >
|
|
|
+ <img :src="item.image" class="select-item-img" />
|
|
|
+ <div class="select-item-info">
|
|
|
+ <div class="select-item-name">{{ item.name }}</div>
|
|
|
+ <div v-if="currentAdIdx !== 2" class="select-item-price">¥{{ item.price }}</div>
|
|
|
+ <div class="select-item-id">ID: {{ item.id }}</div>
|
|
|
+ </div>
|
|
|
+ <el-radio v-model="selectedTempId" :label="item.id"><span></span></el-radio>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 分页组件 -->
|
|
|
+ <div class="select-pagination">
|
|
|
+ <el-pagination
|
|
|
+ v-model:current-page="selectCurrentPage"
|
|
|
+ v-model:page-size="selectPageSize"
|
|
|
+ :total="productTotal"
|
|
|
+ :page-sizes="[8, 12, 20, 50]"
|
|
|
+ layout="total, sizes, prev, pager, next, jumper"
|
|
|
+ background
|
|
|
+ @current-change="onProductPageChange"
|
|
|
+ @size-change="onProductPageSizeChange"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <template #footer>
|
|
|
+ <div style="flex: auto; text-align: right; padding: 20px">
|
|
|
+ <el-button @click="selectDialogVisible = false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="confirmSelect">确定选择</el-button>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-drawer>
|
|
|
+
|
|
|
+ <!-- 轮播图编辑弹窗 -->
|
|
|
+ <el-dialog v-model="dialogVisible" :title="dialogType === 'add' ? '新增轮播图' : '修改轮播图'" width="800px" destroy-on-close>
|
|
|
+ <el-form :model="carouselForm" label-width="100px" class="dialog-form-inner">
|
|
|
+ <el-form-item label="轮播图片:">
|
|
|
+ <UploadImage v-model="carouselForm.image" :limit="1" width="552px" height="190px" />
|
|
|
+ <div class="upload-tip">推荐尺寸:552 * 190,支持上传本地图片</div>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="跳转地址:">
|
|
|
+ <WebLinkInput v-model="carouselForm.link" placeholder="请输入或选择链接" />
|
|
|
+ </el-form-item>
|
|
|
+ <!-- <el-form-item label="跳转地址:">
|
|
|
+ <el-input v-model="carouselForm.link" placeholder="请输入以 http:// 或 https:// 开头的地址" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="打开方式:">
|
|
|
+ <el-radio-group v-model="carouselForm.target">
|
|
|
+ <el-radio label="_self">当前窗口</el-radio>
|
|
|
+ <el-radio label="_blank">新窗口</el-radio>
|
|
|
+ </el-radio-group>
|
|
|
+ </el-form-item> -->
|
|
|
+ <el-form-item label="启用状态:">
|
|
|
+ <el-switch v-model="carouselForm.status" :active-value="1" :inactive-value="0" active-color="#13ce66" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <el-button @click="dialogVisible = false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="submitCarouselForm">确定</el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <!-- 分类设置编辑弹窗 -->
|
|
|
+ <el-dialog v-model="categoryDialogVisible" :title="categoryDialogType === 'add' ? '新增分类' : '修改分类'" width="900px" destroy-on-close>
|
|
|
+ <el-form :model="categoryForm" label-width="100px" class="dialog-form-inner">
|
|
|
+ <el-tabs type="border-card">
|
|
|
+ <el-tab-pane label="基础设置">
|
|
|
+ <el-form-item label="菜单名称:">
|
|
|
+ <el-input v-model="categoryForm.name" placeholder="如:办公电脑 / 办公打印 / 电脑组件" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="图标:">
|
|
|
+ <UploadImage v-model="categoryForm.icon" :limit="1" width="48px" height="48px" />
|
|
|
+ <div class="upload-tip">推荐尺寸:16 * 16,支持上传透明背景 PNG</div>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="同步分类:">
|
|
|
+ <el-select
|
|
|
+ v-model="categoryForm.syncCategoryId"
|
|
|
+ placeholder="请选择或输入关联的一级分类"
|
|
|
+ style="width: 100%"
|
|
|
+ filterable
|
|
|
+ allow-create
|
|
|
+ default-first-option
|
|
|
+ @change="onSyncCategoryChange"
|
|
|
+ >
|
|
|
+ <el-option v-for="opt in syncCategoryOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="标签设置:">
|
|
|
+ <div class="notes-config-list">
|
|
|
+ <div v-for="(tag, index) in categoryForm.tags" :key="index" class="note-config-row">
|
|
|
+ <el-input v-model="tag.name" placeholder="标签名称" style="width: 120px" />
|
|
|
+ <WebLinkInput v-model="tag.link" placeholder="跳转地址" style="flex: 1"/>
|
|
|
+ <el-button type="danger" icon="Delete" circle plain size="small" @click="removeCategoryTag(index)" />
|
|
|
+ </div>
|
|
|
+ <el-button type="primary" icon="Plus" link @click="addCategoryTag">添加标签</el-button>
|
|
|
+ </div>
|
|
|
+ <div class="field-tip">用于在右滑面板顶部显示的业务标签,支持配置独立跳转地址</div>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="启用状态:">
|
|
|
+ <el-switch v-model="categoryForm.status" :active-value="1" :inactive-value="0" active-color="#13ce66" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-tab-pane>
|
|
|
+
|
|
|
+ <el-tab-pane label="右滑面板配置">
|
|
|
+ <div class="panel-config-section">
|
|
|
+ <div class="config-subtitle">品牌位设置 (图3)</div>
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="主标题:">
|
|
|
+ <el-input v-model="categoryForm.panelData.mainTitle" placeholder="如:京东" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="副标题:">
|
|
|
+ <el-input v-model="categoryForm.panelData.subTitle" placeholder="如:3C数码" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <el-form-item label="便签列表:">
|
|
|
+ <div class="notes-config-list">
|
|
|
+ <div v-for="(note, index) in categoryForm.panelData.notes" :key="index" class="note-config-row">
|
|
|
+ <el-input v-model="note.name" placeholder="便签名称" style="width: 120px" />
|
|
|
+ <WebLinkInput v-model="note.link" placeholder="跳转地址" style="flex: 1" />
|
|
|
+ <el-button type="danger" icon="Delete" circle plain size="small" @click="removePanelNote(index)" />
|
|
|
+ </div>
|
|
|
+ <el-button type="primary" icon="Plus" link @click="addPanelNote">添加便签</el-button>
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="panel-config-section m-t-20">
|
|
|
+ <div class="config-subtitle">分类分组设置</div>
|
|
|
+ <div class="field-tip">目前支持在代码中静态配置,弹窗界面待进一步完善。</div>
|
|
|
+ </div>
|
|
|
+ </el-tab-pane>
|
|
|
+ </el-tabs>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <el-button @click="categoryDialogVisible = false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="submitCategoryForm">确定保存</el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <!-- 头部分类编辑弹窗 -->
|
|
|
+ <el-dialog v-model="headerDialogVisible" :title="headerEditIndex > -1 ? '编辑头部分类' : '新增头部分类'" width="600px" destroy-on-close>
|
|
|
+ <el-form :model="headerForm" label-width="100px" class="dialog-form-inner">
|
|
|
+ <el-form-item label="分类名称:">
|
|
|
+ <el-input v-model="headerForm.title" placeholder="如:公共采购" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="分类图标:">
|
|
|
+ <UploadImage v-model="headerForm.icon" :limit="1" width="48px" height="48px" />
|
|
|
+ <div class="upload-tip">建议尺寸:20*20,透明背景 PNG</div>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="跳转地址:">
|
|
|
+ <WebLinkInput v-model="headerForm.link" placeholder="请输入跳转链接" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="打开方式:">
|
|
|
+ <el-radio-group v-model="headerForm.openMode">
|
|
|
+ <el-radio label="current">当前页</el-radio>
|
|
|
+ <el-radio label="new">新窗口</el-radio>
|
|
|
+ </el-radio-group>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="启用状态:">
|
|
|
+ <el-switch v-model="headerForm.status" :active-value="1" :inactive-value="0" active-color="#13ce66" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <el-button @click="headerDialogVisible = false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="submitHeaderForm">确定保存</el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+ <!-- 场景方案编辑弹窗 -->
|
|
|
+
|
|
|
+ <!-- 已选商品管理抽屉 (占据 45% 屏幕) -->
|
|
|
+ <el-drawer
|
|
|
+ v-model="selectedProductDialogVisible"
|
|
|
+ :title="`已选商品管理 - ${recommendList[currentRecommendIndex]?.name}`"
|
|
|
+ size="45%"
|
|
|
+ direction="rtl"
|
|
|
+ custom-class="selected-products-drawer"
|
|
|
+ destroy-on-close
|
|
|
+ >
|
|
|
+ <div class="drawer-content-wrapper">
|
|
|
+ <div class="selected-products-header">
|
|
|
+ <div class="left-actions">
|
|
|
+ <el-button type="primary" icon="Plus" @click="openProductDrawer">添加商品</el-button>
|
|
|
+ <el-button
|
|
|
+ type="danger"
|
|
|
+ plain
|
|
|
+ icon="Delete"
|
|
|
+ :disabled="!recommendList[currentRecommendIndex]?.selectedProducts?.length"
|
|
|
+ @click="batchRemoveSelectedProducts"
|
|
|
+ >批量移除</el-button
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ <div class="right-info">
|
|
|
+ 共 <span class="count">{{ recommendList[currentRecommendIndex]?.selectedProducts?.length || 0 }}</span> 个商品
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <el-table
|
|
|
+ ref="selectedProductsTableRef"
|
|
|
+ :data="pagedSelectedProducts"
|
|
|
+ border
|
|
|
+ height="calc(100vh - 280px)"
|
|
|
+ style="width: 100%"
|
|
|
+ header-cell-class-name="table-header-custom"
|
|
|
+ class="standard-table"
|
|
|
+ >
|
|
|
+ <el-table-column type="selection" width="55" align="center" />
|
|
|
+ <el-table-column label="商品图片" width="100" align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-image :src="row.image" style="width: 50px; height: 50px; border-radius: 4px; border: 1px solid #f0f0f0" fit="contain" />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="name" label="商品名称" min-width="250" show-overflow-tooltip />
|
|
|
+ <el-table-column prop="id" label="商品ID" width="120" align="center" />
|
|
|
+ <el-table-column label="单价" width="120" align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <span class="price-text">¥{{ row.price }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="操作" width="100" align="center" fixed="right">
|
|
|
+ <template #default="{ $index }">
|
|
|
+ <el-button type="danger" link @click="removeSelectedProduct($index)">移除</el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ <template #empty>
|
|
|
+ <div class="empty-placeholder">
|
|
|
+ <el-empty description="暂未添加任何商品" :image-size="160">
|
|
|
+ <el-button type="primary" size="large" @click="openProductDrawer">去添加商品</el-button>
|
|
|
+ </el-empty>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table>
|
|
|
+
|
|
|
+ <div class="drawer-pagination">
|
|
|
+ <el-pagination
|
|
|
+ v-model:current-page="selectedCurrentPage"
|
|
|
+ v-model:page-size="selectedPageSize"
|
|
|
+ :total="recommendList[currentRecommendIndex]?.selectedProducts?.length || 0"
|
|
|
+ :page-sizes="[10, 20, 50, 100]"
|
|
|
+ layout="total, sizes, prev, pager, next, jumper"
|
|
|
+ background
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <template #footer>
|
|
|
+ <div class="drawer-footer-actions">
|
|
|
+ <el-button @click="selectedProductDialogVisible = false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="submitSelectedProducts">确定保存</el-button>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-drawer>
|
|
|
+
|
|
|
+ <!-- 商品多选选择抽屉 (占据 1/3 屏幕) -->
|
|
|
+ <el-drawer
|
|
|
+ v-model="productSelectionDrawerVisible"
|
|
|
+ title="从商品库选择商品"
|
|
|
+ size="45%"
|
|
|
+ direction="rtl"
|
|
|
+ destroy-on-close
|
|
|
+ custom-class="product-selection-drawer"
|
|
|
+ >
|
|
|
+ <div class="drawer-content-wrapper">
|
|
|
+ <div class="drawer-search-bar">
|
|
|
+ <el-input v-model="productQueryParams.itemName" placeholder="输入商品名称或ID搜索" prefix-icon="Search" clearable @input="getProductList" />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="drawer-stat-bar">
|
|
|
+ <el-icon class="info-icon"><InfoFilled /></el-icon>
|
|
|
+ <span
|
|
|
+ >已勾选 <span class="highlight">{{ drawerSelection.length }}</span> 个商品</span
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <el-table
|
|
|
+ ref="drawerTableRef"
|
|
|
+ :data="pagedSelectList"
|
|
|
+ border
|
|
|
+ height="calc(100vh - 280px)"
|
|
|
+ style="width: 100%"
|
|
|
+ row-key="id"
|
|
|
+ @selection-change="handleDrawerSelectionChange"
|
|
|
+ >
|
|
|
+ <el-table-column type="selection" width="50" align="center" :reserve-selection="true" />
|
|
|
+ <el-table-column label="商品信息" min-width="200">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <div class="drawer-product-info">
|
|
|
+ <el-image :src="row.image" class="mini-img" fit="contain" />
|
|
|
+ <div class="detail">
|
|
|
+ <div class="name">{{ row.name }}</div>
|
|
|
+ <div class="id">ID: {{ row.id }}</div>
|
|
|
+ <div class="price">¥{{ row.price }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+
|
|
|
+ <div class="drawer-pagination">
|
|
|
+ <el-pagination
|
|
|
+ v-model:current-page="selectCurrentPage"
|
|
|
+ v-model:page-size="selectPageSize"
|
|
|
+ :total="productTotal"
|
|
|
+ :page-sizes="[10, 20, 50, 100]"
|
|
|
+ layout="total, sizes, prev, pager, next, jumper"
|
|
|
+ background
|
|
|
+ @current-change="onProductPageChange"
|
|
|
+ @size-change="onProductPageSizeChange"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <template #footer>
|
|
|
+ <div class="drawer-footer-actions">
|
|
|
+ <el-button @click="productSelectionDrawerVisible = false">取消</el-button>
|
|
|
+ <el-button type="primary" :disabled="!drawerSelection.length" @click="confirmDrawerSelection"
|
|
|
+ >确认添加 ({{ drawerSelection.length }})</el-button
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-drawer>
|
|
|
+ <el-dialog v-model="scenarioDialogVisible" title="编辑场景方案卡片" width="650px" destroy-on-close>
|
|
|
+ <el-form :model="scenarioForm" label-width="100px" class="dialog-form-inner">
|
|
|
+ <el-form-item label="主标题:">
|
|
|
+ <div class="flex-center gap-10">
|
|
|
+ <el-input v-model="scenarioForm.title" style="width: 200px" />
|
|
|
+ <div class="theme-color-setting-pro">
|
|
|
+ <el-color-picker v-model="scenarioForm.titleColor" />
|
|
|
+ <span class="value">{{ scenarioForm.titleColor }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="副标题:">
|
|
|
+ <div class="flex-center gap-10">
|
|
|
+ <el-input v-model="scenarioForm.subTitle" style="width: 200px" />
|
|
|
+ <div class="theme-color-setting-pro">
|
|
|
+ <el-color-picker v-model="scenarioForm.subTitleColor" />
|
|
|
+ <span class="value">{{ scenarioForm.subTitleColor }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="卡片背景:">
|
|
|
+ <div class="flex-center gap-10">
|
|
|
+ <div class="theme-color-setting-pro">
|
|
|
+ <el-color-picker v-model="scenarioForm.bgColor" />
|
|
|
+ <span class="value">{{ scenarioForm.bgColor }}</span>
|
|
|
+ </div>
|
|
|
+ <span class="text-gray m-l-10">不透明度:</span>
|
|
|
+ <el-slider v-model="scenarioForm.opacity" :min="0" :max="1" :step="0.05" style="width: 120px" />
|
|
|
+ <span class="value m-l-5">{{ Math.round(scenarioForm.opacity * 100) }}%</span>
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="封面图片:">
|
|
|
+ <UploadImage v-model="scenarioForm.image" :limit="1" width="272px" height="80px" />
|
|
|
+ <div class="upload-tip">建议尺寸:272 * 80</div>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="跳转链接:">
|
|
|
+ <WebLinkInput v-model="scenarioForm.link" placeholder="请输入跳转地址" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <el-button @click="scenarioDialogVisible = false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="submitScenarioForm">保存修改</el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <!-- 推荐设置编辑弹窗 -->
|
|
|
+ <el-dialog v-model="recommendDialogVisible" :title="recommendEditIndex > -1 ? '修改推荐分类' : '新增推荐分类'" width="600px" destroy-on-close>
|
|
|
+ <el-form :model="recommendForm" label-width="110px" class="dialog-form-inner">
|
|
|
+ <el-form-item label="主标题:">
|
|
|
+ <el-input v-model="recommendForm.name" placeholder="请输入分类名称" maxlength="6" show-word-limit />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="副标题:">
|
|
|
+ <el-input v-model="recommendForm.subTitle" placeholder="请输入副标题" maxlength="12" show-word-limit />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="分类图标:">
|
|
|
+ <UploadImage v-model="recommendForm.icon" :limit="1" width="64px" height="64px" />
|
|
|
+ <div class="upload-tip">建议尺寸:32 * 32,透明背景 PNG</div>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="数据类型:">
|
|
|
+ <el-radio-group v-model="recommendForm.type">
|
|
|
+ <el-radio label="select">商品自选</el-radio>
|
|
|
+ <el-radio label="category">分类映射</el-radio>
|
|
|
+ </el-radio-group>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item v-if="recommendForm.type === 'category'" label="映射分类:">
|
|
|
+ <el-cascader
|
|
|
+ v-model="recommendForm.categoryValue"
|
|
|
+ :options="categoryOptions"
|
|
|
+ :props="{ value: 'id', label: 'label', children: 'children', checkStrictly: true, expandTrigger: 'hover' }"
|
|
|
+ placeholder="请选择一级/二级/三级分类"
|
|
|
+ style="width: 100%"
|
|
|
+ filterable
|
|
|
+ clearable
|
|
|
+ @change="handleRecommendCategoryChange"
|
|
|
+ />
|
|
|
+ <div class="field-tip">选择后,该页签将自动抓取对应分类下的最新商品</div>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item label="启用状态:">
|
|
|
+ <el-switch v-model="recommendForm.status" :active-value="1" :inactive-value="0" active-color="#13ce66" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <el-button @click="recommendDialogVisible = false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="submitRecommendForm">保存设置</el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <!-- 快捷入口编辑弹窗 -->
|
|
|
+ <el-dialog
|
|
|
+ v-model="quickEntryDialogVisible"
|
|
|
+ :title="quickEntryDialogType === 'add' ? '新增快捷入口' : '编辑快捷入口'"
|
|
|
+ width="600px"
|
|
|
+ append-to-body
|
|
|
+ destroy-on-close
|
|
|
+ >
|
|
|
+ <el-form :model="quickEntryForm" label-width="100px" class="dialog-form-inner">
|
|
|
+ <el-form-item label="入口名称:">
|
|
|
+ <el-input v-model="quickEntryForm.name" placeholder="请输入入口名称(建议4-5个字)" maxlength="6" show-word-limit />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="图标:">
|
|
|
+ <UploadImage v-model="quickEntryForm.icon" :limit="1" width="48px" height="48px" />
|
|
|
+ <div class="upload-tip">建议尺寸: 48*48, 格式: PNG/SVG</div>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="标签文字:">
|
|
|
+ <el-input v-model="quickEntryForm.tag" placeholder="如:返100" maxlength="4" style="width: 200px" />
|
|
|
+ <div class="field-tip">显示在图标右上角的红色气泡,留空则不显示</div>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="跳转地址:">
|
|
|
+ <WebLinkInput v-model="quickEntryForm.link" placeholder="请输入跳转地址" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="启用状态:">
|
|
|
+ <el-switch v-model="quickEntryForm.status" :active-value="1" :inactive-value="0" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <el-button @click="quickEntryDialogVisible = false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="submitQuickEntryForm">确定</el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <!-- 底部固定操作栏 -->
|
|
|
+ <div class="footer-actions">
|
|
|
+ <el-button type="primary" class="btn-confirm" @click="handleMainSave">保存当前页配置</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script lang="ts" setup>
|
|
|
+import UploadImage from '@/components/upload-image/index.vue';
|
|
|
+import WebLinkInput from '@/components/WebLinkInput/index.vue';
|
|
|
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
|
|
+import { ref, reactive, computed, watch, onMounted, onUnmounted, nextTick } from 'vue';
|
|
|
+import { ElMessage, ElMessageBox } from 'element-plus';
|
|
|
+import {
|
|
|
+ listSearchConfig,
|
|
|
+ getSearchConfig,
|
|
|
+ delSearchConfig,
|
|
|
+ addSearchConfig,
|
|
|
+ updateSearchConfig,
|
|
|
+ getCurrentSearchConfig
|
|
|
+} from '@/api/enterprisePurchase/searchConfig';
|
|
|
+import { SearchConfigVO, SearchConfigQuery, SearchConfigForm } from '@/api/enterprisePurchase/searchConfig/types';
|
|
|
+import { listAdLeft, getAdLeft, delAdLeft, addAdLeft, updateAdLeft, getCurrentAdLeft } from '@/api/enterprisePurchase/adLeft';
|
|
|
+import { AdLeftVO, AdLeftQuery, AdLeftForm } from '@/api/enterprisePurchase/adLeft/types';
|
|
|
+import { listCarousel, getCarousel, delCarousel, addCarousel, updateCarousel, changeStatus } from '@/api/enterprisePurchase/carousel';
|
|
|
+import { CarouselVO, CarouselQuery, CarouselForm } from '@/api/enterprisePurchase/carousel/types';
|
|
|
+import {
|
|
|
+ listCategoryMain,
|
|
|
+ getCategoryMain,
|
|
|
+ delCategoryMain,
|
|
|
+ addCategoryMain,
|
|
|
+ updateCategoryMain,
|
|
|
+ changeStatus as categoryMainChangeStatus
|
|
|
+} from '@/api/enterprisePurchase/categoryMain';
|
|
|
+import { CategoryMainVO, CategoryMainQuery, CategoryMainForm } from '@/api/enterprisePurchase/categoryMain/types';
|
|
|
+import { listScenarioCards, getScenarioCards, delScenarioCards, addScenarioCards, updateScenarioCards } from '@/api/enterprisePurchase/scenarioCards';
|
|
|
+import { ScenarioCardsVO, ScenarioCardsQuery, ScenarioCardsForm } from '@/api/enterprisePurchase/scenarioCards/types';
|
|
|
+import { listScenarioGlobalSettings, addScenarioGlobalSettings, updateScenarioGlobalSettings } from '@/api/enterprisePurchase/scenarioGlobalSettings';
|
|
|
+import { ScenarioGlobalSettingsForm } from '@/api/enterprisePurchase/scenarioGlobalSettings/types';
|
|
|
+import { listQuickEntryModule, addQuickEntryModule, updateQuickEntryModule } from '@/api/enterprisePurchase/quickEntryModule';
|
|
|
+import { QuickEntryModuleVO, QuickEntryModuleQuery, QuickEntryModuleForm } from '@/api/enterprisePurchase/quickEntryModule/types';
|
|
|
+import {
|
|
|
+ listQuickEntryItems,
|
|
|
+ getQuickEntryItems,
|
|
|
+ delQuickEntryItems,
|
|
|
+ addQuickEntryItems,
|
|
|
+ updateQuickEntryItems,
|
|
|
+ changeStatus as quickEntryItemsUpdateStatus
|
|
|
+} from '@/api/enterprisePurchase/quickEntryItems';
|
|
|
+import { QuickEntryItemsVO, QuickEntryItemsQuery, QuickEntryItemsForm } from '@/api/enterprisePurchase/quickEntryItems/types';
|
|
|
+import { listBase } from '@/api/pmsProduct/base';
|
|
|
+import { listBrand } from '@/api/product/brand';
|
|
|
+import { categoryTree } from '@/api/product/base/index';
|
|
|
+import { categoryTreeVO } from '@/api/product/category/types';
|
|
|
+import { listAdModuleConfig, getAdModuleConfig, addAdModuleConfig, updateAdModuleConfig } from '@/api/enterprisePurchase/adModuleConfig';
|
|
|
+import { AdModuleConfigVO, AdModuleConfigQuery, AdModuleConfigForm } from '@/api/enterprisePurchase/adModuleConfig/types';
|
|
|
+import {
|
|
|
+ listHeaderCategory,
|
|
|
+ getHeaderCategory,
|
|
|
+ delHeaderCategory,
|
|
|
+ addHeaderCategory,
|
|
|
+ updateHeaderCategory,
|
|
|
+ changeStatus as headerCategoryChangeStatus
|
|
|
+} from '@/api/enterprisePurchase/headerCategory';
|
|
|
+import {
|
|
|
+ listRecommendThemeConfig,
|
|
|
+ getRecommendThemeConfig,
|
|
|
+ delRecommendThemeConfig,
|
|
|
+ addRecommendThemeConfig,
|
|
|
+ updateRecommendThemeConfig
|
|
|
+} from '@/api/enterprisePurchase/recommendThemeConfig';
|
|
|
+import { RecommendThemeConfigVO, RecommendThemeConfigQuery, RecommendThemeConfigForm } from '@/api/enterprisePurchase/recommendThemeConfig/types';
|
|
|
+import {
|
|
|
+ listRecommendCategoryConfig,
|
|
|
+ getRecommendCategoryConfig,
|
|
|
+ delRecommendCategoryConfig,
|
|
|
+ addRecommendCategoryConfig,
|
|
|
+ updateRecommendCategoryConfig,
|
|
|
+ changeStatus as recommendThemeChangeStatus
|
|
|
+} from '@/api/enterprisePurchase/recommendCategoryConfig';
|
|
|
+import {
|
|
|
+ RecommendCategoryConfigVO,
|
|
|
+ RecommendCategoryConfigQuery,
|
|
|
+ RecommendCategoryConfigForm
|
|
|
+} from '@/api/enterprisePurchase/recommendCategoryConfig/types';
|
|
|
+import { HeaderCategoryVO, HeaderCategoryQuery, HeaderCategoryForm } from '@/api/enterprisePurchase/headerCategory/types';
|
|
|
+import {
|
|
|
+ ShoppingCart,
|
|
|
+ Plus,
|
|
|
+ Edit,
|
|
|
+ Delete,
|
|
|
+ Picture,
|
|
|
+ CaretTop,
|
|
|
+ CaretBottom,
|
|
|
+ Menu,
|
|
|
+ ArrowLeft,
|
|
|
+ ArrowRight,
|
|
|
+ Search,
|
|
|
+ InfoFilled,
|
|
|
+ ArrowUp,
|
|
|
+ ArrowDown
|
|
|
+} from '@element-plus/icons-vue';
|
|
|
+import { any } from 'vue-types';
|
|
|
+const uploadAction = import.meta.env.VITE_APP_BASE_API + '/resource/oss/upload';
|
|
|
+const defaultPlaceholder =
|
|
|
+ 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgdmlld0JveD0iMCAwIDEwMCAxMDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHJlY3Qgd2lkdGg9IjEwMCIgaGVpZ2h0PSIxMDAiIGZpbGw9IiNmMmYyZjIiLz48dGV4dCB4PSI1MCUiIHk9IjUwJSIgZG9taW5hbnQtYmFzZWxpbmU9Im1pZGRsZSIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC1zaXplPSIxMiIgZmlsbD0iI2JmYmZiZiI+5pys5peg5Zu+54mHPC90ZXh0Pjwvc3ZnPg==';
|
|
|
+
|
|
|
+// 基础导航状态
|
|
|
+const activeSubTab = ref('search');
|
|
|
+const subTabs = [
|
|
|
+ { label: '搜索栏', value: 'search' },
|
|
|
+ { label: '广告图', value: 'carousel' },
|
|
|
+ { label: '分类设置', value: 'category' },
|
|
|
+ { label: '头部分类', value: 'headerCategory' },
|
|
|
+ { label: '广告模块', value: 'ad-module' },
|
|
|
+ { label: '快捷入口', value: 'quick-entry' },
|
|
|
+ { label: '场景方案', value: 'scenario' },
|
|
|
+ { label: '推荐设置', value: 'recommend' }
|
|
|
+];
|
|
|
+
|
|
|
+// 首页核心配置表单 (搜索栏与基本设置)
|
|
|
+const form = reactive({
|
|
|
+ id: null,
|
|
|
+ mainTitle: '',
|
|
|
+ subTitle: '',
|
|
|
+ placeholderText: '',
|
|
|
+ hotWordsList: [{ name: '', link: '' }],
|
|
|
+ themeColor: '',
|
|
|
+ rightBtnIcon: '',
|
|
|
+ rightBtnText: '',
|
|
|
+ rightBtnLink: '',
|
|
|
+
|
|
|
+ categoryThemeColor: '',
|
|
|
+ headerThemeColor: ''
|
|
|
+});
|
|
|
+
|
|
|
+const leftAdForm = reactive({
|
|
|
+ id: null,
|
|
|
+ leftAdImage: '',
|
|
|
+ leftAdLink: ''
|
|
|
+});
|
|
|
+// 轮播图状态切换
|
|
|
+const handleCarouselStatusChange = async (row: any) => {
|
|
|
+ const oldValue = row.status; // 保存旧值(0 或 1)
|
|
|
+ try {
|
|
|
+ await changeStatus(row.id, row.status); // 传新值
|
|
|
+ proxy?.$modal.msgSuccess('操作成功');
|
|
|
+ } catch {
|
|
|
+ row.status = oldValue; // 失败回滚
|
|
|
+ proxy?.$modal.msgError('操作失败,请重试');
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 轮播图状态切换
|
|
|
+const handleCategoryStatusChange = async (row: any) => {
|
|
|
+ const oldValue = row.status; // 保存旧值(0 或 1)
|
|
|
+ try {
|
|
|
+ await categoryMainChangeStatus(row.id, row.status); // 传新值
|
|
|
+ proxy?.$modal.msgSuccess('操作成功');
|
|
|
+ } catch {
|
|
|
+ row.status = oldValue; // 失败回滚
|
|
|
+ proxy?.$modal.msgError('操作失败,请重试');
|
|
|
+ }
|
|
|
+};
|
|
|
+// 头部分类状态切换
|
|
|
+const handleHeaderCategoryStatusChange = async (row: any) => {
|
|
|
+ const oldValue = row.status; // 保存旧值(0 或 1)
|
|
|
+ try {
|
|
|
+ await headerCategoryChangeStatus(row.id, row.status); // 传新值
|
|
|
+ proxy?.$modal.msgSuccess('操作成功');
|
|
|
+ } catch {
|
|
|
+ row.status = oldValue; // 失败回滚
|
|
|
+ proxy?.$modal.msgError('操作失败,请重试');
|
|
|
+ }
|
|
|
+};
|
|
|
+// 快捷入口状态切换
|
|
|
+const handleQuickEntryStatusChange = async (row: any) => {
|
|
|
+ const oldValue = row.status; // 保存旧值(0 或 1)
|
|
|
+ try {
|
|
|
+ await quickEntryItemsUpdateStatus(row.id, row.status); // 传新值
|
|
|
+ proxy?.$modal.msgSuccess('操作成功');
|
|
|
+ } catch {
|
|
|
+ row.status = oldValue; // 失败回滚
|
|
|
+ proxy?.$modal.msgError('操作失败,请重试');
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 推荐状态切换
|
|
|
+const handleRecommendStatusChange = async (row: any) => {
|
|
|
+ const oldValue = row.status; // 保存旧值(0 或 1)
|
|
|
+ try {
|
|
|
+ await recommendThemeChangeStatus(row.id, row.status); // 传新值
|
|
|
+ proxy?.$modal.msgSuccess('操作成功');
|
|
|
+ } catch {
|
|
|
+ row.status = oldValue; // 失败回滚
|
|
|
+ proxy?.$modal.msgError('操作失败,请重试');
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 左侧广告配置ID(单例)
|
|
|
+const leftAdId = ref<string | number>('');
|
|
|
+const leftAdHover = ref(false);
|
|
|
+
|
|
|
+// 保存配置
|
|
|
+const handleMainSave = async () => {
|
|
|
+ try {
|
|
|
+ let res;
|
|
|
+ if (activeSubTab.value == 'search' || activeSubTab.value == 'category' || activeSubTab.value == 'headerCategory') {
|
|
|
+ // 构建提交数据,过滤空的热词
|
|
|
+ form.categoryThemeColor = categoryThemeColor.value;
|
|
|
+ form.headerThemeColor = headerThemeColor.value;
|
|
|
+ const submitData = {
|
|
|
+ ...form,
|
|
|
+ hotWordList: form.hotWordsList.filter((item) => item.name && item.name.trim() !== '')
|
|
|
+ };
|
|
|
+ if (form.id) {
|
|
|
+ res = await updateSearchConfig(submitData);
|
|
|
+ } else {
|
|
|
+ // 无ID则新增
|
|
|
+ res = await addSearchConfig(submitData);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (res.code === 200) {
|
|
|
+ ElMessage.success('配置保存成功');
|
|
|
+ // 如果是新增,保存返回的ID
|
|
|
+ if (!form.id && res.data) {
|
|
|
+ form.id = res.data.id;
|
|
|
+ }
|
|
|
+ // 重新获取最新配置
|
|
|
+ await getCurrentSearch();
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.msg || '保存失败');
|
|
|
+ }
|
|
|
+ } else if (activeSubTab.value == 'carousel') {
|
|
|
+ saveLeftAdConfig();
|
|
|
+ } else if (activeSubTab.value == 'scenario') {
|
|
|
+ saveScenarioGlobalSettings();
|
|
|
+ } else if (activeSubTab.value == 'quick-entry') {
|
|
|
+ saveQuickEntryModule();
|
|
|
+ } else if (activeSubTab.value == 'recommend') {
|
|
|
+ saveRecommendThemeConfig();
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('保存配置失败:', error);
|
|
|
+ ElMessage.error('保存配置失败');
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 获取当前配置
|
|
|
+const getCurrentAdLeftBtn = async () => {
|
|
|
+ try {
|
|
|
+ const res = await getCurrentAdLeft();
|
|
|
+ if (res.code === 200 && res.data) {
|
|
|
+ // 回显数据到表单
|
|
|
+ leftAdForm.id = res.data.id || null;
|
|
|
+ leftAdForm.leftAdImage = res.data.imageUrl || '';
|
|
|
+ leftAdForm.leftAdLink = res.data.link || '';
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取配置失败:', error);
|
|
|
+ ElMessage.error('获取配置失败');
|
|
|
+ }
|
|
|
+};
|
|
|
+// 获取当前配置
|
|
|
+const getCurrentSearch = async () => {
|
|
|
+ try {
|
|
|
+ const res = await getCurrentSearchConfig();
|
|
|
+ if (res.code === 200 && res.data) {
|
|
|
+ // 回显数据到表单
|
|
|
+ form.id = res.data.id || null;
|
|
|
+ form.mainTitle = res.data.mainTitle || '';
|
|
|
+ form.subTitle = res.data.subTitle || '';
|
|
|
+ form.placeholderText = res.data.placeholderText || '';
|
|
|
+ form.themeColor = res.data.themeColor || '';
|
|
|
+ form.rightBtnIcon = res.data.rightBtnIcon || '';
|
|
|
+ form.rightBtnText = res.data.rightBtnText || '';
|
|
|
+ form.rightBtnLink = res.data.rightBtnLink || '';
|
|
|
+
|
|
|
+ form.categoryThemeColor = res.data.categoryThemeColor || '';
|
|
|
+ form.headerThemeColor = res.data.headerThemeColor || '';
|
|
|
+ categoryThemeColor.value = form.categoryThemeColor;
|
|
|
+ headerThemeColor.value = form.headerThemeColor;
|
|
|
+
|
|
|
+ // 处理热词列表
|
|
|
+ if (res.data.hotWordList && Array.isArray(res.data.hotWordList)) {
|
|
|
+ form.hotWordsList =
|
|
|
+ res.data.hotWordList.length > 0
|
|
|
+ ? res.data.hotWordList.map((item) => ({
|
|
|
+ name: item.name || '',
|
|
|
+ link: item.link || ''
|
|
|
+ }))
|
|
|
+ : [{ name: '', link: '' }];
|
|
|
+ } else {
|
|
|
+ form.hotWordsList = [{ name: '', link: '' }];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取配置失败:', error);
|
|
|
+ ElMessage.error('获取配置失败');
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const handleMainReset = () => {
|
|
|
+ ElMessageBox.confirm('确定要重置当前页面的所有修改吗?', '提示', {
|
|
|
+ confirmButtonText: '确定重置',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ type: 'warning'
|
|
|
+ }).then(() => {
|
|
|
+ ElMessage.info('已还原至初始状态');
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+// 搜索栏逻辑
|
|
|
+const currentPlaceholderIndex = ref(0);
|
|
|
+let timer = null;
|
|
|
+
|
|
|
+const searchPlaceholderList = computed(() => {
|
|
|
+ return form.placeholderText
|
|
|
+ .split(',')
|
|
|
+ .map((t) => t.trim())
|
|
|
+ .filter((t) => t !== '');
|
|
|
+});
|
|
|
+
|
|
|
+const addHotWord = () => {
|
|
|
+ form.hotWordsList.push({ name: '', link: '' });
|
|
|
+};
|
|
|
+
|
|
|
+const removeHotWord = (index) => {
|
|
|
+ form.hotWordsList.splice(index, 1);
|
|
|
+};
|
|
|
+
|
|
|
+const startPlaceholderScroll = () => {
|
|
|
+ if (timer) clearInterval(timer);
|
|
|
+ if (searchPlaceholderList.value.length <= 1) return;
|
|
|
+
|
|
|
+ timer = setInterval(() => {
|
|
|
+ currentPlaceholderIndex.value = (currentPlaceholderIndex.value + 1) % searchPlaceholderList.value.length;
|
|
|
+ }, 3000);
|
|
|
+};
|
|
|
+
|
|
|
+// 广告图模块逻辑
|
|
|
+
|
|
|
+const carouselList = ref<CarouselVO[]>([]);
|
|
|
+
|
|
|
+// 获取轮播图列表
|
|
|
+const getCarouselList = async () => {
|
|
|
+ try {
|
|
|
+ const res = await listCarousel();
|
|
|
+ if (res.code === 200 && res.rows) {
|
|
|
+ carouselList.value = res.rows.map((item: CarouselVO) => ({
|
|
|
+ ...item,
|
|
|
+ image: item.imageUrl || ''
|
|
|
+ }));
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取轮播图列表失败:', error);
|
|
|
+ ElMessage.error('获取轮播图列表失败');
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const activeCarouselList = computed(() => {
|
|
|
+ return carouselList.value.filter((item) => item.status === 1);
|
|
|
+});
|
|
|
+
|
|
|
+// 轮播图弹窗逻辑
|
|
|
+const dialogVisible = ref(false);
|
|
|
+const dialogType = ref('add'); // add | edit
|
|
|
+const currentEditIndex = ref(-1);
|
|
|
+const carouselForm = reactive({
|
|
|
+ image: '',
|
|
|
+ link: '',
|
|
|
+ target: '_self',
|
|
|
+ status: 1
|
|
|
+});
|
|
|
+
|
|
|
+const handleAddCarousel = () => {
|
|
|
+ dialogType.value = 'add';
|
|
|
+ carouselForm.image = '';
|
|
|
+ carouselForm.link = '';
|
|
|
+ carouselForm.target = '_self';
|
|
|
+ carouselForm.status = 1;
|
|
|
+ dialogVisible.value = true;
|
|
|
+};
|
|
|
+
|
|
|
+const handleEditCarousel = (row) => {
|
|
|
+ dialogType.value = 'edit';
|
|
|
+ currentEditIndex.value = carouselList.value.findIndex((item) => item.id === row.id);
|
|
|
+ carouselForm.image = row.image;
|
|
|
+ carouselForm.link = row.link;
|
|
|
+ carouselForm.target = row.target;
|
|
|
+ carouselForm.status = row.status;
|
|
|
+ dialogVisible.value = true;
|
|
|
+};
|
|
|
+
|
|
|
+const submitCarouselForm = async () => {
|
|
|
+ if (!carouselForm.image) {
|
|
|
+ return ElMessage.warning('请先上传轮播图片');
|
|
|
+ }
|
|
|
+
|
|
|
+ const data: CarouselForm = {
|
|
|
+ imageUrl: carouselForm.image,
|
|
|
+ link: carouselForm.link,
|
|
|
+ target: carouselForm.target,
|
|
|
+ status: carouselForm.status,
|
|
|
+ sortOrder: carouselList.value.length
|
|
|
+ };
|
|
|
+
|
|
|
+ try {
|
|
|
+ if (dialogType.value === 'add') {
|
|
|
+ const res = await addCarousel(data);
|
|
|
+ if (res.code === 200) {
|
|
|
+ ElMessage.success('新增成功');
|
|
|
+ dialogVisible.value = false;
|
|
|
+ getCarouselList();
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.msg || '新增失败');
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ const id = carouselList.value[currentEditIndex.value]?.id;
|
|
|
+ const res = await updateCarousel({ ...data, id });
|
|
|
+ if (res.code === 200) {
|
|
|
+ ElMessage.success('修改成功');
|
|
|
+ dialogVisible.value = false;
|
|
|
+ getCarouselList();
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.msg || '修改失败');
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('操作失败:', error);
|
|
|
+ ElMessage.error('操作失败');
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const handleRemoveCarousel = async (index: number) => {
|
|
|
+ const item = carouselList.value[index];
|
|
|
+ try {
|
|
|
+ await ElMessageBox.confirm('确定要删除该轮播图吗?', '提示', { type: 'warning' });
|
|
|
+ const res = await delCarousel(item.id);
|
|
|
+ if (res.code === 200) {
|
|
|
+ ElMessage.success('删除成功');
|
|
|
+ getCarouselList();
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.msg || '删除失败');
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ if (error !== 'cancel') {
|
|
|
+ console.error('删除失败:', error);
|
|
|
+ ElMessage.error('删除失败');
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const moveRow = async (index: number, direction: number) => {
|
|
|
+ const newIndex = index + direction;
|
|
|
+ if (newIndex < 0 || newIndex >= carouselList.value.length) return;
|
|
|
+ const item = carouselList.value.splice(index, 1)[0];
|
|
|
+ carouselList.value.splice(newIndex, 0, item);
|
|
|
+
|
|
|
+ // 同步排序到后端
|
|
|
+ try {
|
|
|
+ const start = Math.min(index, newIndex);
|
|
|
+ const end = Math.max(index, newIndex);
|
|
|
+ for (let i = start; i <= end; i++) {
|
|
|
+ const row = carouselList.value[i];
|
|
|
+ await updateCarousel({
|
|
|
+ id: row.id,
|
|
|
+ imageUrl: row.imageUrl,
|
|
|
+ link: row.link,
|
|
|
+ target: row.target,
|
|
|
+ status: row.status,
|
|
|
+ sortOrder: i
|
|
|
+ });
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('排序更新失败:', error);
|
|
|
+ ElMessage.error('排序更新失败');
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 颜色转换工具:Hex 转 RGBA
|
|
|
+const hexToRgba = (hex, opacity) => {
|
|
|
+ if (!hex) return `rgba(255, 255, 255, ${opacity})`;
|
|
|
+ const r = parseInt(hex.slice(1, 3), 16);
|
|
|
+ const g = parseInt(hex.slice(3, 5), 16);
|
|
|
+ const b = parseInt(hex.slice(5, 7), 16);
|
|
|
+ return `rgba(${r}, ${g}, ${b}, ${opacity})`;
|
|
|
+};
|
|
|
+
|
|
|
+const handleSelectSearch = () => {
|
|
|
+ // 模拟搜索过滤,computed 会自动处理
|
|
|
+};
|
|
|
+
|
|
|
+// 左侧广告图片变更处理
|
|
|
+const onLeftAdImageChange = (file: any) => {
|
|
|
+ if (!file || !file.url) {
|
|
|
+ // 图片被删除
|
|
|
+ handleDeleteLeftAd();
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// ==================== 左侧广告单例配置 ====================
|
|
|
+
|
|
|
+// 获取左侧广告配置
|
|
|
+const fetchLeftAdConfig = async () => {
|
|
|
+ try {
|
|
|
+ const res = await listAdLeft({ pageNum: 1, pageSize: 1 });
|
|
|
+ if (res.code === 200 && res.rows && res.rows.length > 0) {
|
|
|
+ const adData = res.rows[0];
|
|
|
+ leftAdId.value = adData.id;
|
|
|
+ leftAdForm.id = adData.id;
|
|
|
+ leftAdForm.leftAdImage = adData.imageUrl || '';
|
|
|
+ leftAdForm.leftAdLink = adData.link || '';
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取左侧广告配置失败:', error);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 保存左侧广告配置
|
|
|
+const saveLeftAdConfig = async () => {
|
|
|
+ try {
|
|
|
+ if (!leftAdForm.leftAdImage) {
|
|
|
+ ElMessage.warning('请先上传广告图片');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const submitData: AdLeftForm = {
|
|
|
+ id: leftAdId.value || leftAdForm.id || undefined,
|
|
|
+ imageUrl: leftAdForm.leftAdImage,
|
|
|
+ link: leftAdForm.leftAdLink,
|
|
|
+ status: 1 // 默认启用
|
|
|
+ };
|
|
|
+
|
|
|
+ let res;
|
|
|
+ if (submitData.id) {
|
|
|
+ // 有ID则更新
|
|
|
+ res = await updateAdLeft(submitData);
|
|
|
+ } else {
|
|
|
+ // 无ID则新增
|
|
|
+ res = await addAdLeft(submitData);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (res.code === 200) {
|
|
|
+ ElMessage.success('左侧广告保存成功');
|
|
|
+ // 如果是新增,保存返回的ID
|
|
|
+ if (!leftAdId.value && res.data) {
|
|
|
+ leftAdId.value = res.data.id;
|
|
|
+ leftAdForm.id = leftAdId.value;
|
|
|
+ }
|
|
|
+ // 重新获取最新配置
|
|
|
+ await fetchLeftAdConfig();
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.msg || '保存失败');
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('保存左侧广告失败:', error);
|
|
|
+ ElMessage.error('保存左侧广告失败');
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 删除左侧广告
|
|
|
+const handleDeleteLeftAd = () => {
|
|
|
+ ElMessageBox.confirm('确定要删除左侧广告吗?', '提示', {
|
|
|
+ confirmButtonText: '确定删除',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ type: 'warning'
|
|
|
+ })
|
|
|
+ .then(async () => {
|
|
|
+ try {
|
|
|
+ if (leftAdId.value) {
|
|
|
+ const res = await delAdLeft(leftAdId.value);
|
|
|
+ if (res.code === 200) {
|
|
|
+ ElMessage.success('删除成功');
|
|
|
+ // 清空表单数据
|
|
|
+ leftAdId.value = '';
|
|
|
+ leftAdForm.id = leftAdId.value;
|
|
|
+ leftAdForm.leftAdImage = '';
|
|
|
+ leftAdForm.leftAdLink = '';
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.msg || '删除失败');
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 如果没有ID,直接清空表单
|
|
|
+ leftAdForm.leftAdImage = '';
|
|
|
+ leftAdForm.leftAdLink = '';
|
|
|
+ ElMessage.success('已清除');
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('删除左侧广告失败:', error);
|
|
|
+ ElMessage.error('删除失败');
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .catch(() => {
|
|
|
+ // 用户取消操作
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+// 分类设置模块逻辑
|
|
|
+const categoryThemeColor = ref('#e60012');
|
|
|
+const syncCategoryOptions = computed(() => {
|
|
|
+ return (categoryOptions.value || []).map((item: any) => ({
|
|
|
+ value: item.id,
|
|
|
+ label: item.label
|
|
|
+ }));
|
|
|
+});
|
|
|
+
|
|
|
+const onSyncCategoryChange = (val: string | number) => {
|
|
|
+ if (val) {
|
|
|
+ const node = findNodeById(categoryOptions.value, val);
|
|
|
+ categoryForm.syncCategory = node ? node.label : '';
|
|
|
+ } else {
|
|
|
+ categoryForm.syncCategory = '';
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const categoryList = ref<CategoryMainVO[]>([]);
|
|
|
+
|
|
|
+// 获取分类列表
|
|
|
+const getCategoryList = async () => {
|
|
|
+ try {
|
|
|
+ const res = await listCategoryMain();
|
|
|
+ if (res.code === 200 && res.rows) {
|
|
|
+ categoryList.value = res.rows.map((item: CategoryMainVO) => {
|
|
|
+ let extra = { tags: [] as any[], notes: [] as any[], groups: [] as any[] };
|
|
|
+ try {
|
|
|
+ if ((item as any).remark) {
|
|
|
+ extra = JSON.parse((item as any).remark);
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ /* remark JSON 解析失败使用默认值 */
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ ...item,
|
|
|
+ tags: extra.tags || [],
|
|
|
+ panelData: {
|
|
|
+ mainTitle: (item as any).panelMainTitle || '',
|
|
|
+ subTitle: (item as any).panelSubTitle || '',
|
|
|
+ notes: extra.notes || [],
|
|
|
+ groups: extra.groups || []
|
|
|
+ }
|
|
|
+ };
|
|
|
+ });
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取分类列表失败:', error);
|
|
|
+ ElMessage.error('获取分类列表失败');
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const addCategoryTag = () => {
|
|
|
+ categoryForm.tags.push({ name: '', link: '' });
|
|
|
+};
|
|
|
+
|
|
|
+const removeCategoryTag = (index) => {
|
|
|
+ categoryForm.tags.splice(index, 1);
|
|
|
+};
|
|
|
+
|
|
|
+const categoryDialogVisible = ref(false);
|
|
|
+const categoryDialogType = ref('add');
|
|
|
+const categoryForm = reactive({
|
|
|
+ id: null,
|
|
|
+ name: '',
|
|
|
+ icon: '',
|
|
|
+ syncCategoryId: null,
|
|
|
+ syncCategory: '',
|
|
|
+ tags: [],
|
|
|
+ status: 1,
|
|
|
+ panelData: {
|
|
|
+ mainTitle: '',
|
|
|
+ subTitle: '',
|
|
|
+ notes: [],
|
|
|
+ groups: []
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+const handleAddCategory = () => {
|
|
|
+ categoryDialogType.value = 'add';
|
|
|
+ Object.assign(categoryForm, {
|
|
|
+ id: null,
|
|
|
+ name: '',
|
|
|
+ icon: '',
|
|
|
+ syncCategoryId: null,
|
|
|
+ syncCategory: '',
|
|
|
+ tags: [],
|
|
|
+ status: 1,
|
|
|
+ panelData: { mainTitle: '', subTitle: '', notes: [], groups: [] }
|
|
|
+ });
|
|
|
+ categoryDialogVisible.value = true;
|
|
|
+};
|
|
|
+
|
|
|
+const handleEditCategory = (row) => {
|
|
|
+ categoryDialogType.value = 'edit';
|
|
|
+ Object.assign(categoryForm, JSON.parse(JSON.stringify(row)));
|
|
|
+ categoryDialogVisible.value = true;
|
|
|
+};
|
|
|
+
|
|
|
+const submitCategoryForm = async () => {
|
|
|
+ if (!categoryForm.name) return ElMessage.warning('请输入菜单名称');
|
|
|
+
|
|
|
+ const data: CategoryMainForm = {
|
|
|
+ name: categoryForm.name,
|
|
|
+ icon: categoryForm.icon,
|
|
|
+ syncCategoryId: categoryForm.syncCategoryId,
|
|
|
+ syncCategory: categoryForm.syncCategory,
|
|
|
+ status: categoryForm.status,
|
|
|
+ panelMainTitle: categoryForm.panelData.mainTitle,
|
|
|
+ panelSubTitle: categoryForm.panelData.subTitle,
|
|
|
+ remark: JSON.stringify({
|
|
|
+ tags: categoryForm.tags,
|
|
|
+ notes: categoryForm.panelData.notes,
|
|
|
+ groups: categoryForm.panelData.groups
|
|
|
+ })
|
|
|
+ };
|
|
|
+
|
|
|
+ try {
|
|
|
+ if (categoryDialogType.value === 'add') {
|
|
|
+ const res = await addCategoryMain(data);
|
|
|
+ if (res.code === 200) {
|
|
|
+ ElMessage.success('新增成功');
|
|
|
+ categoryDialogVisible.value = false;
|
|
|
+ getCategoryList();
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.msg || '新增失败');
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ const id = categoryForm.id;
|
|
|
+ const res = await updateCategoryMain({ ...data, id });
|
|
|
+ if (res.code === 200) {
|
|
|
+ ElMessage.success('修改成功');
|
|
|
+ categoryDialogVisible.value = false;
|
|
|
+ getCategoryList();
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.msg || '修改失败');
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('操作失败:', error);
|
|
|
+ ElMessage.error('操作失败');
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 场景方案模块逻辑
|
|
|
+const scenarioSettings = reactive({
|
|
|
+ id: null as string | number | null,
|
|
|
+ mainTitle: '场景解决方案',
|
|
|
+ subTitle: '一站全买齐',
|
|
|
+ btnText: '进入全场景',
|
|
|
+ jumpLink: '',
|
|
|
+ themeColor: '#66e0a3'
|
|
|
+});
|
|
|
+
|
|
|
+// 获取场景全局设置
|
|
|
+const getScenarioGlobalSettingsList = async () => {
|
|
|
+ try {
|
|
|
+ const res = await listScenarioGlobalSettings();
|
|
|
+ if (res.code === 200 && res.rows && res.rows.length > 0) {
|
|
|
+ const data = res.rows[0];
|
|
|
+ scenarioSettings.id = data.id;
|
|
|
+ scenarioSettings.mainTitle = data.mainTitle || '';
|
|
|
+ scenarioSettings.subTitle = data.subTitle || '';
|
|
|
+ scenarioSettings.btnText = data.btnText || '';
|
|
|
+ scenarioSettings.jumpLink = data.jumpLink || '';
|
|
|
+ scenarioSettings.themeColor = data.themeColor || '#66e0a3';
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取场景全局设置失败:', error);
|
|
|
+ ElMessage.error('获取场景全局设置失败');
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 保存场景全局设置
|
|
|
+const saveScenarioGlobalSettings = async () => {
|
|
|
+ const data: ScenarioGlobalSettingsForm = {
|
|
|
+ mainTitle: scenarioSettings.mainTitle,
|
|
|
+ subTitle: scenarioSettings.subTitle,
|
|
|
+ btnText: scenarioSettings.btnText,
|
|
|
+ jumpLink: scenarioSettings.jumpLink,
|
|
|
+ themeColor: scenarioSettings.themeColor
|
|
|
+ };
|
|
|
+
|
|
|
+ try {
|
|
|
+ let res;
|
|
|
+ if (scenarioSettings.id) {
|
|
|
+ res = await updateScenarioGlobalSettings({ ...data, id: scenarioSettings.id });
|
|
|
+ } else {
|
|
|
+ res = await addScenarioGlobalSettings(data);
|
|
|
+ if (res.code === 200 && res.data) {
|
|
|
+ scenarioSettings.id = res.data.id;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (res.code === 200) {
|
|
|
+ ElMessage.success('场景全局设置保存成功');
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.msg || '保存失败');
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('保存场景全局设置失败:', error);
|
|
|
+ ElMessage.error('保存失败');
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const scenarioList = ref<ScenarioCardsVO[]>([]);
|
|
|
+
|
|
|
+// 获取场景卡片列表
|
|
|
+const getScenarioList = async () => {
|
|
|
+ try {
|
|
|
+ const res = await listScenarioCards();
|
|
|
+ if (res.code === 200 && res.rows) {
|
|
|
+ scenarioList.value = res.rows.map((item: ScenarioCardsVO) => {
|
|
|
+ let extra = { subTitleColor: '#333333', bgColor: '#ffffff', opacity: 1 };
|
|
|
+ try {
|
|
|
+ if ((item as any).remark) {
|
|
|
+ extra = { ...extra, ...JSON.parse((item as any).remark) };
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ /* remark JSON 解析失败使用默认值 */
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ ...item,
|
|
|
+ image: (item as any).imageUrl || '',
|
|
|
+ link: (item as any).jumpLink || '',
|
|
|
+ subTitleColor: extra.subTitleColor,
|
|
|
+ bgColor: extra.bgColor,
|
|
|
+ opacity: extra.opacity
|
|
|
+ };
|
|
|
+ });
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取场景卡片列表失败:', error);
|
|
|
+ ElMessage.error('获取场景卡片列表失败');
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const scenarioDialogVisible = ref(false);
|
|
|
+const scenarioEditIndex = ref(-1);
|
|
|
+const scenarioForm = reactive({
|
|
|
+ title: '',
|
|
|
+ titleColor: '#008b4e',
|
|
|
+ subTitle: '',
|
|
|
+ subTitleColor: '#333333',
|
|
|
+ image: '',
|
|
|
+ link: '',
|
|
|
+ bgColor: '#ffffff',
|
|
|
+ opacity: 1
|
|
|
+});
|
|
|
+
|
|
|
+const handleEditScenario = (row, index) => {
|
|
|
+ scenarioEditIndex.value = index;
|
|
|
+ Object.assign(scenarioForm, JSON.parse(JSON.stringify(row)));
|
|
|
+ scenarioDialogVisible.value = true;
|
|
|
+};
|
|
|
+
|
|
|
+const submitScenarioForm = async () => {
|
|
|
+ const item = scenarioList.value[scenarioEditIndex.value];
|
|
|
+ if (!item) return;
|
|
|
+
|
|
|
+ const data: ScenarioCardsForm = {
|
|
|
+ id: item.id,
|
|
|
+ title: scenarioForm.title,
|
|
|
+ titleColor: scenarioForm.titleColor,
|
|
|
+ subTitle: scenarioForm.subTitle,
|
|
|
+ imageUrl: scenarioForm.image,
|
|
|
+ jumpLink: scenarioForm.link,
|
|
|
+ sortOrder: (item as any).sortOrder ?? scenarioEditIndex.value,
|
|
|
+ remark: JSON.stringify({
|
|
|
+ subTitleColor: scenarioForm.subTitleColor,
|
|
|
+ bgColor: scenarioForm.bgColor,
|
|
|
+ opacity: scenarioForm.opacity
|
|
|
+ })
|
|
|
+ };
|
|
|
+
|
|
|
+ try {
|
|
|
+ const res = await updateScenarioCards(data);
|
|
|
+ if (res.code === 200) {
|
|
|
+ ElMessage.success('方案修改成功');
|
|
|
+ scenarioDialogVisible.value = false;
|
|
|
+ getScenarioList();
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.msg || '修改失败');
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('修改失败:', error);
|
|
|
+ ElMessage.error('修改失败');
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const moveScenario = async (index: number, direction: number) => {
|
|
|
+ const newIndex = index + direction;
|
|
|
+ if (newIndex < 0 || newIndex >= scenarioList.value.length) return;
|
|
|
+ const item = scenarioList.value.splice(index, 1)[0];
|
|
|
+ scenarioList.value.splice(newIndex, 0, item);
|
|
|
+
|
|
|
+ // 同步排序到后端
|
|
|
+ try {
|
|
|
+ const start = Math.min(index, newIndex);
|
|
|
+ const end = Math.max(index, newIndex);
|
|
|
+ for (let i = start; i <= end; i++) {
|
|
|
+ const row = scenarioList.value[i] as any;
|
|
|
+ await updateScenarioCards({
|
|
|
+ id: row.id,
|
|
|
+ title: row.title,
|
|
|
+ titleColor: row.titleColor,
|
|
|
+ subTitle: row.subTitle,
|
|
|
+ imageUrl: row.image || row.imageUrl,
|
|
|
+ jumpLink: row.link || row.jumpLink,
|
|
|
+ sortOrder: i,
|
|
|
+ remark: JSON.stringify({
|
|
|
+ subTitleColor: row.subTitleColor || '#333333',
|
|
|
+ bgColor: row.bgColor || '#ffffff',
|
|
|
+ opacity: row.opacity ?? 1
|
|
|
+ })
|
|
|
+ });
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('排序更新失败:', error);
|
|
|
+ ElMessage.error('排序更新失败');
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const handleRemoveCategory = async (index: number) => {
|
|
|
+ const item = categoryList.value[index];
|
|
|
+ try {
|
|
|
+ await ElMessageBox.confirm('确定要删除该分类吗?', '提示');
|
|
|
+ const res = await delCategoryMain(item.id);
|
|
|
+ if (res.code === 200) {
|
|
|
+ ElMessage.success('删除成功');
|
|
|
+ getCategoryList();
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.msg || '删除失败');
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ if (error !== 'cancel') {
|
|
|
+ console.error('删除失败:', error);
|
|
|
+ ElMessage.error('删除失败');
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const addPanelNote = () => {
|
|
|
+ categoryForm.panelData.notes.push({ name: '', link: '' });
|
|
|
+};
|
|
|
+
|
|
|
+const removePanelNote = (index) => {
|
|
|
+ categoryForm.panelData.notes.splice(index, 1);
|
|
|
+};
|
|
|
+
|
|
|
+const moveCategory = async (index: number, direction: number) => {
|
|
|
+ const newIndex = index + direction;
|
|
|
+ if (newIndex < 0 || newIndex >= categoryList.value.length) return;
|
|
|
+ const item = categoryList.value.splice(index, 1)[0];
|
|
|
+ categoryList.value.splice(newIndex, 0, item);
|
|
|
+
|
|
|
+ // 同步排序到后端
|
|
|
+ try {
|
|
|
+ const start = Math.min(index, newIndex);
|
|
|
+ const end = Math.max(index, newIndex);
|
|
|
+ for (let i = start; i <= end; i++) {
|
|
|
+ const row = categoryList.value[i] as any;
|
|
|
+ await updateCategoryMain({ id: row.id, sortOrder: i } as any);
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('排序更新失败:', error);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ startPlaceholderScroll();
|
|
|
+});
|
|
|
+
|
|
|
+// 头部分类模块逻辑
|
|
|
+const headerThemeColor = ref('#e60012');
|
|
|
+const headerCategoryList = ref<HeaderCategoryVO[]>([]);
|
|
|
+
|
|
|
+// 获取头部分类列表
|
|
|
+const getHeaderCategoryList = async () => {
|
|
|
+ try {
|
|
|
+ const res = await listHeaderCategory();
|
|
|
+ if (res.code === 200 && res.rows) {
|
|
|
+ headerCategoryList.value = res.rows;
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取头部分类列表失败:', error);
|
|
|
+ ElMessage.error('获取头部分类列表失败');
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const headerDialogVisible = ref(false);
|
|
|
+const headerEditIndex = ref(-1);
|
|
|
+const headerForm = reactive({ title: '', icon: '', link: '', openMode: 'current', status: 1 });
|
|
|
+
|
|
|
+const handleAddHeaderCategory = () => {
|
|
|
+ headerEditIndex.value = -1;
|
|
|
+ Object.assign(headerForm, { title: '', icon: '', link: '', openMode: 'current', status: 1 });
|
|
|
+ headerDialogVisible.value = true;
|
|
|
+};
|
|
|
+
|
|
|
+const handleEditHeaderCategory = (row, index) => {
|
|
|
+ headerEditIndex.value = index;
|
|
|
+ Object.assign(headerForm, JSON.parse(JSON.stringify(row)));
|
|
|
+ headerDialogVisible.value = true;
|
|
|
+};
|
|
|
+
|
|
|
+const submitHeaderForm = async () => {
|
|
|
+ const data: HeaderCategoryForm = {
|
|
|
+ title: headerForm.title,
|
|
|
+ icon: headerForm.icon,
|
|
|
+ link: headerForm.link,
|
|
|
+ openMode: headerForm.openMode,
|
|
|
+ status: headerForm.status
|
|
|
+ };
|
|
|
+
|
|
|
+ try {
|
|
|
+ if (headerEditIndex.value > -1) {
|
|
|
+ const id = headerCategoryList.value[headerEditIndex.value]?.id;
|
|
|
+ const res = await updateHeaderCategory({ ...data, id });
|
|
|
+ if (res.code === 200) {
|
|
|
+ ElMessage.success('修改成功');
|
|
|
+ headerDialogVisible.value = false;
|
|
|
+ getHeaderCategoryList();
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.msg || '修改失败');
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ const res = await addHeaderCategory(data);
|
|
|
+ if (res.code === 200) {
|
|
|
+ ElMessage.success('新增成功');
|
|
|
+ headerDialogVisible.value = false;
|
|
|
+ getHeaderCategoryList();
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.msg || '新增失败');
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('操作失败:', error);
|
|
|
+ ElMessage.error('操作失败');
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const handleDeleteHeaderCategory = async (index: number) => {
|
|
|
+ const item = headerCategoryList.value[index];
|
|
|
+ try {
|
|
|
+ await ElMessageBox.confirm('确定删除该分类吗?', '提示');
|
|
|
+ const res = await delHeaderCategory(item.id);
|
|
|
+ if (res.code === 200) {
|
|
|
+ ElMessage.success('删除成功');
|
|
|
+ getHeaderCategoryList();
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.msg || '删除失败');
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ if (error !== 'cancel') {
|
|
|
+ console.error('删除失败:', error);
|
|
|
+ ElMessage.error('删除失败');
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const moveHeader = async (index: number, direction: number) => {
|
|
|
+ const newIndex = index + direction;
|
|
|
+ if (newIndex < 0 || newIndex >= headerCategoryList.value.length) return;
|
|
|
+ const item = headerCategoryList.value.splice(index, 1)[0];
|
|
|
+ headerCategoryList.value.splice(newIndex, 0, item);
|
|
|
+
|
|
|
+ // 同步排序到后端
|
|
|
+ try {
|
|
|
+ const start = Math.min(index, newIndex);
|
|
|
+ const end = Math.max(index, newIndex);
|
|
|
+ for (let i = start; i <= end; i++) {
|
|
|
+ const row = headerCategoryList.value[i] as any;
|
|
|
+ await updateHeaderCategory({ id: row.id, sortOrder: i } as any);
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('排序更新失败:', error);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 导航滚动逻辑
|
|
|
+const headerNavScrollRef = ref(null);
|
|
|
+const showLeftArrow = ref(false);
|
|
|
+const showRightArrow = ref(false);
|
|
|
+
|
|
|
+const updateNavArrows = () => {
|
|
|
+ if (!headerNavScrollRef.value) return;
|
|
|
+ const { scrollLeft, scrollWidth, clientWidth } = headerNavScrollRef.value;
|
|
|
+ showLeftArrow.value = scrollLeft > 5;
|
|
|
+ showRightArrow.value = scrollLeft + clientWidth < scrollWidth - 5;
|
|
|
+};
|
|
|
+
|
|
|
+const scrollHeaderNav = (direction) => {
|
|
|
+ const container = headerNavScrollRef.value;
|
|
|
+ if (!container) return;
|
|
|
+ const scrollAmount = 350;
|
|
|
+ if (direction === 'left') {
|
|
|
+ container.scrollLeft -= scrollAmount;
|
|
|
+ } else {
|
|
|
+ container.scrollLeft += scrollAmount;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 广告模块逻辑
|
|
|
+const adModules = ref([
|
|
|
+ {
|
|
|
+ id: 1,
|
|
|
+ title: '企业购x百亿补贴',
|
|
|
+ titleColor: '#333333',
|
|
|
+ subTitle: '先采后付 享底价',
|
|
|
+ subTitleColor: '#999999',
|
|
|
+ type: 'subsidy',
|
|
|
+ items: [
|
|
|
+ { id: 101, productName: '企业商用台式机', imageUrl: '/static/images/purchase/pc_desktop.jpg', price: '69.9' },
|
|
|
+ { id: 102, productName: '商务笔记本', imageUrl: '/static/images/purchase/laptop_hp.jpg', price: '84.8' },
|
|
|
+ { id: 103, productName: '智能打印机', imageUrl: '/static/images/purchase/printer_office.jpg', price: '139.9' },
|
|
|
+ { id: 104, productName: '高效办公组网', imageUrl: '/static/images/purchase/network_router.jpg', price: '1749' }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 2,
|
|
|
+ title: '企采榜单',
|
|
|
+ titleColor: '#333333',
|
|
|
+ subTitle: '同行都在买',
|
|
|
+ subTitleColor: '#f58220',
|
|
|
+ type: 'ranking',
|
|
|
+ items: [
|
|
|
+ {
|
|
|
+ id: 201,
|
|
|
+ productName: '办公电脑榜',
|
|
|
+ imageUrl: '/static/images/purchase/laptop_lenovo.jpg',
|
|
|
+ price: '0',
|
|
|
+ tagText: '办公电脑榜',
|
|
|
+ tagLink: '',
|
|
|
+ salesCount: '1543'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 202,
|
|
|
+ productName: '文具榜',
|
|
|
+ imageUrl: '/static/images/purchase/stationery_ranking.jpg',
|
|
|
+ price: '0',
|
|
|
+ tagText: '文具榜',
|
|
|
+ tagLink: '',
|
|
|
+ salesCount: '1200'
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 3,
|
|
|
+ title: '品牌好店',
|
|
|
+ titleColor: '#333333',
|
|
|
+ subTitle: '返2000元E卡',
|
|
|
+ subTitleColor: '#f58220',
|
|
|
+ type: 'brand',
|
|
|
+ items: [
|
|
|
+ { id: 301, productName: '鲁花', imageUrl: '/static/images/purchase/oil_luhua.jpg', tagText: '品质保障', tagLink: '鲁花京东自营旗舰店' },
|
|
|
+ { id: 302, productName: '金龙鱼', imageUrl: '/static/images/purchase/oil_jinlongyu.jpg', tagText: '热销品牌', tagLink: '金龙鱼京东自营旗舰店' }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 4,
|
|
|
+ title: '企业精选',
|
|
|
+ titleColor: '#333333',
|
|
|
+ subTitle: '品牌专供 库存充足',
|
|
|
+ subTitleColor: '#999999',
|
|
|
+ type: 'selection',
|
|
|
+ items: [
|
|
|
+ { id: 401, productName: '高性能工作站', imageUrl: '/static/images/purchase/pc_desktop.jpg', price: '10740' },
|
|
|
+ { id: 402, productName: '办公咖啡机', imageUrl: '/static/images/purchase/coffee_machine.jpg', price: '877' }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 5,
|
|
|
+ title: '企业购x京东新品',
|
|
|
+ titleColor: '#333333',
|
|
|
+ subTitle: '美的新鲜',
|
|
|
+ subTitleColor: '#f58220',
|
|
|
+ type: 'new',
|
|
|
+ items: [
|
|
|
+ { id: 501, productName: '商用冷柜', imageUrl: '/static/images/purchase/freezer.jpg', price: '7188' },
|
|
|
+ { id: 502, productName: '得力笔记本', imageUrl: '/static/images/purchase/notebook_deli.jpg', price: '34.9' }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+]) as any;
|
|
|
+
|
|
|
+const adDialogVisible = ref(false);
|
|
|
+const currentAdIdx = ref(-1);
|
|
|
+const currentItemIdx = ref(-1);
|
|
|
+const adForm = reactive({ title: '', titleColor: '#333333', subTitle: '', subTitleColor: '#f58220', items: [] });
|
|
|
+
|
|
|
+// 实时预览辅助函数
|
|
|
+const getAdTitleStyle = (index: number, type: string) => {
|
|
|
+ const isEditing = adDialogVisible.value && currentAdIdx.value === index;
|
|
|
+ const key = type === 'main' ? 'titleColor' : 'subTitleColor';
|
|
|
+ const defaultColor = type === 'main' ? '#333' : '#999';
|
|
|
+ const color = isEditing ? adForm[key] : adModules.value[index]?.[key] || defaultColor;
|
|
|
+ return { color };
|
|
|
+};
|
|
|
+
|
|
|
+const getAdTitleText = (index: number, type: string) => {
|
|
|
+ const isEditing = adDialogVisible.value && currentAdIdx.value === index;
|
|
|
+ const key = type === 'main' ? 'title' : 'subTitle';
|
|
|
+ return isEditing ? adForm[key] : adModules.value[index]?.[key] || '';
|
|
|
+};
|
|
|
+
|
|
|
+const emptyItem = () => ({ id: null, productId: '', productName: '', imageUrl: '', price: '', tagText: '', tagLink: '', salesCount: '' });
|
|
|
+const slotCounts = [4, 2, 2, 2, 2]; // 每个模块固定条目数
|
|
|
+
|
|
|
+const handleEditAd = (index: number) => {
|
|
|
+ currentAdIdx.value = index;
|
|
|
+ const ad = adModules.value[index] || {};
|
|
|
+ const data = JSON.parse(JSON.stringify(ad));
|
|
|
+ // 确保 items 有固定数量的条目(不够用空模板补齐)
|
|
|
+ const count = slotCounts[index] || 2;
|
|
|
+ while (data.items.length < count) {
|
|
|
+ data.items.push(emptyItem());
|
|
|
+ }
|
|
|
+ Object.assign(adForm, data);
|
|
|
+ adDialogVisible.value = true;
|
|
|
+};
|
|
|
+
|
|
|
+const submitAdForm = async () => {
|
|
|
+ const moduleData = adModules.value[currentAdIdx.value] as any;
|
|
|
+ if (!moduleData) return;
|
|
|
+
|
|
|
+ const data: AdModuleConfigForm = {
|
|
|
+ moduleCode: moduleData.type || '',
|
|
|
+ moduleName: moduleData.title || '',
|
|
|
+ mainTitle: adForm.title,
|
|
|
+ mainTitleColor: adForm.titleColor,
|
|
|
+ subTitle: adForm.subTitle,
|
|
|
+ subTitleColor: adForm.subTitleColor,
|
|
|
+ jumpLink: '',
|
|
|
+ status: 1,
|
|
|
+ sortOrder: currentAdIdx.value,
|
|
|
+ adModuleItemList: adForm.items
|
|
|
+ .filter((item: any) => item.productId || item.imageUrl)
|
|
|
+ .map((item: any) => ({
|
|
|
+ productId: item.productId || item.id,
|
|
|
+ productName: item.productName || '',
|
|
|
+ imageUrl: item.imageUrl || '',
|
|
|
+ price: item.price || 0,
|
|
|
+ tagText: item.tagText || '',
|
|
|
+ tagLink: item.tagLink || '',
|
|
|
+ salesCount: item.salesCount || 0
|
|
|
+ }))
|
|
|
+ };
|
|
|
+
|
|
|
+ try {
|
|
|
+ let res;
|
|
|
+ // 从 API 加载的真实数据才有有效 id,mock id(1-5)用 POST 新增
|
|
|
+ const isFromApi = moduleData.moduleCode && moduleData.id && String(moduleData.id).length > 10;
|
|
|
+ if (isFromApi) {
|
|
|
+ res = await updateAdModuleConfig({ ...data, id: moduleData.id });
|
|
|
+ } else {
|
|
|
+ res = await addAdModuleConfig(data);
|
|
|
+ }
|
|
|
+ if (res.code === 200) {
|
|
|
+ // 新增时回写后端返回的 id
|
|
|
+ const savedData = JSON.parse(JSON.stringify(adForm));
|
|
|
+ if (!isFromApi && res.data?.id) {
|
|
|
+ savedData.id = res.data.id;
|
|
|
+ }
|
|
|
+ adModules.value[currentAdIdx.value] = savedData;
|
|
|
+ adDialogVisible.value = false;
|
|
|
+ ElMessage.success('配置保存成功');
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.msg || '保存失败');
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('保存广告模块配置失败:', error);
|
|
|
+ ElMessage.error('保存失败');
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 获取广告模块配置列表
|
|
|
+const getAdModuleList = async () => {
|
|
|
+ const defaults = adModules.value; // 保留当前 mock 数据作为缺省填充
|
|
|
+ try {
|
|
|
+ const res = await listAdModuleConfig();
|
|
|
+ if (res.code === 200 && res.rows && res.rows.length > 0) {
|
|
|
+ const apiList = res.rows.map((item: AdModuleConfigVO) => ({
|
|
|
+ ...item,
|
|
|
+ title: item.mainTitle || '',
|
|
|
+ titleColor: item.mainTitleColor || '#333333',
|
|
|
+ subTitle: item.subTitle || '',
|
|
|
+ subTitleColor: item.subTitleColor || '#999999',
|
|
|
+ type: item.moduleCode || '',
|
|
|
+ items: (item.adModuleItemList || []).map((sub: any) => ({
|
|
|
+ id: sub.id,
|
|
|
+ productId: sub.productId || '',
|
|
|
+ productName: sub.productName || '',
|
|
|
+ imageUrl: sub.imageUrl || '',
|
|
|
+ price: sub.price || 0,
|
|
|
+ tagText: sub.tagText || '',
|
|
|
+ tagLink: sub.tagLink || '',
|
|
|
+ salesCount: sub.salesCount || 0
|
|
|
+ }))
|
|
|
+ }));
|
|
|
+ // 确保始终有 5 个模块(模板固定访问 adModules[0]~[4])
|
|
|
+ adModules.value = Array.from({ length: 5 }, (_, i) => apiList[i] || defaults[i] || { title: '', items: [] });
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取广告模块配置失败:', error);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 推荐设置模块逻辑
|
|
|
+const recommendThemeColor = ref('#e60012');
|
|
|
+const recommendProductThemeColor = ref('#e60012');
|
|
|
+const recommendActiveId = ref(1);
|
|
|
+
|
|
|
+const recommendThemeConfigId = ref<string | number | null>(null);
|
|
|
+
|
|
|
+const getRecommendThemeConfigList = async () => {
|
|
|
+ try {
|
|
|
+ const res = await listRecommendThemeConfig();
|
|
|
+ if (res.code === 200 && res.rows && res.rows.length > 0) {
|
|
|
+ const data = res.rows[0];
|
|
|
+ recommendThemeConfigId.value = data.id;
|
|
|
+ recommendThemeColor.value = data.themeColor || '#e60012';
|
|
|
+ recommendProductThemeColor.value = data.productThemeColor || '#e60012';
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取推荐主题配置失败:', error);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const saveRecommendThemeConfig = async () => {
|
|
|
+ const data: RecommendThemeConfigForm = {
|
|
|
+ themeColor: recommendThemeColor.value,
|
|
|
+ productThemeColor: recommendProductThemeColor.value
|
|
|
+ };
|
|
|
+ try {
|
|
|
+ let res;
|
|
|
+ if (recommendThemeConfigId.value) {
|
|
|
+ res = await updateRecommendThemeConfig({ ...data, id: recommendThemeConfigId.value });
|
|
|
+ } else {
|
|
|
+ res = await addRecommendThemeConfig(data);
|
|
|
+ if (res.code === 200 && res.data) {
|
|
|
+ recommendThemeConfigId.value = res.data.id;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (res.code === 200) {
|
|
|
+ ElMessage.success('推荐主题配置保存成功');
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.msg || '保存失败');
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('保存推荐主题配置失败:', error);
|
|
|
+ ElMessage.error('保存失败');
|
|
|
+ }
|
|
|
+};
|
|
|
+const recommendList = ref<RecommendCategoryConfigVO[]>([]);
|
|
|
+
|
|
|
+const getRecommendCategoryList = async () => {
|
|
|
+ try {
|
|
|
+ const res = await listRecommendCategoryConfig();
|
|
|
+ if (res.code === 200 && res.rows) {
|
|
|
+ recommendList.value = res.rows.map((item: RecommendCategoryConfigVO) => ({
|
|
|
+ ...item,
|
|
|
+ type: (item as any).dataType || 'select',
|
|
|
+ icon: (item as any).iconUrl || '',
|
|
|
+ categoryValue: [],
|
|
|
+ selectedProducts: (() => {
|
|
|
+ try {
|
|
|
+ return (item as any).selectedProductIds ? JSON.parse((item as any).selectedProductIds) : [];
|
|
|
+ } catch {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ })()
|
|
|
+ }));
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取推荐分类列表失败:', error);
|
|
|
+ ElMessage.error('获取推荐分类列表失败');
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const activeRecommendList = computed(() => {
|
|
|
+ return recommendList.value.filter((item) => item.status === 1);
|
|
|
+});
|
|
|
+
|
|
|
+const recommendDialogVisible = ref(false);
|
|
|
+const recommendEditIndex = ref(-1);
|
|
|
+const recommendForm = reactive({
|
|
|
+ name: '',
|
|
|
+ subTitle: '',
|
|
|
+ icon: '',
|
|
|
+ type: 'select',
|
|
|
+ categoryValue: [],
|
|
|
+ categoryLabel: '',
|
|
|
+ selectedProducts: [],
|
|
|
+ status: 1
|
|
|
+});
|
|
|
+
|
|
|
+const categoryOptions = ref<categoryTreeVO[]>([]);
|
|
|
+
|
|
|
+const getCategoryTreeData = async () => {
|
|
|
+ try {
|
|
|
+ const res = await categoryTree({ dataSource: 'A10' } as any);
|
|
|
+ categoryOptions.value = (res.data || []) as any[];
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取分类树失败:', error);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const findNodeById = (nodes: any[], id: string | number): any => {
|
|
|
+ for (const node of nodes) {
|
|
|
+ if (node.id === id) return node;
|
|
|
+ if (node.children) {
|
|
|
+ const found = findNodeById(node.children, id);
|
|
|
+ if (found) return found;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+};
|
|
|
+
|
|
|
+// 根据 syncCategoryId 从分类树获取二级+三级分组展示数据
|
|
|
+const getSubCategoryGroups = (syncCategoryId: string | number | null) => {
|
|
|
+ if (!syncCategoryId) return [];
|
|
|
+ const node = findNodeById(categoryOptions.value, syncCategoryId);
|
|
|
+ if (!node || !node.children) return [];
|
|
|
+ return node.children.map((child: any) => ({
|
|
|
+ title: child.label,
|
|
|
+ items: (child.children || []).map((sub: any) => sub.label)
|
|
|
+ }));
|
|
|
+};
|
|
|
+
|
|
|
+const handleAddRecommend = () => {
|
|
|
+ recommendEditIndex.value = -1;
|
|
|
+ Object.assign(recommendForm, {
|
|
|
+ name: '',
|
|
|
+ subTitle: '',
|
|
|
+ icon: '',
|
|
|
+ type: 'select',
|
|
|
+ categoryValue: [],
|
|
|
+ categoryLabel: '',
|
|
|
+ selectedProducts: [],
|
|
|
+ status: 1
|
|
|
+ });
|
|
|
+ recommendDialogVisible.value = true;
|
|
|
+};
|
|
|
+
|
|
|
+const handleEditRecommend = (row, index) => {
|
|
|
+ recommendEditIndex.value = index;
|
|
|
+ Object.assign(recommendForm, JSON.parse(JSON.stringify(row)));
|
|
|
+ recommendDialogVisible.value = true;
|
|
|
+};
|
|
|
+
|
|
|
+const getLabelsByValues = (values: (string | number)[], tree: any[]): string => {
|
|
|
+ const labels: string[] = [];
|
|
|
+ for (const val of values) {
|
|
|
+ const node = findNodeById(tree, val);
|
|
|
+ if (node) labels.push(node.label);
|
|
|
+ }
|
|
|
+ return labels.join(' > ');
|
|
|
+};
|
|
|
+
|
|
|
+const handleRecommendCategoryChange = (val) => {
|
|
|
+ if (val && val.length > 0) {
|
|
|
+ recommendForm.categoryLabel = getLabelsByValues(val, categoryOptions.value);
|
|
|
+ } else {
|
|
|
+ recommendForm.categoryLabel = '';
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const submitRecommendForm = async () => {
|
|
|
+ // 序列化已选商品
|
|
|
+ const selectedProducts = recommendEditIndex.value > -1 ? recommendList.value[recommendEditIndex.value]?.selectedProducts || [] : [];
|
|
|
+
|
|
|
+ const data: RecommendCategoryConfigForm = {
|
|
|
+ name: recommendForm.name,
|
|
|
+ subTitle: recommendForm.subTitle,
|
|
|
+ iconUrl: recommendForm.icon,
|
|
|
+ dataType: recommendForm.type,
|
|
|
+ categoryLabel: recommendForm.categoryLabel || '',
|
|
|
+ categoryPath: (recommendForm.categoryValue || []).join(','),
|
|
|
+ selectedProductIds: JSON.stringify(selectedProducts),
|
|
|
+ status: recommendForm.status,
|
|
|
+ sortOrder: recommendList.value.length
|
|
|
+ };
|
|
|
+
|
|
|
+ try {
|
|
|
+ if (recommendEditIndex.value > -1) {
|
|
|
+ const id = recommendList.value[recommendEditIndex.value]?.id;
|
|
|
+ const isFromApi = id && String(id).length > 10;
|
|
|
+ const res = isFromApi ? await updateRecommendCategoryConfig({ ...data, id }) : await addRecommendCategoryConfig(data);
|
|
|
+ if (res.code === 200) {
|
|
|
+ ElMessage.success('修改成功');
|
|
|
+ recommendDialogVisible.value = false;
|
|
|
+ getRecommendCategoryList();
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.msg || '保存失败');
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ const res = await addRecommendCategoryConfig(data);
|
|
|
+ if (res.code === 200) {
|
|
|
+ ElMessage.success('新增成功');
|
|
|
+ recommendDialogVisible.value = false;
|
|
|
+ getRecommendCategoryList();
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.msg || '保存失败');
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('保存失败:', error);
|
|
|
+ ElMessage.error('保存失败');
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const handleDeleteRecommend = async (index: number) => {
|
|
|
+ const item = recommendList.value[index];
|
|
|
+ try {
|
|
|
+ await ElMessageBox.confirm('确定要删除该推荐分类吗?', '提示');
|
|
|
+ const res = await delRecommendCategoryConfig(item.id);
|
|
|
+ if (res.code === 200) {
|
|
|
+ ElMessage.success('删除成功');
|
|
|
+ getRecommendCategoryList();
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.msg || '删除失败');
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ if (error !== 'cancel') {
|
|
|
+ console.error('删除失败:', error);
|
|
|
+ ElMessage.error('删除失败');
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const moveRecommend = async (index: number, direction: number) => {
|
|
|
+ const newIndex = index + direction;
|
|
|
+ if (newIndex < 0 || newIndex >= recommendList.value.length) return;
|
|
|
+ const item = recommendList.value.splice(index, 1)[0];
|
|
|
+ recommendList.value.splice(newIndex, 0, item);
|
|
|
+
|
|
|
+ try {
|
|
|
+ const start = Math.min(index, newIndex);
|
|
|
+ const end = Math.max(index, newIndex);
|
|
|
+ for (let i = start; i <= end; i++) {
|
|
|
+ const row = recommendList.value[i] as any;
|
|
|
+ await updateRecommendCategoryConfig({
|
|
|
+ id: row.id,
|
|
|
+ name: row.name,
|
|
|
+ subTitle: row.subTitle,
|
|
|
+ iconUrl: row.icon || row.iconUrl,
|
|
|
+ dataType: row.type || row.dataType,
|
|
|
+ categoryLabel: row.categoryLabel || '',
|
|
|
+ status: row.status,
|
|
|
+ sortOrder: i
|
|
|
+ });
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('排序更新失败:', error);
|
|
|
+ ElMessage.error('排序更新失败');
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 推荐商品选择增强逻辑
|
|
|
+const selectedProductDialogVisible = ref(false);
|
|
|
+const productSelectionDrawerVisible = ref(false);
|
|
|
+const currentRecommendIndex = ref(-1);
|
|
|
+const drawerSelection = ref([]);
|
|
|
+const selectedProductsTableRef = ref(null);
|
|
|
+const drawerTableRef = ref(null);
|
|
|
+
|
|
|
+const selectedCurrentPage = ref(1);
|
|
|
+const selectedPageSize = ref(10);
|
|
|
+
|
|
|
+const pagedSelectedProducts = computed(() => {
|
|
|
+ const list = recommendList.value[currentRecommendIndex.value]?.selectedProducts || [];
|
|
|
+ const start = (selectedCurrentPage.value - 1) * selectedPageSize.value;
|
|
|
+ const end = start + selectedPageSize.value;
|
|
|
+ return list.slice(start, end);
|
|
|
+});
|
|
|
+
|
|
|
+const handleSelectedProductsSelectionChange = (val) => {
|
|
|
+ currentItemIdx.value = val; // 临时借用 currentItemIdx 存储选中的行
|
|
|
+};
|
|
|
+
|
|
|
+const openRecommendProductSelect = (index) => {
|
|
|
+ currentRecommendIndex.value = index;
|
|
|
+ selectedCurrentPage.value = 1;
|
|
|
+ selectedProductDialogVisible.value = true;
|
|
|
+};
|
|
|
+
|
|
|
+const submitSelectedProducts = async () => {
|
|
|
+ const item = recommendList.value[currentRecommendIndex.value];
|
|
|
+ if (!item) return;
|
|
|
+
|
|
|
+ const selectedProducts = item.selectedProducts || [];
|
|
|
+ const data: RecommendCategoryConfigForm = {
|
|
|
+ id: item.id,
|
|
|
+ name: item.name,
|
|
|
+ subTitle: item.subTitle,
|
|
|
+ iconUrl: (item as any).icon || (item as any).iconUrl || '',
|
|
|
+ dataType: (item as any).type || (item as any).dataType || 'select',
|
|
|
+ categoryLabel: (item as any).categoryLabel || '',
|
|
|
+ selectedProductIds: JSON.stringify(selectedProducts),
|
|
|
+ status: item.status
|
|
|
+ };
|
|
|
+
|
|
|
+ try {
|
|
|
+ const res = await updateRecommendCategoryConfig(data);
|
|
|
+ if (res.code === 200) {
|
|
|
+ selectedProductDialogVisible.value = false;
|
|
|
+ ElMessage.success('已选商品配置已保存');
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.msg || '保存失败');
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('保存已选商品失败:', error);
|
|
|
+ ElMessage.error('保存失败');
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const openProductDrawer = async () => {
|
|
|
+ const currentList = recommendList.value[currentRecommendIndex.value]?.selectedProducts || [];
|
|
|
+ drawerSelection.value = JSON.parse(JSON.stringify(currentList));
|
|
|
+ productQueryParams.itemName = '';
|
|
|
+ productQueryParams.pageNum = 1;
|
|
|
+ productQueryParams.pageSize = selectPageSize.value;
|
|
|
+ selectCurrentPage.value = 1;
|
|
|
+ productSelectionDrawerVisible.value = true;
|
|
|
+ await getProductList();
|
|
|
+
|
|
|
+ // 回显勾选逻辑
|
|
|
+ nextTick(() => {
|
|
|
+ if (drawerTableRef.value) {
|
|
|
+ drawerTableRef.value.clearSelection();
|
|
|
+ productList.value.forEach((item) => {
|
|
|
+ if (currentList.some((exist) => exist.id === item.id)) {
|
|
|
+ drawerTableRef.value.toggleRowSelection(item, true);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+const handleDrawerSelectionChange = (val) => {
|
|
|
+ drawerSelection.value = val;
|
|
|
+};
|
|
|
+
|
|
|
+const confirmDrawerSelection = () => {
|
|
|
+ // 直接全量覆盖,支持在抽屉里取消勾选来删除
|
|
|
+ recommendList.value[currentRecommendIndex.value].selectedProducts = JSON.parse(JSON.stringify(drawerSelection.value));
|
|
|
+
|
|
|
+ ElMessage.success('商品列表同步成功');
|
|
|
+ productSelectionDrawerVisible.value = false;
|
|
|
+};
|
|
|
+
|
|
|
+const removeSelectedProduct = (index) => {
|
|
|
+ recommendList.value[currentRecommendIndex.value].selectedProducts.splice(index, 1);
|
|
|
+ ElMessage.success('移除成功');
|
|
|
+};
|
|
|
+
|
|
|
+const batchRemoveSelectedProducts = () => {
|
|
|
+ const selectedRows = selectedProductsTableRef.value?.getSelectionRows() || [];
|
|
|
+ if (selectedRows.length === 0) return ElMessage.warning('请先勾选要移除的商品');
|
|
|
+
|
|
|
+ ElMessageBox.confirm(`确定移除选中的 ${selectedRows.length} 个商品吗?`, '提示').then(() => {
|
|
|
+ const currentList = recommendList.value[currentRecommendIndex.value].selectedProducts;
|
|
|
+ const selectedIds = selectedRows.map((r) => r.id);
|
|
|
+ recommendList.value[currentRecommendIndex.value].selectedProducts = currentList.filter((item) => !selectedIds.includes(item.id));
|
|
|
+ ElMessage.success('批量移除成功');
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+// 推荐预览滚动逻辑
|
|
|
+const recommendScrollRef = ref(null);
|
|
|
+const recShowLeft = ref(false);
|
|
|
+const recShowRight = ref(false);
|
|
|
+
|
|
|
+const updateRecArrows = () => {
|
|
|
+ if (!recommendScrollRef.value) return;
|
|
|
+ const { scrollLeft, scrollWidth, clientWidth } = recommendScrollRef.value;
|
|
|
+ recShowLeft.value = scrollLeft > 5;
|
|
|
+ recShowRight.value = scrollLeft + clientWidth < scrollWidth - 5;
|
|
|
+};
|
|
|
+
|
|
|
+const scrollRecommend = (direction) => {
|
|
|
+ const container = recommendScrollRef.value;
|
|
|
+ if (!container) return;
|
|
|
+ const scrollAmount = 400;
|
|
|
+ if (direction === 'left') {
|
|
|
+ container.scrollLeft -= scrollAmount;
|
|
|
+ } else {
|
|
|
+ container.scrollLeft += scrollAmount;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 商品选择逻辑
|
|
|
+const selectDialogVisible = ref(false);
|
|
|
+const selectedTempId = ref(null);
|
|
|
+const selectCurrentPage = ref(1);
|
|
|
+const selectPageSize = ref(8);
|
|
|
+
|
|
|
+// 商品列表(API数据)
|
|
|
+const productList = ref<any[]>([]);
|
|
|
+const productTotal = ref(0);
|
|
|
+
|
|
|
+const productQueryParams = reactive({
|
|
|
+ pageNum: 1,
|
|
|
+ pageSize: 8,
|
|
|
+ itemName: '',
|
|
|
+ productStatus: 1
|
|
|
+});
|
|
|
+
|
|
|
+const brandQueryParams = reactive({
|
|
|
+ pageNum: 1,
|
|
|
+ pageSize: 8,
|
|
|
+ brandName: ''
|
|
|
+});
|
|
|
+
|
|
|
+/** 获取商品/品牌列表(根据广告模块类型自动切换) */
|
|
|
+const getProductList = async () => {
|
|
|
+ try {
|
|
|
+ if (selectDialogVisible.value && currentAdIdx.value === 2) {
|
|
|
+ // 品牌好店:查询品牌
|
|
|
+ brandQueryParams.brandName = productQueryParams.itemName;
|
|
|
+ brandQueryParams.pageNum = productQueryParams.pageNum;
|
|
|
+ brandQueryParams.pageSize = productQueryParams.pageSize;
|
|
|
+ const res = await listBrand(brandQueryParams);
|
|
|
+ productList.value = (res.rows || []).map((item: any) => ({
|
|
|
+ id: item.id,
|
|
|
+ name: item.brandName || '',
|
|
|
+ image: item.brandLogo || '',
|
|
|
+ price: ''
|
|
|
+ }));
|
|
|
+ productTotal.value = res.total || 0;
|
|
|
+ } else {
|
|
|
+ // 其他广告模块:查询商品
|
|
|
+ const res = await listBase(productQueryParams);
|
|
|
+ productList.value = (res.rows || []).map((item: any) => ({
|
|
|
+ id: item.id,
|
|
|
+ name: item.itemName || '',
|
|
|
+ image: item.productImage || item.productImageUrl || '',
|
|
|
+ price: item.memberPrice ?? item.minSellingPrice ?? item.marketPrice ?? '',
|
|
|
+ productNo: item.productNo || '',
|
|
|
+ isSelf: item.isSelf,
|
|
|
+ minOrderQuantity: item.minOrderQuantity || 1
|
|
|
+ }));
|
|
|
+ productTotal.value = res.total || 0;
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取列表失败:', error);
|
|
|
+ ElMessage.error('获取列表失败');
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const filteredSelectList = computed(() => productList.value);
|
|
|
+
|
|
|
+const pagedSelectList = computed(() => productList.value);
|
|
|
+
|
|
|
+const onProductPageChange = (page: number) => {
|
|
|
+ productQueryParams.pageNum = page;
|
|
|
+ selectCurrentPage.value = page;
|
|
|
+ getProductList();
|
|
|
+};
|
|
|
+
|
|
|
+const onProductPageSizeChange = (size: number) => {
|
|
|
+ productQueryParams.pageSize = size;
|
|
|
+ productQueryParams.pageNum = 1;
|
|
|
+ selectCurrentPage.value = 1;
|
|
|
+ selectPageSize.value = size;
|
|
|
+ getProductList();
|
|
|
+};
|
|
|
+
|
|
|
+const openProductSelect = (index: number) => {
|
|
|
+ currentItemIdx.value = index;
|
|
|
+ selectedTempId.value = adForm.items[index].id;
|
|
|
+ productQueryParams.itemName = '';
|
|
|
+ productQueryParams.pageNum = 1;
|
|
|
+ productQueryParams.pageSize = selectPageSize.value;
|
|
|
+ selectCurrentPage.value = 1;
|
|
|
+ selectDialogVisible.value = true;
|
|
|
+ getProductList();
|
|
|
+};
|
|
|
+
|
|
|
+const confirmSelect = () => {
|
|
|
+ const item = productList.value.find((i) => i.id === selectedTempId.value);
|
|
|
+ if (item) {
|
|
|
+ const target = adForm.items[currentItemIdx.value];
|
|
|
+ target.id = item.id;
|
|
|
+ target.productId = item.id;
|
|
|
+ target.productName = item.name;
|
|
|
+ target.imageUrl = item.image;
|
|
|
+ target.price = item.price;
|
|
|
+ selectDialogVisible.value = false;
|
|
|
+ ElMessage.success('选择成功');
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 快捷入口模块逻辑
|
|
|
+const qePageIndex = ref(0);
|
|
|
+const quickEntrySettings = reactive({
|
|
|
+ id: null as string | number | null,
|
|
|
+ moduleName: '企业工作台',
|
|
|
+ jumpLink: ''
|
|
|
+});
|
|
|
+
|
|
|
+// 获取快捷入口模块配置
|
|
|
+const getQuickEntryModuleList = async () => {
|
|
|
+ try {
|
|
|
+ const res = await listQuickEntryModule();
|
|
|
+ if (res.code === 200 && res.rows && res.rows.length > 0) {
|
|
|
+ const data = res.rows[0];
|
|
|
+ quickEntrySettings.id = data.id;
|
|
|
+ quickEntrySettings.moduleName = data.moduleName || '';
|
|
|
+ quickEntrySettings.jumpLink = data.jumpLink || '';
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取快捷入口模块配置失败:', error);
|
|
|
+ ElMessage.error('获取快捷入口模块配置失败');
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 保存快捷入口模块配置
|
|
|
+const saveQuickEntryModule = async () => {
|
|
|
+ const data: QuickEntryModuleForm = {
|
|
|
+ moduleName: quickEntrySettings.moduleName,
|
|
|
+ jumpLink: quickEntrySettings.jumpLink
|
|
|
+ };
|
|
|
+
|
|
|
+ try {
|
|
|
+ let res;
|
|
|
+ if (quickEntrySettings.id) {
|
|
|
+ res = await updateQuickEntryModule({ ...data, id: quickEntrySettings.id });
|
|
|
+ } else {
|
|
|
+ res = await addQuickEntryModule(data);
|
|
|
+ if (res.code === 200 && res.data) {
|
|
|
+ quickEntrySettings.id = res.data.id;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (res.code === 200) {
|
|
|
+ ElMessage.success('快捷入口模块配置保存成功');
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.msg || '保存失败');
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('保存快捷入口模块配置失败:', error);
|
|
|
+ ElMessage.error('保存失败');
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const quickEntryList = ref<QuickEntryItemsVO[]>([]);
|
|
|
+
|
|
|
+// 获取快捷入口项列表
|
|
|
+const getQuickEntryItemsList = async () => {
|
|
|
+ try {
|
|
|
+ const res = await listQuickEntryItems();
|
|
|
+ if (res.code === 200 && res.rows) {
|
|
|
+ quickEntryList.value = res.rows.map((item: QuickEntryItemsVO) => ({
|
|
|
+ ...item,
|
|
|
+ icon: (item as any).iconUrl || '',
|
|
|
+ tag: (item as any).tagText || '',
|
|
|
+ link: (item as any).jumpLink || ''
|
|
|
+ }));
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取快捷入口项列表失败:', error);
|
|
|
+ ElMessage.error('获取快捷入口项列表失败');
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const qePageCount = computed(() => Math.ceil(quickEntryList.value.filter((i) => i.status === 1).length / 8));
|
|
|
+
|
|
|
+const getPageItems = (pageIdx) => {
|
|
|
+ const activeItems = quickEntryList.value.filter((i) => i.status === 1);
|
|
|
+ return activeItems.slice(pageIdx * 8, (pageIdx + 1) * 8);
|
|
|
+};
|
|
|
+
|
|
|
+const quickEntryDialogVisible = ref(false);
|
|
|
+const quickEntryDialogType = ref('add');
|
|
|
+const qeEditIndex = ref(-1);
|
|
|
+const quickEntryForm = reactive({ name: '', icon: '', tag: '', link: '', status: 1 });
|
|
|
+
|
|
|
+const handleAddQuickEntry = () => {
|
|
|
+ quickEntryDialogType.value = 'add';
|
|
|
+ Object.assign(quickEntryForm, { name: '', icon: '', tag: '', link: '', status: 1 });
|
|
|
+ quickEntryDialogVisible.value = true;
|
|
|
+};
|
|
|
+
|
|
|
+const handleEditQuickEntry = (row, index) => {
|
|
|
+ quickEntryDialogType.value = 'edit';
|
|
|
+ qeEditIndex.value = index;
|
|
|
+ Object.assign(quickEntryForm, JSON.parse(JSON.stringify(row)));
|
|
|
+ quickEntryDialogVisible.value = true;
|
|
|
+};
|
|
|
+
|
|
|
+const submitQuickEntryForm = async () => {
|
|
|
+ if (!quickEntryForm.name) return ElMessage.warning('请输入入口名称');
|
|
|
+
|
|
|
+ const data: QuickEntryItemsForm = {
|
|
|
+ name: quickEntryForm.name,
|
|
|
+ iconUrl: quickEntryForm.icon,
|
|
|
+ tagText: quickEntryForm.tag,
|
|
|
+ jumpLink: quickEntryForm.link,
|
|
|
+ status: quickEntryForm.status,
|
|
|
+ sortOrder: quickEntryList.value.length
|
|
|
+ };
|
|
|
+
|
|
|
+ try {
|
|
|
+ if (quickEntryDialogType.value === 'add') {
|
|
|
+ const res = await addQuickEntryItems(data);
|
|
|
+ if (res.code === 200) {
|
|
|
+ ElMessage.success('新增成功');
|
|
|
+ quickEntryDialogVisible.value = false;
|
|
|
+ getQuickEntryItemsList();
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.msg || '新增失败');
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ const id = quickEntryList.value[qeEditIndex.value]?.id;
|
|
|
+ const res = await updateQuickEntryItems({ ...data, id });
|
|
|
+ if (res.code === 200) {
|
|
|
+ ElMessage.success('修改成功');
|
|
|
+ quickEntryDialogVisible.value = false;
|
|
|
+ getQuickEntryItemsList();
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.msg || '修改失败');
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('操作失败:', error);
|
|
|
+ ElMessage.error('操作失败');
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const handleDeleteQuickEntry = async (index: number) => {
|
|
|
+ const item = quickEntryList.value[index];
|
|
|
+ try {
|
|
|
+ await ElMessageBox.confirm('确定要删除该入口吗?', '提示', { type: 'warning' });
|
|
|
+ const res = await delQuickEntryItems(item.id);
|
|
|
+ if (res.code === 200) {
|
|
|
+ ElMessage.success('删除成功');
|
|
|
+ getQuickEntryItemsList();
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.msg || '删除失败');
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ if (error !== 'cancel') {
|
|
|
+ console.error('删除失败:', error);
|
|
|
+ ElMessage.error('删除失败');
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const moveQE = async (index: number, direction: number) => {
|
|
|
+ const newIndex = index + direction;
|
|
|
+ if (newIndex < 0 || newIndex >= quickEntryList.value.length) return;
|
|
|
+ const item = quickEntryList.value.splice(index, 1)[0];
|
|
|
+ quickEntryList.value.splice(newIndex, 0, item);
|
|
|
+
|
|
|
+ // 同步排序到后端
|
|
|
+ try {
|
|
|
+ const start = Math.min(index, newIndex);
|
|
|
+ const end = Math.max(index, newIndex);
|
|
|
+ for (let i = start; i <= end; i++) {
|
|
|
+ const row = quickEntryList.value[i] as any;
|
|
|
+ await updateQuickEntryItems({
|
|
|
+ id: row.id,
|
|
|
+ name: row.name,
|
|
|
+ iconUrl: row.icon || row.iconUrl,
|
|
|
+ tagText: row.tag || row.tagText,
|
|
|
+ jumpLink: row.link || row.jumpLink,
|
|
|
+ status: row.status,
|
|
|
+ sortOrder: i
|
|
|
+ });
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('排序更新失败:', error);
|
|
|
+ ElMessage.error('排序更新失败');
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+watch(
|
|
|
+ headerCategoryList,
|
|
|
+ () => {
|
|
|
+ nextTick(() => updateNavArrows());
|
|
|
+ },
|
|
|
+ { deep: true }
|
|
|
+);
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ startPlaceholderScroll();
|
|
|
+ // 获取当前配置
|
|
|
+ getCurrentSearch();
|
|
|
+ getCurrentAdLeftBtn();
|
|
|
+ getCarouselList();
|
|
|
+ getHeaderCategoryList();
|
|
|
+ getCategoryList();
|
|
|
+ getScenarioGlobalSettingsList();
|
|
|
+ getScenarioList();
|
|
|
+ getQuickEntryModuleList();
|
|
|
+ getQuickEntryItemsList();
|
|
|
+ getAdModuleList();
|
|
|
+ getRecommendThemeConfigList();
|
|
|
+ getRecommendCategoryList();
|
|
|
+ getCategoryTreeData();
|
|
|
+ // 获取左侧广告配置
|
|
|
+ nextTick(() => {
|
|
|
+ updateNavArrows();
|
|
|
+ updateRecArrows();
|
|
|
+ });
|
|
|
+ // 延迟再次检测,确保图片加载后布局稳定
|
|
|
+ setTimeout(() => {
|
|
|
+ updateRecArrows();
|
|
|
+ }, 500);
|
|
|
+ window.addEventListener('resize', updateNavArrows);
|
|
|
+ window.addEventListener('resize', updateRecArrows);
|
|
|
+});
|
|
|
+
|
|
|
+onUnmounted(() => {
|
|
|
+ if (timer) clearInterval(timer);
|
|
|
+ window.removeEventListener('resize', updateNavArrows);
|
|
|
+ window.removeEventListener('resize', updateRecArrows);
|
|
|
+});
|
|
|
+
|
|
|
+watch(
|
|
|
+ activeRecommendList,
|
|
|
+ () => {
|
|
|
+ nextTick(() => {
|
|
|
+ updateRecArrows();
|
|
|
+ });
|
|
|
+ },
|
|
|
+ { deep: true }
|
|
|
+);
|
|
|
+
|
|
|
+// 监听子页签切换,确保切换回推荐设置时更新箭头状态
|
|
|
+watch(activeSubTab, (newVal) => {
|
|
|
+ if (newVal === 'recommend') {
|
|
|
+ nextTick(() => {
|
|
|
+ setTimeout(() => {
|
|
|
+ updateRecArrows();
|
|
|
+ }, 300);
|
|
|
+ });
|
|
|
+ }
|
|
|
+});
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+/* 基础辅助类 */
|
|
|
+.flex-column {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+}
|
|
|
+.flex-center {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+.gap-5 {
|
|
|
+ gap: 5px;
|
|
|
+}
|
|
|
+.gap-10 {
|
|
|
+ gap: 10px;
|
|
|
+}
|
|
|
+.m-b-30 {
|
|
|
+ margin-bottom: 30px;
|
|
|
+}
|
|
|
+
|
|
|
+.scenario-editor-container {
|
|
|
+ padding: 32px 40px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ background-color: #fff; /* 纯白背景 */
|
|
|
+ min-height: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.preview-section-standard {
|
|
|
+ background: #fcfdfe;
|
|
|
+ border: 1px solid #eef2f6;
|
|
|
+ border-radius: 8px;
|
|
|
+ padding: 32px;
|
|
|
+ margin-bottom: 32px;
|
|
|
+}
|
|
|
+
|
|
|
+.section-title-standard {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #333;
|
|
|
+ margin-bottom: 24px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+
|
|
|
+.section-title-standard::before {
|
|
|
+ content: '';
|
|
|
+ width: 4px;
|
|
|
+ height: 16px;
|
|
|
+ background-color: #e60012;
|
|
|
+ margin-right: 12px;
|
|
|
+ border-radius: 2px;
|
|
|
+}
|
|
|
+
|
|
|
+.config-section-standard {
|
|
|
+ padding: 32px 0;
|
|
|
+ border-bottom: 1px solid #f0f0f0;
|
|
|
+}
|
|
|
+
|
|
|
+.settings-form-standard {
|
|
|
+ padding-left: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.preview-tip {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #999;
|
|
|
+ margin-top: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.rank-action-btns {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ gap: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.rank-btn-mini {
|
|
|
+ cursor: pointer;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #909399;
|
|
|
+ transition: color 0.2s;
|
|
|
+}
|
|
|
+
|
|
|
+.rank-btn-mini:hover {
|
|
|
+ color: #e60012;
|
|
|
+}
|
|
|
+
|
|
|
+.standard-table :deep(.el-table__header) th {
|
|
|
+ background-color: #f8fafc;
|
|
|
+ color: #606266;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+
|
|
|
+.parameter-settings {
|
|
|
+ flex: 1;
|
|
|
+ min-height: 0; /* 关键:防止 flex 子项被内容撑开 */
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ background-color: #fff;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.sub-tabs {
|
|
|
+ display: flex;
|
|
|
+ padding: 0 20px;
|
|
|
+ border-bottom: 1px solid #f0f0f0;
|
|
|
+ flex-shrink: 0;
|
|
|
+ height: 48px; /* 固定高度,防止切换时抖动 */
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+
|
|
|
+.sub-tab-item {
|
|
|
+ padding: 12px 20px;
|
|
|
+ cursor: pointer;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #666;
|
|
|
+ font-weight: 500;
|
|
|
+ position: relative;
|
|
|
+}
|
|
|
+
|
|
|
+.sub-tab-item.active {
|
|
|
+ color: var(--primary-color);
|
|
|
+}
|
|
|
+
|
|
|
+.sub-tab-item.active::after {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ bottom: 0;
|
|
|
+ left: 20px;
|
|
|
+ right: 20px;
|
|
|
+ height: 2px;
|
|
|
+ background-color: var(--primary-color);
|
|
|
+}
|
|
|
+
|
|
|
+.content-body {
|
|
|
+ flex: 1;
|
|
|
+ overflow-y: auto;
|
|
|
+}
|
|
|
+
|
|
|
+/* 搜索编辑区布局 */
|
|
|
+.search-editor {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ padding: 20px 40px;
|
|
|
+ gap: 30px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 预览区域 */
|
|
|
+.preview-section {
|
|
|
+ background-color: #fff;
|
|
|
+ border: 1px dashed #dcdfe6;
|
|
|
+ border-radius: 8px;
|
|
|
+ padding: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.preview-title {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #909399;
|
|
|
+ margin-bottom: 15px;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+
|
|
|
+.live-preview-box {
|
|
|
+ background-color: #fff;
|
|
|
+ padding: 40px 20px;
|
|
|
+ border-radius: 4px;
|
|
|
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
|
|
|
+}
|
|
|
+
|
|
|
+/* 仿真 Mockup */
|
|
|
+.search-bar-mockup {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ max-width: 1400px; /* 进一步加宽容器 */
|
|
|
+ margin: 0 auto;
|
|
|
+ gap: 30px;
|
|
|
+}
|
|
|
+
|
|
|
+.mockup-left {
|
|
|
+ flex-shrink: 0;
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+
|
|
|
+.main-title {
|
|
|
+ font-size: 32px;
|
|
|
+ font-weight: 800;
|
|
|
+ line-height: 1.2;
|
|
|
+}
|
|
|
+
|
|
|
+.sub-title {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #999;
|
|
|
+ margin-top: 4px;
|
|
|
+ letter-spacing: 1px;
|
|
|
+}
|
|
|
+
|
|
|
+.mockup-center {
|
|
|
+ flex: 2; /* 增加中心区域的弹性占比,使搜索框更宽 */
|
|
|
+}
|
|
|
+
|
|
|
+.search-input-wrapper {
|
|
|
+ height: 44px;
|
|
|
+ border: 2px solid transparent; /* 由动态绑定控制颜色 */
|
|
|
+ border-radius: 8px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ overflow: hidden;
|
|
|
+ position: relative;
|
|
|
+}
|
|
|
+
|
|
|
+.placeholder-scroll {
|
|
|
+ flex: 1;
|
|
|
+ padding: 0 15px;
|
|
|
+ height: 100%;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.scroll-container {
|
|
|
+ height: 100%;
|
|
|
+ position: relative;
|
|
|
+}
|
|
|
+
|
|
|
+.scroll-item {
|
|
|
+ height: 100%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ color: #999;
|
|
|
+ font-size: 15px;
|
|
|
+}
|
|
|
+
|
|
|
+.search-btn {
|
|
|
+ width: 90px;
|
|
|
+ height: 34px;
|
|
|
+ color: #fff;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ font-weight: bold;
|
|
|
+ font-size: 15px;
|
|
|
+ cursor: pointer;
|
|
|
+ border-radius: 6px;
|
|
|
+ letter-spacing: 2px;
|
|
|
+ margin-right: 4px;
|
|
|
+ transition: opacity 0.2s;
|
|
|
+}
|
|
|
+
|
|
|
+.search-btn:hover {
|
|
|
+ opacity: 0.9;
|
|
|
+}
|
|
|
+
|
|
|
+.hot-words {
|
|
|
+ margin-top: 8px;
|
|
|
+ display: flex;
|
|
|
+ gap: 15px;
|
|
|
+ padding-left: 5px;
|
|
|
+}
|
|
|
+
|
|
|
+.hot-word {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #999;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: color 0.2s;
|
|
|
+}
|
|
|
+
|
|
|
+.hot-word:hover {
|
|
|
+ color: var(--theme-color);
|
|
|
+}
|
|
|
+
|
|
|
+.mockup-right {
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.cart-btn {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ padding: 8px 16px;
|
|
|
+ border: 1px solid transparent; /* 由动态绑定控制颜色 */
|
|
|
+ border-radius: 8px;
|
|
|
+ font-size: 14px;
|
|
|
+ cursor: pointer;
|
|
|
+ background-color: #fff;
|
|
|
+ transition: all 0.2s;
|
|
|
+}
|
|
|
+
|
|
|
+.cart-btn:hover {
|
|
|
+ filter: brightness(0.95);
|
|
|
+}
|
|
|
+
|
|
|
+/* 广告图编辑区 */
|
|
|
+.carousel-editor {
|
|
|
+ padding: 20px 40px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 40px;
|
|
|
+}
|
|
|
+
|
|
|
+.editor-section {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.section-header {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.section-title {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #333;
|
|
|
+}
|
|
|
+
|
|
|
+.section-desc {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #999;
|
|
|
+}
|
|
|
+
|
|
|
+/* 左侧广告设置 */
|
|
|
+.left-ad-container {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.left-ad-preview-wrapper {
|
|
|
+ width: 80px;
|
|
|
+ height: 460px;
|
|
|
+ border-radius: 8px;
|
|
|
+ overflow: hidden;
|
|
|
+ position: relative;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: width 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
|
|
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
|
+ border: 1px solid #eee;
|
|
|
+}
|
|
|
+
|
|
|
+.left-ad-preview-wrapper.expanded {
|
|
|
+ width: 790px;
|
|
|
+}
|
|
|
+
|
|
|
+.left-ad-img {
|
|
|
+ width: 790px; /* 固定宽度,外层容器裁剪 */
|
|
|
+ height: 460px;
|
|
|
+ object-fit: cover;
|
|
|
+ display: block;
|
|
|
+}
|
|
|
+
|
|
|
+.left-ad-empty {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ background-color: #f5f7fa;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ color: #909399;
|
|
|
+ border: 1px dashed #dcdfe6;
|
|
|
+}
|
|
|
+
|
|
|
+.ad-actions {
|
|
|
+ position: absolute;
|
|
|
+ top: 20px;
|
|
|
+ right: 20px;
|
|
|
+ display: flex;
|
|
|
+ gap: 10px;
|
|
|
+ background: rgba(255, 255, 255, 0.9);
|
|
|
+ padding: 8px;
|
|
|
+ border-radius: 30px;
|
|
|
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
|
+}
|
|
|
+
|
|
|
+.left-ad-settings {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 15px;
|
|
|
+ padding: 10px 0; /* 移除背景和边框 */
|
|
|
+}
|
|
|
+
|
|
|
+.settings-input-ad {
|
|
|
+ max-width: 500px;
|
|
|
+}
|
|
|
+
|
|
|
+.left-ad-tip {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6px;
|
|
|
+ font-size: 13px;
|
|
|
+ color: #a8abb2;
|
|
|
+}
|
|
|
+
|
|
|
+/* 轮播图预览 */
|
|
|
+.carousel-preview-box {
|
|
|
+ padding: 10px 0;
|
|
|
+}
|
|
|
+
|
|
|
+.preview-mockup {
|
|
|
+ width: 552px;
|
|
|
+ height: 190px;
|
|
|
+ border-radius: 8px;
|
|
|
+ overflow: hidden;
|
|
|
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
|
|
+ background-color: #f5f7fa;
|
|
|
+}
|
|
|
+
|
|
|
+/* 强力锁定轮播图指示器至左下角 */
|
|
|
+:deep(.el-carousel__indicators--horizontal) {
|
|
|
+ left: 20px !important;
|
|
|
+ right: auto !important;
|
|
|
+ bottom: 15px !important;
|
|
|
+ transform: none !important;
|
|
|
+ width: fit-content !important;
|
|
|
+ margin: 0 !important;
|
|
|
+ padding: 0 !important;
|
|
|
+ display: flex !important;
|
|
|
+ justify-content: flex-start !important;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.el-carousel__indicator--horizontal) {
|
|
|
+ display: inline-block !important;
|
|
|
+ padding: 0 3px !important;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.el-carousel__indicators--inside) {
|
|
|
+ left: 20px !important;
|
|
|
+ transform: none !important;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.el-carousel__indicator) {
|
|
|
+ padding: 0 3px;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.el-carousel__button) {
|
|
|
+ width: 6px;
|
|
|
+ height: 6px;
|
|
|
+ border-radius: 50%;
|
|
|
+ background-color: rgba(255, 255, 255, 0.4) !important;
|
|
|
+ opacity: 1 !important;
|
|
|
+ transition: all 0.3s;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.el-carousel__indicator.is-active .el-carousel__button) {
|
|
|
+ width: 20px; /* 激活状态变为长胶囊 */
|
|
|
+ border-radius: 10px;
|
|
|
+ background-color: #ffffff !important;
|
|
|
+}
|
|
|
+
|
|
|
+.carousel-slide {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.carousel-slide img {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ object-fit: cover;
|
|
|
+}
|
|
|
+
|
|
|
+.carousel-empty {
|
|
|
+ height: 100%;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ color: #ccc;
|
|
|
+ background-color: #f9f9f9;
|
|
|
+}
|
|
|
+
|
|
|
+/* 轮播列表 */
|
|
|
+.carousel-list-box {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 15px;
|
|
|
+ margin-top: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.list-toolbar {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ padding: 0 5px;
|
|
|
+}
|
|
|
+
|
|
|
+.btn-add-carousel {
|
|
|
+ padding: 0 25px;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+
|
|
|
+/* 自定义表头样式 */
|
|
|
+:deep(.table-header-custom) {
|
|
|
+ background-color: #f8f9fb !important;
|
|
|
+ color: #333 !important;
|
|
|
+ font-weight: bold !important;
|
|
|
+ height: 50px;
|
|
|
+}
|
|
|
+
|
|
|
+.drag-tip {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #a8abb2;
|
|
|
+}
|
|
|
+
|
|
|
+.table-img {
|
|
|
+ width: 140px;
|
|
|
+ height: 48px;
|
|
|
+ border-radius: 4px;
|
|
|
+ border: 1px solid #f0f0f0;
|
|
|
+ display: block;
|
|
|
+}
|
|
|
+
|
|
|
+.rank-box {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ gap: 2px;
|
|
|
+}
|
|
|
+
|
|
|
+.rank-icon {
|
|
|
+ cursor: pointer;
|
|
|
+ color: #909399;
|
|
|
+ font-size: 16px;
|
|
|
+ transition: color 0.2s;
|
|
|
+}
|
|
|
+
|
|
|
+.rank-icon:hover {
|
|
|
+ color: #409eff;
|
|
|
+}
|
|
|
+
|
|
|
+/* 弹窗上传样式 */
|
|
|
+.upload-placeholder {
|
|
|
+ width: 240px;
|
|
|
+ height: 82px;
|
|
|
+ border: 1px dashed #dcdfe6;
|
|
|
+ border-radius: 6px;
|
|
|
+ cursor: pointer;
|
|
|
+ overflow: hidden;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ background-color: #f5f7fa;
|
|
|
+ transition: border-color 0.2s;
|
|
|
+}
|
|
|
+
|
|
|
+.upload-placeholder:hover {
|
|
|
+ border-color: #409eff;
|
|
|
+}
|
|
|
+
|
|
|
+.form-preview-img {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ object-fit: cover;
|
|
|
+}
|
|
|
+
|
|
|
+.upload-icon {
|
|
|
+ font-size: 28px;
|
|
|
+ color: #8c939d;
|
|
|
+}
|
|
|
+
|
|
|
+.upload-tip {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #999;
|
|
|
+ margin-top: 5px;
|
|
|
+ line-height: 1.4;
|
|
|
+}
|
|
|
+
|
|
|
+.dialog-form-inner {
|
|
|
+ padding: 30px 40px; /* 大幅增加内边距,使其更高、更大气 */
|
|
|
+}
|
|
|
+
|
|
|
+.dialog-form-inner :deep(.el-form-item) {
|
|
|
+ margin-bottom: 25px; /* 增加表单项间距 */
|
|
|
+}
|
|
|
+
|
|
|
+.list-scroll-enter-active,
|
|
|
+.list-scroll-leave-active {
|
|
|
+ transition: all 0.5s ease;
|
|
|
+ position: absolute;
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.list-scroll-enter-from {
|
|
|
+ transform: translateY(100%);
|
|
|
+ opacity: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.list-scroll-leave-to {
|
|
|
+ transform: translateY(-100%);
|
|
|
+ opacity: 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* 设置表单 */
|
|
|
+.settings-section {
|
|
|
+ padding: 0 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.settings-input {
|
|
|
+ max-width: 600px;
|
|
|
+}
|
|
|
+
|
|
|
+.color-picker-wrap {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+
|
|
|
+.tip-text {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #999;
|
|
|
+ margin-top: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.color-val {
|
|
|
+ margin-left: 10px;
|
|
|
+ color: #666;
|
|
|
+ font-family: monospace;
|
|
|
+}
|
|
|
+
|
|
|
+/* 热词配置样式 */
|
|
|
+.hot-words-config {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 10px;
|
|
|
+ width: 800px; /* 加宽配置区域 */
|
|
|
+}
|
|
|
+
|
|
|
+.hot-word-row {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.hot-word-input-name {
|
|
|
+ width: 200px; /* 稍微加宽名称框 */
|
|
|
+}
|
|
|
+
|
|
|
+.hot-word-input-link {
|
|
|
+ flex: 1; /* 此时地址框会占据剩余更多空间 */
|
|
|
+}
|
|
|
+
|
|
|
+.add-hotword-btn {
|
|
|
+ width: fit-content;
|
|
|
+ padding: 0;
|
|
|
+ margin-top: 5px;
|
|
|
+}
|
|
|
+
|
|
|
+.footer-actions {
|
|
|
+ padding: 20px 40px;
|
|
|
+ border-top: 1px solid #f0f0f0;
|
|
|
+ display: flex;
|
|
|
+ gap: 15px;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.btn-confirm {
|
|
|
+ padding: 0 30px;
|
|
|
+}
|
|
|
+
|
|
|
+.btn-reset {
|
|
|
+ padding: 0 30px;
|
|
|
+}
|
|
|
+/* 分类设置编辑区 */
|
|
|
+.category-editor {
|
|
|
+ padding: 20px 40px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 40px;
|
|
|
+}
|
|
|
+
|
|
|
+.category-preview-container {
|
|
|
+ padding: 20px 0;
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-start;
|
|
|
+}
|
|
|
+
|
|
|
+/* 图1: 仿真菜单 */
|
|
|
+.category-menu-mockup {
|
|
|
+ width: 280px;
|
|
|
+ height: 398px;
|
|
|
+ background: #f8f9fa;
|
|
|
+ border-radius: 4px;
|
|
|
+ padding: 10px 0;
|
|
|
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
|
|
|
+ position: relative;
|
|
|
+ z-index: 10;
|
|
|
+ overflow: visible; /* 必须可见,否则遮罩层无法伸出 */
|
|
|
+}
|
|
|
+
|
|
|
+/* 仿真菜单项 */
|
|
|
+.menu-item {
|
|
|
+ height: 44px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ padding: 0 15px;
|
|
|
+ cursor: pointer;
|
|
|
+ position: relative;
|
|
|
+ transition: all 0.1s;
|
|
|
+ background: transparent;
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+
|
|
|
+.menu-item:hover {
|
|
|
+ background-color: #fff !important;
|
|
|
+ color: v-bind(categoryThemeColor);
|
|
|
+ border: 1px solid v-bind(categoryThemeColor);
|
|
|
+ border-right: none;
|
|
|
+ border-radius: 12px 0 0 12px;
|
|
|
+ z-index: 1000;
|
|
|
+ margin-left: 10px;
|
|
|
+ padding-left: 15px;
|
|
|
+ width: calc(100% - 10px);
|
|
|
+}
|
|
|
+
|
|
|
+.menu-icon {
|
|
|
+ width: 16px;
|
|
|
+ height: 16px;
|
|
|
+ margin-right: 15px; /* 增加间距 */
|
|
|
+ color: #999;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.menu-icon img {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ object-fit: contain;
|
|
|
+}
|
|
|
+
|
|
|
+.menu-name {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #333;
|
|
|
+ flex: 1;
|
|
|
+ white-space: nowrap;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+}
|
|
|
+
|
|
|
+/* 遮盖层:仅抹除中间垂直线,保留上下边框连贯 */
|
|
|
+.menu-item:hover::after {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ top: 0; /* 保持在边框内侧 */
|
|
|
+ bottom: 0; /* 保持在边框内侧 */
|
|
|
+ right: -1px; /* 贴合右边缘 */
|
|
|
+ width: 2px; /* 覆盖面板左边框 */
|
|
|
+ background: #fff;
|
|
|
+ z-index: 1001;
|
|
|
+}
|
|
|
+
|
|
|
+.menu-item:hover .menu-icon {
|
|
|
+ color: v-bind(categoryThemeColor);
|
|
|
+}
|
|
|
+
|
|
|
+.menu-name {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #333;
|
|
|
+}
|
|
|
+
|
|
|
+.menu-item:hover .menu-name {
|
|
|
+ color: v-bind(categoryThemeColor);
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+
|
|
|
+/* 图2: 右滑面板 */
|
|
|
+.category-panel-mockup {
|
|
|
+ position: absolute;
|
|
|
+ left: 100%; /* 紧贴菜单项右侧 */
|
|
|
+ margin-left: 0;
|
|
|
+ top: -1px; /* 顶部边框对齐 */
|
|
|
+ width: 980px;
|
|
|
+ min-height: 480px;
|
|
|
+ background: #fff;
|
|
|
+ box-shadow: 15px 15px 40px rgba(0, 0, 0, 0.1);
|
|
|
+ border-radius: 0 12px 12px 12px;
|
|
|
+ z-index: 500;
|
|
|
+ border: 1px solid v-bind(categoryThemeColor);
|
|
|
+ display: none;
|
|
|
+ flex-direction: column;
|
|
|
+ box-sizing: border-box;
|
|
|
+ cursor: default;
|
|
|
+}
|
|
|
+
|
|
|
+/* 悬停时显示面板 */
|
|
|
+.menu-item:hover .category-panel-mockup {
|
|
|
+ display: flex;
|
|
|
+}
|
|
|
+
|
|
|
+.panel-content {
|
|
|
+ flex: 1;
|
|
|
+ padding: 25px 30px;
|
|
|
+ position: relative; /* 为品牌位定位提供基准 */
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.panel-tabs {
|
|
|
+ display: flex;
|
|
|
+ gap: 15px;
|
|
|
+ margin-bottom: 25px;
|
|
|
+ padding-right: 200px; /* 为右上角品牌位留出空间 */
|
|
|
+}
|
|
|
+
|
|
|
+.panel-tab-item {
|
|
|
+ padding: 6px 14px;
|
|
|
+ background-color: #f5f5f5;
|
|
|
+ color: #666;
|
|
|
+ font-size: 12px;
|
|
|
+ border-radius: 4px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+}
|
|
|
+
|
|
|
+.panel-tab-item:hover {
|
|
|
+ background-color: #f5f5f5; /* 保持浅灰或根据主题调整 */
|
|
|
+ color: v-bind(categoryThemeColor);
|
|
|
+ filter: brightness(0.95);
|
|
|
+}
|
|
|
+
|
|
|
+.panel-body {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column; /* 改为垂直布局,内容横向撑开 */
|
|
|
+ flex: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.panel-main {
|
|
|
+ flex: 1;
|
|
|
+ overflow-y: auto;
|
|
|
+ max-height: 400px;
|
|
|
+}
|
|
|
+
|
|
|
+.category-group {
|
|
|
+ margin-bottom: 15px;
|
|
|
+ display: flex;
|
|
|
+}
|
|
|
+
|
|
|
+.group-title {
|
|
|
+ width: 80px;
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #333;
|
|
|
+ flex-shrink: 0;
|
|
|
+ line-height: 1.6;
|
|
|
+}
|
|
|
+
|
|
|
+.group-items {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 6px 15px;
|
|
|
+}
|
|
|
+
|
|
|
+.group-item {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #666;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: color 0.2s;
|
|
|
+}
|
|
|
+
|
|
|
+.group-item:hover {
|
|
|
+ color: v-bind(categoryThemeColor);
|
|
|
+}
|
|
|
+
|
|
|
+/* 品牌位移至右上角 (图3) */
|
|
|
+.panel-side {
|
|
|
+ display: none; /* 移除侧边栏布局 */
|
|
|
+}
|
|
|
+
|
|
|
+.brand-box {
|
|
|
+ position: absolute;
|
|
|
+ top: 25px;
|
|
|
+ right: 30px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: flex-end; /* 右对齐更美观 */
|
|
|
+ z-index: 105;
|
|
|
+}
|
|
|
+
|
|
|
+.brand-main-title {
|
|
|
+ font-size: 20px;
|
|
|
+ font-weight: 900;
|
|
|
+ color: v-bind(categoryThemeColor);
|
|
|
+ margin-bottom: 4px;
|
|
|
+ display: flex;
|
|
|
+ align-items: baseline;
|
|
|
+}
|
|
|
+
|
|
|
+.brand-strong {
|
|
|
+ color: #333;
|
|
|
+ margin-left: 2px;
|
|
|
+ font-size: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.brand-notes {
|
|
|
+ display: flex;
|
|
|
+ gap: 5px;
|
|
|
+ color: v-bind(categoryThemeColor);
|
|
|
+ font-size: 12px;
|
|
|
+ justify-content: flex-end;
|
|
|
+}
|
|
|
+
|
|
|
+.note-item {
|
|
|
+ cursor: pointer;
|
|
|
+}
|
|
|
+
|
|
|
+.note-sep {
|
|
|
+ color: #eee;
|
|
|
+ margin: 0 2px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 工具栏主题色设置 - 专业版 */
|
|
|
+.theme-color-setting-pro {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ margin-right: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.theme-color-setting-pro .label {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #333;
|
|
|
+}
|
|
|
+
|
|
|
+.theme-color-setting-pro .value {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #666;
|
|
|
+ margin-left: 5px;
|
|
|
+ font-family: monospace;
|
|
|
+}
|
|
|
+
|
|
|
+/* 覆盖 el-color-picker 样式 */
|
|
|
+:deep(.theme-color-setting-pro .el-color-picker__trigger) {
|
|
|
+ width: 28px;
|
|
|
+ height: 28px;
|
|
|
+ padding: 2px;
|
|
|
+ border-radius: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 列表样式 */
|
|
|
+.table-icon-preview {
|
|
|
+ width: 24px;
|
|
|
+ height: 24px;
|
|
|
+}
|
|
|
+
|
|
|
+.tag-wrap {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 5px;
|
|
|
+}
|
|
|
+
|
|
|
+.m-r-5 {
|
|
|
+ margin-right: 5px;
|
|
|
+}
|
|
|
+.m-t-20 {
|
|
|
+ margin-top: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 弹窗样式 */
|
|
|
+.panel-config-section {
|
|
|
+ background: #fafafa;
|
|
|
+ padding: 15px;
|
|
|
+ border-radius: 4px;
|
|
|
+ border: 1px dashed #eee;
|
|
|
+}
|
|
|
+
|
|
|
+/* 移除冗余的 .config-subtitle 定义,统一使用下方全局定义的样式 */
|
|
|
+
|
|
|
+.notes-config-list {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.note-config-row {
|
|
|
+ display: flex;
|
|
|
+ gap: 10px;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+
|
|
|
+.field-tip {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #999;
|
|
|
+ margin-top: 5px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 正方形图标上传 */
|
|
|
+.upload-placeholder-square {
|
|
|
+ width: 80px;
|
|
|
+ height: 80px;
|
|
|
+ border: 1px dashed #d9d9d9;
|
|
|
+ border-radius: 4px;
|
|
|
+ cursor: pointer;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ background-color: #fafafa;
|
|
|
+ transition: border-color 0.2s;
|
|
|
+}
|
|
|
+
|
|
|
+.upload-placeholder-square:hover {
|
|
|
+ border-color: v-bind(categoryThemeColor);
|
|
|
+}
|
|
|
+
|
|
|
+.upload-placeholder-square .upload-icon {
|
|
|
+ font-size: 20px;
|
|
|
+ color: #999;
|
|
|
+}
|
|
|
+
|
|
|
+.form-preview-img-square {
|
|
|
+ width: 16px;
|
|
|
+ height: 16px;
|
|
|
+ object-fit: contain;
|
|
|
+}
|
|
|
+
|
|
|
+/* 头部分类样式 */
|
|
|
+.header-category-editor {
|
|
|
+ padding: 20px 40px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 40px;
|
|
|
+}
|
|
|
+
|
|
|
+.header-preview-outer {
|
|
|
+ width: 100%;
|
|
|
+ background: #f8f9fa;
|
|
|
+ padding: 40px 20px;
|
|
|
+ border-radius: 8px;
|
|
|
+ display: block; /* 改为 block 以便支持内部滚动 */
|
|
|
+ overflow-x: auto; /* 允许内部滚动,防止溢出 */
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+
|
|
|
+.header-preview-box {
|
|
|
+ width: 1350px; /* 强制保持 1350px 宽度以高度还原设计 */
|
|
|
+ height: 60px;
|
|
|
+ background: #fff;
|
|
|
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
|
|
+ border-radius: 4px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ padding: 0 40px; /* 为箭头预留空间 */
|
|
|
+ margin: 0 auto; /* 在容器足够大时居中 */
|
|
|
+ flex-shrink: 0; /* 禁止缩小 */
|
|
|
+ position: relative;
|
|
|
+ overflow: hidden; /* 关键:确保内部超出部分被剪裁,由滚动容器处理 */
|
|
|
+}
|
|
|
+
|
|
|
+.header-nav-scroll {
|
|
|
+ flex: 1;
|
|
|
+ overflow-x: auto;
|
|
|
+ scrollbar-width: none;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ scroll-behavior: smooth;
|
|
|
+}
|
|
|
+
|
|
|
+.header-nav-scroll::-webkit-scrollbar {
|
|
|
+ display: none;
|
|
|
+}
|
|
|
+
|
|
|
+.header-nav-list {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 30px;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.nav-arrow {
|
|
|
+ position: absolute;
|
|
|
+ top: 50%;
|
|
|
+ transform: translateY(-50%);
|
|
|
+ width: 24px;
|
|
|
+ height: 24px;
|
|
|
+ background: #eee;
|
|
|
+ border-radius: 50%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ cursor: pointer;
|
|
|
+ z-index: 10;
|
|
|
+ color: #666;
|
|
|
+ transition: all 0.2s;
|
|
|
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
|
|
|
+}
|
|
|
+
|
|
|
+.nav-arrow:hover {
|
|
|
+ background: #e0e0e0;
|
|
|
+ color: #333;
|
|
|
+}
|
|
|
+
|
|
|
+.left-arrow {
|
|
|
+ left: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.right-arrow {
|
|
|
+ right: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.header-nav-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ cursor: pointer;
|
|
|
+ white-space: nowrap;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.header-nav-item .item-icon {
|
|
|
+ width: 22px;
|
|
|
+ height: 22px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+
|
|
|
+.header-nav-item .item-icon img {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ object-fit: contain;
|
|
|
+}
|
|
|
+
|
|
|
+.header-nav-item .item-text {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #333;
|
|
|
+ transition: color 0.2s;
|
|
|
+}
|
|
|
+
|
|
|
+.header-nav-item:hover .item-text {
|
|
|
+ color: var(--hover-color);
|
|
|
+}
|
|
|
+
|
|
|
+/* 广告模块样式 */
|
|
|
+.ad-preview-grid {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: nowrap;
|
|
|
+ gap: 20px;
|
|
|
+ max-width: 1600px;
|
|
|
+ margin: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.ad-item {
|
|
|
+ background: #f8f9fa;
|
|
|
+ border-radius: 12px;
|
|
|
+ padding: 15px;
|
|
|
+ position: relative;
|
|
|
+ overflow: hidden;
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+
|
|
|
+.ad-item:hover .ad-hover-mask {
|
|
|
+ display: flex;
|
|
|
+}
|
|
|
+
|
|
|
+.ad-hover-mask {
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ bottom: 0;
|
|
|
+ background: rgba(0, 0, 0, 0.4);
|
|
|
+ display: none;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ z-index: 100;
|
|
|
+}
|
|
|
+
|
|
|
+.ad-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: baseline;
|
|
|
+ margin-bottom: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.ad-title-main {
|
|
|
+ font-size: 18px;
|
|
|
+ font-weight: 800;
|
|
|
+ color: #333;
|
|
|
+}
|
|
|
+
|
|
|
+.ad-title-sub {
|
|
|
+ font-size: 13px;
|
|
|
+}
|
|
|
+
|
|
|
+.ad-title-sub.orange {
|
|
|
+ color: #f58220;
|
|
|
+}
|
|
|
+.ad-title-sub.gray {
|
|
|
+ color: #999;
|
|
|
+}
|
|
|
+
|
|
|
+/* Ad 1: 百亿补贴 */
|
|
|
+.ad-products-subsidy {
|
|
|
+ display: flex;
|
|
|
+ gap: 15px;
|
|
|
+}
|
|
|
+
|
|
|
+.ad-subsidy .product-item {
|
|
|
+ flex: 1;
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+
|
|
|
+.ad-subsidy .product-img {
|
|
|
+ width: 94px;
|
|
|
+ height: 94px;
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 8px;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.ad-subsidy .product-img img {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ object-fit: contain;
|
|
|
+}
|
|
|
+
|
|
|
+.ad-subsidy .product-price {
|
|
|
+ color: #e60012;
|
|
|
+ font-weight: bold;
|
|
|
+ font-size: 15px;
|
|
|
+}
|
|
|
+
|
|
|
+.ad-ranking {
|
|
|
+ padding: 12px !important;
|
|
|
+}
|
|
|
+
|
|
|
+.ad-ranking .ad-header {
|
|
|
+ margin-bottom: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+/* Ad 2: 榜单 */
|
|
|
+.ad-products-ranking {
|
|
|
+ display: flex;
|
|
|
+ gap: 8px;
|
|
|
+ flex: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.ranking-item {
|
|
|
+ flex: 1;
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 8px;
|
|
|
+ padding: 6px 5px 0;
|
|
|
+ position: relative;
|
|
|
+ text-align: center;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ overflow: hidden;
|
|
|
+ height: 135px; /* 高度调小一点 */
|
|
|
+}
|
|
|
+
|
|
|
+.ranking-badge {
|
|
|
+ display: inline-block;
|
|
|
+ background: #fff3e5;
|
|
|
+ color: #f58220;
|
|
|
+ font-size: 11px;
|
|
|
+ padding: 2px 10px;
|
|
|
+ border-radius: 20px;
|
|
|
+ white-space: nowrap;
|
|
|
+ margin-bottom: 4px;
|
|
|
+ transform: scale(0.9); /* 略微缩小标签以腾出空间 */
|
|
|
+}
|
|
|
+
|
|
|
+.ranking-item .product-img {
|
|
|
+ width: 80px; /* 略微缩小图片从 84 降至 80 */
|
|
|
+ height: 80px;
|
|
|
+ margin-bottom: 4px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+
|
|
|
+.ranking-item .product-img img {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ object-fit: contain;
|
|
|
+}
|
|
|
+
|
|
|
+.ranking-footer {
|
|
|
+ background: #fff1f1;
|
|
|
+ color: #e60012;
|
|
|
+ font-size: 12px;
|
|
|
+ padding: 5px 0;
|
|
|
+ font-weight: bold;
|
|
|
+ width: 100%;
|
|
|
+ margin-top: auto;
|
|
|
+ line-height: 1.2;
|
|
|
+}
|
|
|
+
|
|
|
+/* Ad 3: 品牌 */
|
|
|
+.ad-brands-content {
|
|
|
+ display: flex;
|
|
|
+ gap: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.brand-item {
|
|
|
+ flex: 1;
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+
|
|
|
+.brand-logo {
|
|
|
+ width: 50px;
|
|
|
+ height: 50px;
|
|
|
+ margin: 0 auto 8px;
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 4px;
|
|
|
+ padding: 5px;
|
|
|
+}
|
|
|
+
|
|
|
+.brand-logo img {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ object-fit: contain;
|
|
|
+}
|
|
|
+
|
|
|
+.brand-name {
|
|
|
+ font-size: 11px;
|
|
|
+ color: #0071bc;
|
|
|
+ margin-bottom: 5px;
|
|
|
+ height: 20px;
|
|
|
+ line-height: 20px;
|
|
|
+ white-space: nowrap;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.brand-tag-btn {
|
|
|
+ display: inline-block;
|
|
|
+ padding: 2px 10px;
|
|
|
+ border: 1px solid #e60012;
|
|
|
+ color: #e60012;
|
|
|
+ border-radius: 10px;
|
|
|
+ font-size: 11px;
|
|
|
+}
|
|
|
+
|
|
|
+/* Ad 4 & 5: 精选/新品 */
|
|
|
+.ad-products-selection {
|
|
|
+ display: flex;
|
|
|
+ gap: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.selection-item {
|
|
|
+ flex: 1;
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+
|
|
|
+.selection-item .product-img {
|
|
|
+ width: 84px;
|
|
|
+ height: 84px;
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 8px;
|
|
|
+ margin-bottom: 5px;
|
|
|
+}
|
|
|
+
|
|
|
+.selection-item .product-img img {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ object-fit: contain;
|
|
|
+}
|
|
|
+
|
|
|
+.product-price-row {
|
|
|
+ display: flex;
|
|
|
+ align-items: baseline;
|
|
|
+ justify-content: flex-start;
|
|
|
+ gap: 2px;
|
|
|
+}
|
|
|
+
|
|
|
+.product-price-row.center {
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+
|
|
|
+.p-unit {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #e60012;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+.p-val {
|
|
|
+ font-size: 16px;
|
|
|
+ color: #e60012;
|
|
|
+ font-weight: 800;
|
|
|
+}
|
|
|
+.p-tag {
|
|
|
+ background: #3fa9f5;
|
|
|
+ color: #fff;
|
|
|
+ font-size: 10px;
|
|
|
+ padding: 0 4px;
|
|
|
+ border-radius: 2px;
|
|
|
+ margin-left: 2px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 选择弹窗样式 */
|
|
|
+.select-item-row {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ padding: 12px;
|
|
|
+ border: 1px solid #eee;
|
|
|
+ border-radius: 8px;
|
|
|
+ margin-bottom: 10px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+}
|
|
|
+
|
|
|
+.select-item-row:hover {
|
|
|
+ border-color: #409eff;
|
|
|
+ background: #f0f7ff;
|
|
|
+}
|
|
|
+.select-item-row.active {
|
|
|
+ border-color: #409eff;
|
|
|
+ background: #f0f7ff;
|
|
|
+}
|
|
|
+
|
|
|
+.select-item-img {
|
|
|
+ width: 80px;
|
|
|
+ height: 80px;
|
|
|
+ object-fit: contain;
|
|
|
+ margin-right: 20px;
|
|
|
+ background: #f9f9f9;
|
|
|
+ border-radius: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.select-item-info {
|
|
|
+ flex: 1;
|
|
|
+}
|
|
|
+.select-item-name {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #333;
|
|
|
+ margin-bottom: 4px;
|
|
|
+}
|
|
|
+.select-item-price {
|
|
|
+ color: #e60012;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+
|
|
|
+.select-pagination {
|
|
|
+ margin-top: 30px;
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ padding-bottom: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.select-item-id {
|
|
|
+ font-size: 11px;
|
|
|
+ color: #999;
|
|
|
+ margin-top: 2px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 场景方案预览样式 */
|
|
|
+.scenario-preview-outer {
|
|
|
+ width: 100%;
|
|
|
+ overflow-x: auto;
|
|
|
+ padding: 10px 0;
|
|
|
+}
|
|
|
+
|
|
|
+.scenario-preview-box-clean {
|
|
|
+ width: 1600px;
|
|
|
+ max-width: 100%;
|
|
|
+ height: 158px;
|
|
|
+ background: var(--s-theme-color, #66e0a3);
|
|
|
+ border-radius: 16px; /* 加大圆角 */
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ padding: 0 20px 0 32px; /* 减小左边距使标题区左移 */
|
|
|
+ box-sizing: border-box;
|
|
|
+ position: relative;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.s-title-group {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+ margin-bottom: 12px;
|
|
|
+ white-space: nowrap; /* 强制不换行,向后追加 */
|
|
|
+}
|
|
|
+
|
|
|
+.s-main-title {
|
|
|
+ font-size: 24px; /* 调整为 24px */
|
|
|
+ font-weight: 900;
|
|
|
+ color: #fff;
|
|
|
+ flex-shrink: 0; /* 确保不收缩 */
|
|
|
+}
|
|
|
+
|
|
|
+.s-sub-title-inline {
|
|
|
+ font-size: 24px; /* 调整为 24px */
|
|
|
+ font-weight: 900;
|
|
|
+ color: #fff;
|
|
|
+ flex-shrink: 0; /* 确保不收缩 */
|
|
|
+}
|
|
|
+
|
|
|
+.s-btn-wrap {
|
|
|
+ margin-top: 15px;
|
|
|
+}
|
|
|
+
|
|
|
+.s-btn-premium {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ background-color: #fff;
|
|
|
+ color: #e60012;
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: bold;
|
|
|
+ padding: 6px 20px;
|
|
|
+ border-radius: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.scenario-card-premium {
|
|
|
+ width: 288px;
|
|
|
+ height: 130px;
|
|
|
+ border-radius: 8px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ position: relative;
|
|
|
+}
|
|
|
+
|
|
|
+.card-top-header {
|
|
|
+ height: 42px; /* 固定高度,确保下方图片间距准确 */
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ padding: 0 12px;
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+
|
|
|
+.card-titles-group {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.card-main-title {
|
|
|
+ font-size: 15px;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+
|
|
|
+.card-sub-title {
|
|
|
+ font-size: 13px;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+
|
|
|
+.card-arrow-icon {
|
|
|
+ width: 18px;
|
|
|
+ height: 18px;
|
|
|
+ border-radius: 50%;
|
|
|
+ color: #fff;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ font-size: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.card-image-content {
|
|
|
+ width: 272px;
|
|
|
+ height: 80px;
|
|
|
+ margin: 0 8px 8px 8px; /* 左右下保留相同间距 (8px) */
|
|
|
+ border-radius: 4px;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.card-image-content img {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ object-fit: cover;
|
|
|
+}
|
|
|
+
|
|
|
+/* 响应式逻辑:优先隐藏最后一个方案卡片,再隐藏副标题 */
|
|
|
+@media (max-width: 1680px) {
|
|
|
+ .scenario-preview-box-clean {
|
|
|
+ width: 1300px;
|
|
|
+ }
|
|
|
+ .hidden-card-fourth {
|
|
|
+ display: none !important;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@media (max-width: 1400px) {
|
|
|
+ .scenario-preview-box-clean {
|
|
|
+ width: 1000px;
|
|
|
+ }
|
|
|
+ .s-sub-title-inline {
|
|
|
+ display: none !important;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.scenario-header-left {
|
|
|
+ width: 320px;
|
|
|
+ flex-shrink: 0;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+}
|
|
|
+
|
|
|
+.scenario-cards-wrap {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+ gap: 12px; /* 间距调小 */
|
|
|
+}
|
|
|
+
|
|
|
+.scenario-card {
|
|
|
+ width: 288px;
|
|
|
+ height: 130px;
|
|
|
+ background-color: #fff;
|
|
|
+ border-radius: 12px;
|
|
|
+ padding: 12px;
|
|
|
+ box-sizing: border-box;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
|
+ cursor: pointer;
|
|
|
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
|
|
+ border: 1px solid rgba(0, 0, 0, 0.02);
|
|
|
+}
|
|
|
+
|
|
|
+.scenario-card:hover {
|
|
|
+ transform: translateY(-8px);
|
|
|
+ box-shadow: 0 12px 24px rgba(0, 0, 0, 0.1);
|
|
|
+ border-color: rgba(102, 224, 163, 0.3);
|
|
|
+}
|
|
|
+
|
|
|
+.card-top {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ padding: 0 5px;
|
|
|
+}
|
|
|
+
|
|
|
+.card-titles {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.card-main-title {
|
|
|
+ font-size: 18px;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+
|
|
|
+.card-sub-title {
|
|
|
+ font-size: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.card-arrow {
|
|
|
+ width: 18px;
|
|
|
+ height: 18px;
|
|
|
+ border-radius: 50%;
|
|
|
+ color: #fff;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ font-size: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.card-img {
|
|
|
+ width: 272px;
|
|
|
+ height: 80px;
|
|
|
+ border-radius: 6px;
|
|
|
+ overflow: hidden;
|
|
|
+ background-color: #f5f7fa;
|
|
|
+}
|
|
|
+
|
|
|
+.card-img img {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ object-fit: cover;
|
|
|
+}
|
|
|
+
|
|
|
+/* 响应式调整 */
|
|
|
+@media (max-width: 1500px) {
|
|
|
+ .scenario-preview-box {
|
|
|
+ width: 1300px;
|
|
|
+ }
|
|
|
+ .hidden-card-fourth {
|
|
|
+ display: none !important;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@media (max-width: 1200px) {
|
|
|
+ .scenario-preview-box {
|
|
|
+ width: 1000px;
|
|
|
+ }
|
|
|
+ .s-sub-title {
|
|
|
+ display: none !important;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* 广告模块弹窗精修 */
|
|
|
+.ad-setup-dialog :deep(.el-dialog__body) {
|
|
|
+ padding: 30px 40px;
|
|
|
+ max-height: 700px;
|
|
|
+ overflow-y: auto;
|
|
|
+}
|
|
|
+
|
|
|
+.brand-name-display {
|
|
|
+ font-size: 15px;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #333;
|
|
|
+}
|
|
|
+
|
|
|
+.ad-setup-dialog :deep(.el-table__row) {
|
|
|
+ height: 90px; /* 增加行高,大气美观 */
|
|
|
+}
|
|
|
+
|
|
|
+.config-subtitle {
|
|
|
+ font-size: 15px;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #333;
|
|
|
+ margin: 25px 0 15px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ line-height: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.config-subtitle::before {
|
|
|
+ content: '';
|
|
|
+ width: 3px;
|
|
|
+ height: 14px;
|
|
|
+ background: #e60012;
|
|
|
+ margin-right: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 快捷入口预览样式 */
|
|
|
+.quick-entry-preview-outer {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: flex-start;
|
|
|
+ padding: 20px 0;
|
|
|
+}
|
|
|
+
|
|
|
+.qe-mockup-card {
|
|
|
+ width: 230px;
|
|
|
+ height: 167px;
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 12px;
|
|
|
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
|
|
+ padding: 12px 16px;
|
|
|
+ box-sizing: border-box;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ overflow: hidden;
|
|
|
+ position: relative;
|
|
|
+}
|
|
|
+
|
|
|
+.qe-card-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.qe-card-title {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #333;
|
|
|
+}
|
|
|
+
|
|
|
+.qe-header-arrow {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #999;
|
|
|
+}
|
|
|
+
|
|
|
+.qe-grid-container {
|
|
|
+ width: 198px;
|
|
|
+ height: 108px;
|
|
|
+ margin: 0 auto;
|
|
|
+ position: relative;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.qe-grid-wrapper {
|
|
|
+ display: flex;
|
|
|
+ transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
|
|
+ height: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.qe-grid-page {
|
|
|
+ width: 198px;
|
|
|
+ flex-shrink: 0;
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(4, 1fr);
|
|
|
+ grid-template-rows: repeat(2, 1fr);
|
|
|
+ gap: 12px 0;
|
|
|
+}
|
|
|
+
|
|
|
+.qe-item {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ gap: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.qe-icon-wrap {
|
|
|
+ width: 24px;
|
|
|
+ height: 24px;
|
|
|
+ position: relative;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+
|
|
|
+.qe-icon-img {
|
|
|
+ width: 24px;
|
|
|
+ height: 24px;
|
|
|
+ object-fit: contain;
|
|
|
+}
|
|
|
+
|
|
|
+.qe-icon-placeholder {
|
|
|
+ font-size: 20px;
|
|
|
+ color: #666;
|
|
|
+}
|
|
|
+
|
|
|
+.qe-tag-bubble {
|
|
|
+ position: absolute;
|
|
|
+ top: -8px;
|
|
|
+ right: -15px;
|
|
|
+ background: #ff4d4f;
|
|
|
+ color: #fff;
|
|
|
+ font-size: 9px; /* 精确 9px */
|
|
|
+ padding: 1px 5px;
|
|
|
+ border-radius: 10px;
|
|
|
+ white-space: nowrap;
|
|
|
+ transform: scale(0.9);
|
|
|
+ z-index: 100; /* 提升至最高图层 */
|
|
|
+}
|
|
|
+
|
|
|
+.qe-name {
|
|
|
+ font-size: 11px; /* 精确 11px */
|
|
|
+ color: #333;
|
|
|
+ text-align: center;
|
|
|
+ white-space: nowrap;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.qe-nav-btns {
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ bottom: 0;
|
|
|
+ pointer-events: none;
|
|
|
+}
|
|
|
+
|
|
|
+.qe-nav-btn {
|
|
|
+ position: absolute;
|
|
|
+ top: 50%;
|
|
|
+ transform: translateY(-50%);
|
|
|
+ width: 22px;
|
|
|
+ height: 22px;
|
|
|
+ background: rgba(255, 255, 255, 0.9);
|
|
|
+ border-radius: 50%;
|
|
|
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ cursor: pointer;
|
|
|
+ pointer-events: auto;
|
|
|
+ color: #666;
|
|
|
+ z-index: 110; /* 提升至最高图层,确保不被内容遮挡 */
|
|
|
+}
|
|
|
+
|
|
|
+.qe-nav-btn:hover {
|
|
|
+ background: #fff;
|
|
|
+ color: #e60012;
|
|
|
+}
|
|
|
+
|
|
|
+.qe-nav-btn.prev {
|
|
|
+ left: -10px;
|
|
|
+}
|
|
|
+.qe-nav-btn.next {
|
|
|
+ right: -10px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 快捷入口编辑器特定样式 */
|
|
|
+.quick-entry-editor {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ padding: 20px 40px;
|
|
|
+ gap: 30px;
|
|
|
+}
|
|
|
+
|
|
|
+.config-form-inline {
|
|
|
+ background: #fcfdfe;
|
|
|
+ padding: 20px;
|
|
|
+ border-radius: 8px;
|
|
|
+ border: 1px solid #eef2f6;
|
|
|
+}
|
|
|
+
|
|
|
+.table-icon-cell {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+/* 推荐设置编辑区容器 */
|
|
|
+.recommend-editor-container {
|
|
|
+ padding: 24px;
|
|
|
+ border-bottom: none !important;
|
|
|
+}
|
|
|
+
|
|
|
+/* 推荐设置预览样式 */
|
|
|
+.recommend-preview-outer {
|
|
|
+ width: 100%;
|
|
|
+ padding: 10px 0 20px;
|
|
|
+ position: relative;
|
|
|
+}
|
|
|
+
|
|
|
+.recommend-preview-container {
|
|
|
+ width: 1600px !important; /* 严格按照要求设置背景尺寸 */
|
|
|
+ height: 88px !important;
|
|
|
+ position: relative;
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 8px;
|
|
|
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
|
|
|
+ overflow: hidden;
|
|
|
+ margin-bottom: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.recommend-preview-box {
|
|
|
+ width: 100% !important;
|
|
|
+ height: 100% !important;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ padding: 0 40px;
|
|
|
+ box-sizing: border-box;
|
|
|
+ overflow-x: auto;
|
|
|
+ scroll-behavior: smooth;
|
|
|
+ -ms-overflow-style: none;
|
|
|
+ scrollbar-width: none;
|
|
|
+}
|
|
|
+
|
|
|
+.recommend-preview-box::-webkit-scrollbar {
|
|
|
+ display: none;
|
|
|
+}
|
|
|
+
|
|
|
+.recommend-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ padding: 10px 24px;
|
|
|
+ margin: 0 4px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
|
+ height: 60px;
|
|
|
+ border-radius: 8px;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.recommend-item:hover {
|
|
|
+ background: #f5f5f5;
|
|
|
+}
|
|
|
+
|
|
|
+.recommend-item.active {
|
|
|
+ background: transparent;
|
|
|
+}
|
|
|
+
|
|
|
+.recommend-icon {
|
|
|
+ width: 32px;
|
|
|
+ height: 32px;
|
|
|
+ margin-right: 12px;
|
|
|
+ flex-shrink: 0;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+
|
|
|
+.table-icon-preview {
|
|
|
+ width: 40px;
|
|
|
+ height: 40px;
|
|
|
+ background: #f5f5f5;
|
|
|
+ border-radius: 4px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ margin: 0 auto;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.row-icon {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ object-fit: contain;
|
|
|
+}
|
|
|
+
|
|
|
+.recommend-text {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ white-space: nowrap;
|
|
|
+}
|
|
|
+
|
|
|
+.r-main-title {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: bold;
|
|
|
+ line-height: 1.4;
|
|
|
+ white-space: nowrap;
|
|
|
+}
|
|
|
+
|
|
|
+.r-sub-title {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #999;
|
|
|
+ margin-top: 2px;
|
|
|
+ white-space: nowrap;
|
|
|
+}
|
|
|
+
|
|
|
+.recommend-nav-btn {
|
|
|
+ position: absolute;
|
|
|
+ top: 50%;
|
|
|
+ transform: translateY(-50%);
|
|
|
+ width: 32px;
|
|
|
+ height: 32px;
|
|
|
+ background: rgba(255, 255, 255, 0.9);
|
|
|
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
|
+ border-radius: 50%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ cursor: pointer;
|
|
|
+ color: #666;
|
|
|
+ z-index: 100;
|
|
|
+ transition: all 0.2s;
|
|
|
+ border: 1px solid #eee;
|
|
|
+}
|
|
|
+
|
|
|
+.recommend-nav-btn:hover {
|
|
|
+ color: var(--r-theme-color);
|
|
|
+ background: #fff;
|
|
|
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
|
+}
|
|
|
+
|
|
|
+.recommend-nav-btn.prev {
|
|
|
+ left: 10px;
|
|
|
+}
|
|
|
+.recommend-nav-btn.next {
|
|
|
+ right: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 修复色块标签换行 */
|
|
|
+.config-section-standard :deep(.el-form-item__label) {
|
|
|
+ white-space: nowrap;
|
|
|
+}
|
|
|
+/* 去除底部冗余线条 */
|
|
|
+.recommend-editor-container :deep(.el-table__inner-wrapper::before),
|
|
|
+.recommend-editor-container :deep(.el-table--border::after),
|
|
|
+.recommend-editor-container :deep(.el-table--group::after),
|
|
|
+.recommend-editor-container :deep(.el-table::before) {
|
|
|
+ display: none !important;
|
|
|
+}
|
|
|
+
|
|
|
+.recommend-icon img {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ object-fit: contain;
|
|
|
+ background: #f2f2f2;
|
|
|
+ border-radius: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 已选商品管理弹窗增强 */
|
|
|
+.selected-products-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 20px;
|
|
|
+ padding: 10px 0;
|
|
|
+}
|
|
|
+
|
|
|
+.selected-products-header .right-info {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #666;
|
|
|
+}
|
|
|
+
|
|
|
+.selected-products-header .count {
|
|
|
+ color: #e60012;
|
|
|
+ font-weight: 800;
|
|
|
+ margin: 0 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.price-text {
|
|
|
+ color: #e60012;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+
|
|
|
+.empty-placeholder {
|
|
|
+ padding: 60px 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* 商品多选抽屉样式 */
|
|
|
+.product-selection-drawer :deep(.el-drawer__body) {
|
|
|
+ padding: 0;
|
|
|
+ background: #fcfdfe;
|
|
|
+}
|
|
|
+
|
|
|
+.drawer-content-wrapper {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ height: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.drawer-search-bar {
|
|
|
+ padding: 20px;
|
|
|
+ background: #fff;
|
|
|
+ border-bottom: 1px solid #f0f2f5;
|
|
|
+}
|
|
|
+
|
|
|
+.drawer-stat-bar {
|
|
|
+ padding: 12px 20px;
|
|
|
+ background: #e6f7ff;
|
|
|
+ border: 1px solid #91d5ff;
|
|
|
+ color: #1890ff;
|
|
|
+ font-size: 13px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ margin: 15px 20px 0;
|
|
|
+ border-radius: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.drawer-stat-bar .highlight {
|
|
|
+ font-weight: bold;
|
|
|
+ font-size: 16px;
|
|
|
+ margin: 0 2px;
|
|
|
+}
|
|
|
+
|
|
|
+.drawer-content-wrapper :deep(.el-table) {
|
|
|
+ margin: 15px 20px;
|
|
|
+ width: auto !important;
|
|
|
+}
|
|
|
+
|
|
|
+.drawer-product-info {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.drawer-product-info .mini-img {
|
|
|
+ width: 50px;
|
|
|
+ height: 50px;
|
|
|
+ border-radius: 4px;
|
|
|
+ border: 1px solid #f0f0f0;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.drawer-product-info .detail {
|
|
|
+ flex: 1;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.drawer-product-info .name {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #333;
|
|
|
+ line-height: 1.4;
|
|
|
+ margin-bottom: 4px;
|
|
|
+ display: -webkit-box;
|
|
|
+ -webkit-line-clamp: 2;
|
|
|
+ -webkit-box-orient: vertical;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.drawer-product-info .id {
|
|
|
+ font-size: 11px;
|
|
|
+ color: #999;
|
|
|
+}
|
|
|
+
|
|
|
+.drawer-product-info .price {
|
|
|
+ color: #e60012;
|
|
|
+ font-weight: bold;
|
|
|
+ margin-top: 2px;
|
|
|
+}
|
|
|
+
|
|
|
+.drawer-pagination {
|
|
|
+ padding: 20px;
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ background: #fff;
|
|
|
+ border-top: 1px solid #f0f2f5;
|
|
|
+}
|
|
|
+
|
|
|
+.drawer-footer-actions {
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+ gap: 12px;
|
|
|
+ padding: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.m-l-10 {
|
|
|
+ margin-left: 10px;
|
|
|
+}
|
|
|
+</style>
|