|
|
@@ -0,0 +1,1223 @@
|
|
|
+<template>
|
|
|
+ <el-dialog v-model="dialogVisible" title="选择链接" width="65%" @close="handleClose">
|
|
|
+ <div class="link-selector-container">
|
|
|
+ <!-- 左侧树状结构导航 -->
|
|
|
+ <div class="tree-container">
|
|
|
+ <el-tree ref="treeRef" :data="treeData" node-key="id" :props="defaultProps" @node-click="handleNodeClick" default-expand-all></el-tree>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 右侧链接内容 -->
|
|
|
+ <div class="links-container">
|
|
|
+ <div v-if="selectedCategory" class="category-title">{{ selectedCategory.title || selectedCategory.name }}</div>
|
|
|
+ <div v-else class="category-title">基础链接</div>
|
|
|
+
|
|
|
+ <!-- 链接区域 -->
|
|
|
+ <div class="link-list-container">
|
|
|
+ <!-- 商品链接选择区域 -->
|
|
|
+ <div v-if="isProductCategoryComputed" class="product-selection-container">
|
|
|
+ <!-- 搜索框 -->
|
|
|
+ <div class="product-search">
|
|
|
+ <el-input v-model="productSearchKeyword" placeholder="请输入商品名称关键词" clearable @keyup.enter="handleProductSearch" />
|
|
|
+ <el-button type="primary" @click="handleProductSearch">搜索</el-button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 商品列表 -->
|
|
|
+ <div class="product-list">
|
|
|
+ <el-table
|
|
|
+ :data="productList"
|
|
|
+ :style="{ width: '100%' }"
|
|
|
+ :row-class-name="({ row }) => (selectedProduct?.id === row.id ? 'product-row-active' : '')"
|
|
|
+ @row-click="handleProductSelect"
|
|
|
+ class="custom-product-table"
|
|
|
+ >
|
|
|
+ <el-table-column width="80">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-radio :model-value="selectedProduct?.id" :label="row.id" @change="() => handleProductSelect(row)" />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="ID" prop="id" width="180" />
|
|
|
+ <el-table-column label="图片" width="120">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-image
|
|
|
+ :src="row.image || ''"
|
|
|
+ style="width: 80px; height: 60px; object-fit: cover; display: block"
|
|
|
+ :preview-src-list="row.image ? [row.image] : []"
|
|
|
+ >
|
|
|
+ <template #error>
|
|
|
+ <div class="image-slot">
|
|
|
+ <el-icon><Picture /></el-icon>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-image>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="name" label="商品名称">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <div class="product-name">{{ row.name }}</div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 文章链接选择区域 -->
|
|
|
+ <div v-else-if="isArticleCategoryComputed" class="article-selection-container">
|
|
|
+ <!-- 搜索框 -->
|
|
|
+ <div class="product-search">
|
|
|
+ <el-input v-model="articleSearchKeyword" placeholder="请输入文章标题关键词" clearable @keyup.enter="handleArticleSearch" />
|
|
|
+ <el-button type="primary" @click="handleArticleSearch">搜索</el-button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 文章列表 -->
|
|
|
+ <div class="article-list">
|
|
|
+ <el-table
|
|
|
+ :data="articleList"
|
|
|
+ :style="{ width: '100%' }"
|
|
|
+ :row-class-name="({ row }) => (selectedArticle?.id === row.id ? 'article-row-active' : '')"
|
|
|
+ @row-click="handleArticleSelect"
|
|
|
+ class="custom-article-table"
|
|
|
+ >
|
|
|
+ <el-table-column width="80">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-radio :model-value="selectedArticle?.id" :label="row.id" @change="() => handleArticleSelect(row)" />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="ID" prop="id" width="180" />
|
|
|
+ <el-table-column prop="title" label="文章标题">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <div class="article-title">{{ row.title }}</div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 微页面链接选择区域 -->
|
|
|
+ <div v-else-if="isMicropageCategoryComputed" class="micropage-selection-container">
|
|
|
+ <!-- 搜索框 -->
|
|
|
+ <div class="product-search">
|
|
|
+ <el-input v-model="micropageSearchKeyword" placeholder="请输入微页面名称关键词" clearable @keyup.enter="handleMicropageSearch" />
|
|
|
+ <el-button type="primary" @click="handleMicropageSearch">搜索</el-button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 微页面列表 -->
|
|
|
+ <div class="micropage-list">
|
|
|
+ <el-table
|
|
|
+ :data="micropageList"
|
|
|
+ :style="{ width: '100%' }"
|
|
|
+ :row-class-name="({ row }) => (selectedMicropage?.id === row.id ? 'micropage-row-active' : '')"
|
|
|
+ @row-click="handleMicropageSelect"
|
|
|
+ class="custom-micropage-table"
|
|
|
+ >
|
|
|
+ <el-table-column width="80">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-radio :model-value="selectedMicropage?.id" :label="row.id" @change="() => handleMicropageSelect(row)" />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="ID" prop="id" width="180" />
|
|
|
+ <el-table-column prop="name" label="标题名称名称">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <div class="micropage-name">{{ row.name }}</div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 自定义链接输入区域 -->
|
|
|
+ <div v-else-if="isCustomLinkCategory" class="custom-link-container">
|
|
|
+ <el-form label-position="top">
|
|
|
+ <el-form-item label="跳转路径">
|
|
|
+ <el-input
|
|
|
+ v-model="customLinkPath"
|
|
|
+ placeholder="请输入跳转路径"
|
|
|
+ clearable
|
|
|
+ @input="() => emit('update:modelValue', customLinkPath.trim())"
|
|
|
+ >
|
|
|
+ <template #prefix>
|
|
|
+ <el-icon><Aim /></el-icon>
|
|
|
+ </template>
|
|
|
+ </el-input>
|
|
|
+ <div class="custom-link-tips">
|
|
|
+ <el-icon size="14"><Warning /></el-icon>
|
|
|
+ <span>请确保链接格式正确,支持相对路径和绝对路径</span>
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 预设链接展示区域 -->
|
|
|
+ <div v-else class="links-grid">
|
|
|
+ <el-button
|
|
|
+ v-for="link in currentLinks"
|
|
|
+ :key="link.id"
|
|
|
+ size="small"
|
|
|
+ :class="['link-button', { active: selectedLink && selectedLink.id === link.id }]"
|
|
|
+ :type="selectedLink && selectedLink.id === link.id ? 'primary' : 'default'"
|
|
|
+ :title="link.url"
|
|
|
+ @click="handleLinkClick(link)"
|
|
|
+ >
|
|
|
+ {{ link.name }}
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 链接预览 -->
|
|
|
+ <div v-if="selectedLink || (isCustomLinkCategory && customLinkPath)" class="link-preview">
|
|
|
+ <span class="preview-label">当前选择:</span>
|
|
|
+ <span v-if="selectedLink" class="preview-name">{{ selectedLink.name }}</span>
|
|
|
+ <span v-else class="preview-name">自定义链接</span>
|
|
|
+ <span class="preview-path">{{ selectedLink ? selectedLink.url : customLinkPath }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 底部按钮 -->
|
|
|
+ <template #footer>
|
|
|
+ <el-button @click="handleCancel">取消</el-button>
|
|
|
+ <el-button type="primary" @click="handleConfirm">确定</el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script lang="ts" setup>
|
|
|
+import { ref, computed, watch, nextTick, onMounted, onUnmounted } from 'vue';
|
|
|
+import type { TreeNode } from 'element-plus';
|
|
|
+import { ElMessage } from 'element-plus';
|
|
|
+import { Aim, Warning, Picture } from '@element-plus/icons-vue';
|
|
|
+import { getCategoryTree } from '@/api/mall/pageCategory/api';
|
|
|
+import { listPageLink } from '@/api/mall/pageLink/api';
|
|
|
+import { pcDiyList } from '@/api/diy/index';
|
|
|
+import { listBase } from '@/api/pmsProduct/base';
|
|
|
+import { listServiceCase } from '@/api/product/serviceCase';
|
|
|
+
|
|
|
+interface LinkItem {
|
|
|
+ id: string;
|
|
|
+ name: string;
|
|
|
+ url: string;
|
|
|
+}
|
|
|
+
|
|
|
+// 商品数据接口
|
|
|
+interface ProductItem {
|
|
|
+ id: number;
|
|
|
+ name: string;
|
|
|
+ image: string;
|
|
|
+}
|
|
|
+
|
|
|
+// 文章数据接口
|
|
|
+interface ArticleItem {
|
|
|
+ id: number;
|
|
|
+ title: string;
|
|
|
+}
|
|
|
+
|
|
|
+// 微页面数据接口
|
|
|
+interface MicropageItem {
|
|
|
+ id: number;
|
|
|
+ name: string;
|
|
|
+}
|
|
|
+
|
|
|
+// Props
|
|
|
+interface Props {
|
|
|
+ visible: boolean;
|
|
|
+ modelValue?: string;
|
|
|
+}
|
|
|
+
|
|
|
+const props = withDefaults(defineProps<Props>(), {
|
|
|
+ visible: false,
|
|
|
+ modelValue: ''
|
|
|
+});
|
|
|
+
|
|
|
+// Emits
|
|
|
+interface Emits {
|
|
|
+ 'update:visible': [value: boolean];
|
|
|
+ 'update:modelValue': [value: string];
|
|
|
+ 'confirm': [value: string, link: LinkItem];
|
|
|
+ 'cancel': [];
|
|
|
+ 'close': [];
|
|
|
+ 'reset': [];
|
|
|
+}
|
|
|
+
|
|
|
+const emit = defineEmits<Emits>();
|
|
|
+
|
|
|
+// 响应式数据
|
|
|
+const dialogVisible = computed({
|
|
|
+ get: () => props.visible,
|
|
|
+ set: (value) => emit('update:visible', value)
|
|
|
+});
|
|
|
+
|
|
|
+const treeRef = ref<InstanceType<(typeof import('element-plus'))['ElTree']>>();
|
|
|
+const selectedCategory = ref<any>();
|
|
|
+const selectedLink = ref<LinkItem>();
|
|
|
+const customLinkPath = ref('');
|
|
|
+const selectedProduct = ref<ProductItem>();
|
|
|
+const productList = ref<ProductItem[]>([]);
|
|
|
+const productSearchKeyword = ref('');
|
|
|
+const isProductCategory = ref(false);
|
|
|
+
|
|
|
+// 文章相关响应式数据
|
|
|
+const selectedArticle = ref<ArticleItem>();
|
|
|
+const articleList = ref<ArticleItem[]>([]);
|
|
|
+const articleSearchKeyword = ref('');
|
|
|
+const isArticleCategory = ref(false);
|
|
|
+
|
|
|
+// 微页面相关响应式数据
|
|
|
+// 获取DIY上下文
|
|
|
+const selectedMicropage = ref<MicropageItem>();
|
|
|
+const micropageList = ref<any[]>([]);
|
|
|
+const micropageSearchKeyword = ref('');
|
|
|
+const isMicropageCategory = ref(false);
|
|
|
+
|
|
|
+// 动态树数据
|
|
|
+const treeData = ref<any[]>([]);
|
|
|
+// 分类对应的链接数据
|
|
|
+const categoryLinksMap = ref<Map<string | number, LinkItem[]>>(new Map());
|
|
|
+
|
|
|
+// 加载分类数据
|
|
|
+const loadCategoryData = async () => {
|
|
|
+ try {
|
|
|
+ const res = await getCategoryTree();
|
|
|
+ if (res.data) {
|
|
|
+ // 只保留非顶级的分类
|
|
|
+ const filteredItems = res.data.find((item) => item.id === 0).children || [];
|
|
|
+ // 剩余的节点转换为树结构
|
|
|
+ const convertToTreeFormat = (items: any[]) => {
|
|
|
+ return items.map((item) => ({
|
|
|
+ id: item.id,
|
|
|
+ title: item.name,
|
|
|
+ originalData: item,
|
|
|
+ children: item.children ? convertToTreeFormat(item.children) : []
|
|
|
+ }));
|
|
|
+ };
|
|
|
+ treeData.value = convertToTreeFormat(filteredItems);
|
|
|
+ // 获取所有分类的 ID 列表
|
|
|
+ const allCategoryIds = getAllCategoryIds(treeData.value);
|
|
|
+
|
|
|
+ // 并行请求所有分类的链接数据
|
|
|
+ const linkPromises = allCategoryIds.map((id) => loadLinksForCategory(id));
|
|
|
+ const allLinks = await Promise.all(linkPromises);
|
|
|
+
|
|
|
+ // 将结果合并到 categoryLinksMap
|
|
|
+ allLinks.forEach((links, index) => {
|
|
|
+ const categoryId = allCategoryIds[index];
|
|
|
+ if (links && links.length > 0) {
|
|
|
+ categoryLinksMap.value.set(categoryId, links);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取分类树失败:', error);
|
|
|
+ ElMessage.error('获取分类树失败');
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 递归获取所有分类 ID
|
|
|
+const getAllCategoryIds = (nodes: any[]): number[] => {
|
|
|
+ const ids: number[] = [];
|
|
|
+ nodes.forEach((node) => {
|
|
|
+ ids.push(node.id);
|
|
|
+ if (node.children && node.children.length > 0) {
|
|
|
+ ids.push(...getAllCategoryIds(node.children));
|
|
|
+ }
|
|
|
+ });
|
|
|
+ return ids;
|
|
|
+};
|
|
|
+
|
|
|
+// 加载指定分类的链接数据
|
|
|
+const loadLinksForCategory = async (categoryId: number): Promise<LinkItem[]> => {
|
|
|
+ try {
|
|
|
+ const res = await listPageLink({
|
|
|
+ cateId: categoryId,
|
|
|
+ pageNum: 1,
|
|
|
+ pageSize: 100,
|
|
|
+ createTime: undefined
|
|
|
+ });
|
|
|
+ if (res.rows) {
|
|
|
+ return res.rows.map((item: any) => ({
|
|
|
+ id: item.linkKey,
|
|
|
+ name: item.name,
|
|
|
+ url: item.url
|
|
|
+ }));
|
|
|
+ }
|
|
|
+ return [];
|
|
|
+ } catch (error) {
|
|
|
+ console.error(`获取分类 ${categoryId} 链接失败:`, error);
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 默认树配置
|
|
|
+const defaultProps = {
|
|
|
+ children: 'children',
|
|
|
+ label: 'title'
|
|
|
+};
|
|
|
+
|
|
|
+// 当前显示的链接列表
|
|
|
+const currentLinks = computed(() => {
|
|
|
+ if (!selectedCategory.value || !selectedCategory.value.id) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 从缓存中获取链接数据
|
|
|
+ return categoryLinksMap.value.get(selectedCategory.value.id) || [];
|
|
|
+});
|
|
|
+
|
|
|
+// 是否为自定义链接分类
|
|
|
+const isCustomLinkCategory = computed(() => {
|
|
|
+ return selectedCategory.value?.originalData?.type === '4' || selectedCategory.value?.name === '自定义链接';
|
|
|
+});
|
|
|
+
|
|
|
+// 是否为商品分类
|
|
|
+const isProductCategoryComputed = computed(() => {
|
|
|
+ return selectedCategory.value?.name === '商品' || selectedCategory.value?.title === '商品';
|
|
|
+});
|
|
|
+
|
|
|
+// 是否为文章分类
|
|
|
+const isArticleCategoryComputed = computed(() => {
|
|
|
+ return selectedCategory.value?.name === '文章' || selectedCategory.value?.title === '文章';
|
|
|
+});
|
|
|
+
|
|
|
+// 是否为微页面分类
|
|
|
+const isMicropageCategoryComputed = computed(() => {
|
|
|
+ return selectedCategory.value?.name === '微页面' || selectedCategory.value?.title === '微页面';
|
|
|
+});
|
|
|
+
|
|
|
+// 处理节点点击
|
|
|
+async function handleNodeClick(node: TreeNode) {
|
|
|
+ const treeNode = node as any;
|
|
|
+ if (treeNode.id) {
|
|
|
+ selectedCategory.value = treeNode;
|
|
|
+ selectedLink.value = undefined;
|
|
|
+ selectedProduct.value = undefined;
|
|
|
+ selectedArticle.value = undefined;
|
|
|
+ selectedMicropage.value = undefined;
|
|
|
+
|
|
|
+ // 设置当前分类类型
|
|
|
+ isProductCategory.value = isProductCategoryComputed.value;
|
|
|
+ isArticleCategory.value = isArticleCategoryComputed.value;
|
|
|
+ isMicropageCategory.value = isMicropageCategoryComputed.value;
|
|
|
+
|
|
|
+ if (isProductCategory.value) {
|
|
|
+ // 如果是商品分类,清空搜索关键词并加载商品列表
|
|
|
+ productSearchKeyword.value = '';
|
|
|
+ await loadProductList();
|
|
|
+ emit('update:modelValue', '');
|
|
|
+ } else if (isArticleCategory.value) {
|
|
|
+ // 如果是文章分类,清空搜索关键词并加载文章列表
|
|
|
+ articleSearchKeyword.value = '';
|
|
|
+ await loadArticleList();
|
|
|
+ emit('update:modelValue', '');
|
|
|
+ } else if (isMicropageCategory.value) {
|
|
|
+ // 如果是微页面分类,清空搜索关键词并加载微页面列表
|
|
|
+ micropageSearchKeyword.value = '';
|
|
|
+ await loadMicropageList();
|
|
|
+ emit('update:modelValue', '');
|
|
|
+ } else if (isCustomLinkCategory.value) {
|
|
|
+ // 如果是自定义链接分类,清空选择
|
|
|
+ customLinkPath.value = '';
|
|
|
+ emit('update:modelValue', '');
|
|
|
+ } else {
|
|
|
+ // 获取当前分类的链接
|
|
|
+ const links = currentLinks.value;
|
|
|
+ if (links.length > 0) {
|
|
|
+ // 自动选择第一个链接
|
|
|
+ selectedLink.value = links[0];
|
|
|
+ emit('update:modelValue', links[0].url);
|
|
|
+ } else {
|
|
|
+ emit('update:modelValue', '');
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 加载商品列表
|
|
|
+async function loadProductList(keyword: string = '') {
|
|
|
+ try {
|
|
|
+ // 这里模拟获取商品数据,实际项目中应该调用API
|
|
|
+ const queryParams: any = {
|
|
|
+ pageNum: 1,
|
|
|
+ pageSize: 100,
|
|
|
+ itemName: keyword
|
|
|
+ };
|
|
|
+ const res = await listBase(queryParams);
|
|
|
+ if (res.rows) {
|
|
|
+ productList.value = res.rows.map((item: any) => ({
|
|
|
+ id: Number(item.id),
|
|
|
+ name: item.itemName,
|
|
|
+ image: item.productImage
|
|
|
+ }));
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载商品列表失败:', error);
|
|
|
+ ElMessage.error('加载商品列表失败');
|
|
|
+ productList.value = [];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 搜索商品
|
|
|
+async function handleProductSearch() {
|
|
|
+ await loadProductList(productSearchKeyword.value);
|
|
|
+}
|
|
|
+
|
|
|
+// 选择商品
|
|
|
+function handleProductSelect(product: ProductItem) {
|
|
|
+ selectedProduct.value = product;
|
|
|
+ // 构建商品详情链接:pages/goods/detail/detail?id=商品Id
|
|
|
+ const productUrl = `/pages/goods/detail/detail?id=${product.id}`;
|
|
|
+ emit('update:modelValue', productUrl);
|
|
|
+}
|
|
|
+
|
|
|
+// 加载文章列表
|
|
|
+async function loadArticleList(keyword: string = '') {
|
|
|
+ try {
|
|
|
+ const queryParams = {
|
|
|
+ pageNum: 1,
|
|
|
+ pageSize: 100,
|
|
|
+ caseTitle: keyword
|
|
|
+ };
|
|
|
+
|
|
|
+ const res = await listServiceCase(queryParams);
|
|
|
+ if (res.data) {
|
|
|
+ articleList.value = res.data.map((item: any) => ({
|
|
|
+ id: item.id,
|
|
|
+ title: item.caseTitle
|
|
|
+ }));
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载文章列表失败:', error);
|
|
|
+ ElMessage.error('加载文章列表失败');
|
|
|
+ articleList.value = [];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 搜索文章
|
|
|
+async function handleArticleSearch() {
|
|
|
+ await loadArticleList(articleSearchKeyword.value);
|
|
|
+}
|
|
|
+
|
|
|
+// 选择文章
|
|
|
+function handleArticleSelect(article: ArticleItem) {
|
|
|
+ selectedArticle.value = article;
|
|
|
+ // 构建文章详情链接:pages/article/detail/detail?id=文章Id
|
|
|
+ const articleUrl = `/pages/article/detail/detail?id=${article.id}`;
|
|
|
+ emit('update:modelValue', articleUrl);
|
|
|
+}
|
|
|
+
|
|
|
+// 加载微页面列表
|
|
|
+async function loadMicropageList(keyword: string = '') {
|
|
|
+ try {
|
|
|
+ // 这里模拟获取微页面数据,实际项目中应该调用API
|
|
|
+ // 假设的查询参数结构
|
|
|
+ const queryParams = {
|
|
|
+ pageNum: 1,
|
|
|
+ pageSize: 100,
|
|
|
+ title: keyword
|
|
|
+ };
|
|
|
+ let res = undefined;
|
|
|
+ res = await pcDiyList(queryParams);
|
|
|
+ if (res.rows) {
|
|
|
+ micropageList.value = res.list.map((item: any) => ({
|
|
|
+ id: item.id,
|
|
|
+ name: item.name
|
|
|
+ }));
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载微页面列表失败:', error);
|
|
|
+ ElMessage.error('加载微页面列表失败');
|
|
|
+ micropageList.value = [];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 搜索微页面
|
|
|
+async function handleMicropageSearch() {
|
|
|
+ await loadMicropageList(micropageSearchKeyword.value);
|
|
|
+}
|
|
|
+
|
|
|
+// 选择微页面
|
|
|
+function handleMicropageSelect(micropage: MicropageItem) {
|
|
|
+ selectedMicropage.value = micropage;
|
|
|
+ // 构建微页面链接:pages/micropage/detail/detail?id=微页面Id
|
|
|
+ const micropageUrl = `/pages/diy/index?id=${micropage.id}`;
|
|
|
+ emit('update:modelValue', micropageUrl);
|
|
|
+}
|
|
|
+
|
|
|
+// 处理链接点击
|
|
|
+function handleLinkClick(link: LinkItem) {
|
|
|
+ selectedLink.value = link;
|
|
|
+ // 触发临时选择事件,让父组件可以实时获取选择的链接
|
|
|
+ emit('update:modelValue', link.url);
|
|
|
+}
|
|
|
+
|
|
|
+// 处理确定
|
|
|
+function handleConfirm() {
|
|
|
+ if (isProductCategory.value) {
|
|
|
+ if (!selectedProduct.value) {
|
|
|
+ ElMessage({
|
|
|
+ message: '请选择商品',
|
|
|
+ type: 'warning',
|
|
|
+ duration: 2000
|
|
|
+ });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ dialogVisible.value = false;
|
|
|
+ ElMessage({
|
|
|
+ message: '商品设置成功',
|
|
|
+ type: 'success',
|
|
|
+ duration: 2000
|
|
|
+ });
|
|
|
+ } else if (isArticleCategory.value) {
|
|
|
+ if (!selectedArticle.value) {
|
|
|
+ ElMessage({
|
|
|
+ message: '请选择文章',
|
|
|
+ type: 'warning',
|
|
|
+ duration: 2000
|
|
|
+ });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ dialogVisible.value = false;
|
|
|
+ ElMessage({
|
|
|
+ message: '文章设置成功',
|
|
|
+ type: 'success',
|
|
|
+ duration: 2000
|
|
|
+ });
|
|
|
+ } else if (isMicropageCategory.value) {
|
|
|
+ if (!selectedMicropage.value) {
|
|
|
+ ElMessage({
|
|
|
+ message: '请选择微页面',
|
|
|
+ type: 'warning',
|
|
|
+ duration: 2000
|
|
|
+ });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ dialogVisible.value = false;
|
|
|
+ ElMessage({
|
|
|
+ message: '微页面设置成功',
|
|
|
+ type: 'success',
|
|
|
+ duration: 2000
|
|
|
+ });
|
|
|
+ } else if (isCustomLinkCategory.value) {
|
|
|
+ // 自定义链接处理
|
|
|
+ if (!customLinkPath.value.trim()) {
|
|
|
+ ElMessage({
|
|
|
+ message: '请输入自定义链接地址',
|
|
|
+ type: 'warning',
|
|
|
+ duration: 2000
|
|
|
+ });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 验证链接格式
|
|
|
+ const path = customLinkPath.value.trim();
|
|
|
+ // 创建临时的自定义链接项
|
|
|
+ const customLink: LinkItem = {
|
|
|
+ id: `custom-${Date.now()}`,
|
|
|
+ name: '自定义链接',
|
|
|
+ url: path
|
|
|
+ };
|
|
|
+
|
|
|
+ emit('update:modelValue', path);
|
|
|
+ emit('confirm', path, customLink);
|
|
|
+ dialogVisible.value = false;
|
|
|
+ ElMessage({
|
|
|
+ message: '自定义链接设置成功',
|
|
|
+ type: 'success',
|
|
|
+ duration: 2000
|
|
|
+ });
|
|
|
+ } else if (selectedLink.value) {
|
|
|
+ // 普通链接处理
|
|
|
+ emit('update:modelValue', selectedLink.value.url);
|
|
|
+ emit('confirm', selectedLink.value.url, selectedLink.value);
|
|
|
+ dialogVisible.value = false;
|
|
|
+ // 确认选择后,显示成功提示
|
|
|
+ ElMessage({
|
|
|
+ message: '链接选择成功',
|
|
|
+ type: 'success',
|
|
|
+ duration: 2000
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ // 如果没有选择链接,提示用户
|
|
|
+ ElMessage({
|
|
|
+ message: '请选择一个链接',
|
|
|
+ type: 'warning',
|
|
|
+ duration: 2000
|
|
|
+ });
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 处理取消
|
|
|
+function handleCancel() {
|
|
|
+ emit('cancel');
|
|
|
+ dialogVisible.value = false;
|
|
|
+}
|
|
|
+
|
|
|
+// 打开对话框的方法,用于外部调用
|
|
|
+const open = () => {
|
|
|
+ dialogVisible.value = true;
|
|
|
+ // 如果数据还没加载完成,则重新加载
|
|
|
+ if (treeData.value.length === 0) {
|
|
|
+ loadCategoryData();
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 处理关闭
|
|
|
+function handleClose() {
|
|
|
+ emit('close');
|
|
|
+ // 对话框关闭时重置所有状态,不更新modelValue
|
|
|
+ selectedCategory.value = undefined;
|
|
|
+ selectedLink.value = undefined;
|
|
|
+ selectedProduct.value = undefined;
|
|
|
+ selectedArticle.value = undefined;
|
|
|
+ selectedMicropage.value = undefined;
|
|
|
+ if (treeRef.value) {
|
|
|
+ treeRef.value.setCurrentKey(null);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 重置状态
|
|
|
+function resetState() {
|
|
|
+ selectedCategory.value = undefined;
|
|
|
+ selectedLink.value = undefined;
|
|
|
+ customLinkPath.value = '';
|
|
|
+ selectedProduct.value = undefined;
|
|
|
+ selectedArticle.value = undefined;
|
|
|
+ selectedMicropage.value = undefined;
|
|
|
+ productSearchKeyword.value = '';
|
|
|
+ articleSearchKeyword.value = '';
|
|
|
+ micropageSearchKeyword.value = '';
|
|
|
+
|
|
|
+ // 重置树的当前节点
|
|
|
+ if (treeRef.value) {
|
|
|
+ treeRef.value.setCurrentKey(null);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 监听dialogVisible变化,当对话框打开时初始化状态
|
|
|
+watch(
|
|
|
+ () => dialogVisible.value,
|
|
|
+ async (newValue) => {
|
|
|
+ if (newValue) {
|
|
|
+ nextTick(async () => {
|
|
|
+ // 重置自定义链接输入
|
|
|
+ customLinkPath.value = '';
|
|
|
+ // 清空缓存
|
|
|
+ categoryLinksMap.value.clear();
|
|
|
+
|
|
|
+ // 加载分类数据
|
|
|
+ await loadCategoryData();
|
|
|
+
|
|
|
+ if (props.modelValue) {
|
|
|
+ // 如果有传入的modelValue,则尝试匹配
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果没有匹配的链接或没有传入modelValue,则设置默认选中
|
|
|
+ if (!selectedCategory.value && treeData.value.length > 0) {
|
|
|
+ // 找到第一个有子分类的节点
|
|
|
+ const findFirstNodeWithLinks = (nodes: any[]) => {
|
|
|
+ for (const node of nodes) {
|
|
|
+ if (node.children && node.children.length > 0) {
|
|
|
+ return node.children[0];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ };
|
|
|
+
|
|
|
+ const firstNode = findFirstNodeWithLinks(treeData.value);
|
|
|
+ if (firstNode) {
|
|
|
+ selectedCategory.value = firstNode;
|
|
|
+ if (treeRef.value && firstNode.id) {
|
|
|
+ treeRef.value.setCurrentKey(firstNode.id);
|
|
|
+ // 触发节点点击,加载链接数据
|
|
|
+ await handleNodeClick(firstNode);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+);
|
|
|
+
|
|
|
+// 键盘导航处理
|
|
|
+function handleKeyDown(event: KeyboardEvent) {
|
|
|
+ // 只有当对话框可见时才处理键盘事件
|
|
|
+ if (!dialogVisible.value || !selectedLink.value) return;
|
|
|
+
|
|
|
+ const links = currentLinks.value;
|
|
|
+ if (links.length === 0) return;
|
|
|
+
|
|
|
+ const currentIndex = links.findIndex((link) => link.id === selectedLink.value?.id);
|
|
|
+
|
|
|
+ switch (event.key) {
|
|
|
+ case 'ArrowRight':
|
|
|
+ // 向右箭头:选择下一个链接
|
|
|
+ event.preventDefault();
|
|
|
+ if (currentIndex < links.length - 1) {
|
|
|
+ handleLinkClick(links[currentIndex + 1]);
|
|
|
+ } else if (links.length > 0) {
|
|
|
+ handleLinkClick(links[0]);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 'ArrowLeft':
|
|
|
+ // 向左箭头:选择上一个链接
|
|
|
+ event.preventDefault();
|
|
|
+ if (currentIndex > 0) {
|
|
|
+ handleLinkClick(links[currentIndex - 1]);
|
|
|
+ } else if (links.length > 0) {
|
|
|
+ handleLinkClick(links[links.length - 1]);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 'Enter':
|
|
|
+ // 回车键:确认选择
|
|
|
+ event.preventDefault();
|
|
|
+ handleConfirm();
|
|
|
+ break;
|
|
|
+ case 'Escape':
|
|
|
+ // ESC键:取消
|
|
|
+ event.preventDefault();
|
|
|
+ handleCancel();
|
|
|
+ break;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 重置方法
|
|
|
+defineExpose({
|
|
|
+ reset: () => {
|
|
|
+ resetState();
|
|
|
+ emit('reset');
|
|
|
+ emit('update:modelValue', '');
|
|
|
+ },
|
|
|
+ open,
|
|
|
+ resetState
|
|
|
+});
|
|
|
+
|
|
|
+// 组件挂载时添加键盘事件监听
|
|
|
+onMounted(() => {
|
|
|
+ document.addEventListener('keydown', handleKeyDown);
|
|
|
+});
|
|
|
+
|
|
|
+// 组件卸载时移除键盘事件监听
|
|
|
+onUnmounted(() => {
|
|
|
+ document.removeEventListener('keydown', handleKeyDown);
|
|
|
+});
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+/* 主容器样式 */
|
|
|
+.link-selector-container {
|
|
|
+ display: flex;
|
|
|
+ height: 800px;
|
|
|
+ gap: 20px;
|
|
|
+ animation: fadeIn 0.3s ease-in-out;
|
|
|
+}
|
|
|
+
|
|
|
+/* 商品链接选择区域样式 */
|
|
|
+.product-selection-container {
|
|
|
+ height: 100%;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+}
|
|
|
+
|
|
|
+.product-search {
|
|
|
+ margin-bottom: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.product-search .el-input {
|
|
|
+ width: 30%;
|
|
|
+ margin-right: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.product-list {
|
|
|
+ flex: 1;
|
|
|
+ overflow-y: auto;
|
|
|
+}
|
|
|
+
|
|
|
+/* Element UI表格样式调整 */
|
|
|
+.product-list .el-table {
|
|
|
+ --el-table-header-bg-color: #1890ff;
|
|
|
+ --el-table-header-text-color: #ffffff;
|
|
|
+ --el-table-border-color: #e4e7ed;
|
|
|
+}
|
|
|
+
|
|
|
+.product-list .el-table :deep(.el-table__header th) {
|
|
|
+ font-weight: 500;
|
|
|
+ text-align: left;
|
|
|
+ padding: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.product-list .el-table :deep(.el-table__body td) {
|
|
|
+ padding: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.product-list .el-table :deep(.el-table__row:hover) {
|
|
|
+ background-color: #f5f7fa;
|
|
|
+}
|
|
|
+
|
|
|
+/* 选中行样式 */
|
|
|
+.product-row-active {
|
|
|
+ background-color: #ecf5ff !important;
|
|
|
+}
|
|
|
+
|
|
|
+/* 商品名称样式 */
|
|
|
+.product-name {
|
|
|
+ font-size: 14px;
|
|
|
+ line-height: 20px;
|
|
|
+ word-break: break-word;
|
|
|
+ max-width: 300px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 左侧树容器 */
|
|
|
+.tree-container {
|
|
|
+ width: 200px;
|
|
|
+ border-right: 1px solid #e4e7ed;
|
|
|
+ overflow-y: auto;
|
|
|
+ background-color: #fafafa;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+/* 右侧链接容器 */
|
|
|
+.links-container {
|
|
|
+ flex: 1;
|
|
|
+ overflow-y: auto;
|
|
|
+ padding: 15px;
|
|
|
+ background-color: #ffffff;
|
|
|
+ border-radius: 4px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+/* 分类标题 */
|
|
|
+.category-title {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: bold;
|
|
|
+ margin-bottom: 20px;
|
|
|
+ color: #303133;
|
|
|
+ padding-bottom: 8px;
|
|
|
+ border-bottom: 2px solid #409eff;
|
|
|
+ animation: slideInLeft 0.3s ease-out;
|
|
|
+}
|
|
|
+
|
|
|
+/* 链接网格 */
|
|
|
+.links-grid {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 12px;
|
|
|
+ margin-bottom: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 自定义链接容器 */
|
|
|
+.custom-link-container {
|
|
|
+ animation: fadeInUp 0.3s ease-out;
|
|
|
+}
|
|
|
+
|
|
|
+/* 自定义链接提示 */
|
|
|
+.custom-link-tips {
|
|
|
+ margin-top: 8px;
|
|
|
+ font-size: 12px;
|
|
|
+ color: #909399;
|
|
|
+ display: flex;
|
|
|
+ align-items: flex-start;
|
|
|
+ gap: 4px;
|
|
|
+ word-break: break-all;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.custom-link-tips .el-icon {
|
|
|
+ color: #e6a23c;
|
|
|
+}
|
|
|
+
|
|
|
+/* 链接按钮 */
|
|
|
+.link-button {
|
|
|
+ margin-bottom: 8px;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ min-width: 120px;
|
|
|
+ animation: fadeInUp 0.3s ease-out;
|
|
|
+}
|
|
|
+
|
|
|
+.link-button:hover {
|
|
|
+ transform: translateY(-1px);
|
|
|
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
|
+}
|
|
|
+
|
|
|
+.link-button.active {
|
|
|
+ background-color: #409eff;
|
|
|
+ color: white;
|
|
|
+ animation: pulse 0.5s ease-in-out;
|
|
|
+}
|
|
|
+
|
|
|
+/* 链接预览区域 */
|
|
|
+.link-preview {
|
|
|
+ margin-top: auto;
|
|
|
+ padding: 12px;
|
|
|
+ background-color: #f5f7fa;
|
|
|
+ border-radius: 4px;
|
|
|
+ border-left: 4px solid #409eff;
|
|
|
+ font-size: 14px;
|
|
|
+ animation: slideInUp 0.4s ease-out;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.link-preview:hover {
|
|
|
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
|
|
+}
|
|
|
+
|
|
|
+.preview-label {
|
|
|
+ color: #606266;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+.preview-name {
|
|
|
+ color: #409eff;
|
|
|
+ font-weight: bold;
|
|
|
+ margin: 0 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.preview-path {
|
|
|
+ color: #909399;
|
|
|
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
|
|
+ word-break: break-all;
|
|
|
+}
|
|
|
+
|
|
|
+/* 滚动条样式 */
|
|
|
+.tree-container::-webkit-scrollbar,
|
|
|
+.links-container::-webkit-scrollbar {
|
|
|
+ width: 6px;
|
|
|
+ height: 6px;
|
|
|
+}
|
|
|
+
|
|
|
+.tree-container::-webkit-scrollbar-thumb,
|
|
|
+.links-container::-webkit-scrollbar-thumb {
|
|
|
+ background-color: #dcdfe6;
|
|
|
+ border-radius: 3px;
|
|
|
+ transition: background-color 0.3s;
|
|
|
+}
|
|
|
+
|
|
|
+.tree-container::-webkit-scrollbar-thumb:hover,
|
|
|
+.links-container::-webkit-scrollbar-thumb:hover {
|
|
|
+ background-color: #c0c4cc;
|
|
|
+}
|
|
|
+
|
|
|
+.tree-container::-webkit-scrollbar-track,
|
|
|
+.links-container::-webkit-scrollbar-track {
|
|
|
+ background-color: #f5f7fa;
|
|
|
+}
|
|
|
+
|
|
|
+/* Element Plus 树样式覆盖 */
|
|
|
+:deep(.el-tree-node) {
|
|
|
+ padding: 2px 0;
|
|
|
+ transition: all 0.2s ease;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.el-tree-node__content) {
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ padding: 8px 12px;
|
|
|
+ margin: 0;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.el-tree-node__content:hover) {
|
|
|
+ background-color: #ecf5ff;
|
|
|
+ transform: translateX(2px);
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.el-tree-node.is-current > .el-tree-node__content) {
|
|
|
+ background-color: #ecf5ff;
|
|
|
+ color: #409eff;
|
|
|
+ font-weight: 500;
|
|
|
+ border-right: 3px solid #409eff;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.el-tree-node.is-current > .el-tree-node__content .el-tree-node__label) {
|
|
|
+ color: #409eff;
|
|
|
+}
|
|
|
+
|
|
|
+/* 自定义表格样式 */
|
|
|
+:deep(.custom-product-table .el-table__header th) {
|
|
|
+ background-color: #188fff54 !important;
|
|
|
+}
|
|
|
+
|
|
|
+/* 移除单选框后面的点 */
|
|
|
+:deep(.el-radio__label) {
|
|
|
+ display: none;
|
|
|
+}
|
|
|
+
|
|
|
+/* 确保单选框样式正确 */
|
|
|
+:deep(.el-radio) {
|
|
|
+ margin-right: 0;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+
|
|
|
+/* 图片加载失败时的样式 */
|
|
|
+:deep(.image-slot) {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ width: 80px;
|
|
|
+ height: 60px;
|
|
|
+ background-color: #f5f7fa;
|
|
|
+ color: #909399;
|
|
|
+}
|
|
|
+
|
|
|
+/* 动画定义 */
|
|
|
+@keyframes fadeIn {
|
|
|
+ from {
|
|
|
+ opacity: 0;
|
|
|
+ }
|
|
|
+ to {
|
|
|
+ opacity: 1;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes slideInLeft {
|
|
|
+ from {
|
|
|
+ opacity: 0;
|
|
|
+ transform: translateX(-20px);
|
|
|
+ }
|
|
|
+ to {
|
|
|
+ opacity: 1;
|
|
|
+ transform: translateX(0);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes slideInUp {
|
|
|
+ from {
|
|
|
+ opacity: 0;
|
|
|
+ transform: translateY(20px);
|
|
|
+ }
|
|
|
+ to {
|
|
|
+ opacity: 1;
|
|
|
+ transform: translateY(0);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes fadeInUp {
|
|
|
+ from {
|
|
|
+ opacity: 0;
|
|
|
+ transform: translateY(10px);
|
|
|
+ }
|
|
|
+ to {
|
|
|
+ opacity: 1;
|
|
|
+ transform: translateY(0);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes pulse {
|
|
|
+ 0% {
|
|
|
+ transform: scale(1);
|
|
|
+ }
|
|
|
+ 50% {
|
|
|
+ transform: scale(1.05);
|
|
|
+ }
|
|
|
+ 100% {
|
|
|
+ transform: scale(1);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* 响应式设计 */
|
|
|
+@media (max-width: 1200px) {
|
|
|
+ .link-selector-container {
|
|
|
+ height: 350px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .tree-container {
|
|
|
+ width: 180px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@media (max-width: 768px) {
|
|
|
+ .link-selector-container {
|
|
|
+ height: 300px;
|
|
|
+ gap: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .tree-container {
|
|
|
+ width: 160px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .links-container {
|
|
|
+ padding: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .link-button {
|
|
|
+ min-width: 100px;
|
|
|
+ padding: 8px 12px;
|
|
|
+ font-size: 13px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .category-title {
|
|
|
+ font-size: 14px;
|
|
|
+ margin-bottom: 15px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .link-preview {
|
|
|
+ font-size: 12px;
|
|
|
+ padding: 8px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@media (max-width: 480px) {
|
|
|
+ .link-selector-container {
|
|
|
+ flex-direction: column;
|
|
|
+ height: 450px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .tree-container {
|
|
|
+ width: 100%;
|
|
|
+ height: 120px;
|
|
|
+ border-right: none;
|
|
|
+ border-bottom: 1px solid #e4e7ed;
|
|
|
+ }
|
|
|
+
|
|
|
+ .links-container {
|
|
|
+ flex: 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ .links-grid {
|
|
|
+ gap: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .link-button {
|
|
|
+ min-width: calc(50% - 4px);
|
|
|
+ margin-bottom: 0;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* 加载动画 */
|
|
|
+:deep(.el-loading-spinner) {
|
|
|
+ margin-top: -20px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 确保对话框在小屏幕上也能良好显示 */
|
|
|
+:deep(.el-dialog__wrapper) {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.el-dialog) {
|
|
|
+ margin: 0 !important;
|
|
|
+ max-height: 90vh;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.el-dialog__body) {
|
|
|
+ flex: 1;
|
|
|
+ overflow-y: auto;
|
|
|
+ padding: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 当选择链接时的反馈 */
|
|
|
+:deep(.el-button--primary.is-active) {
|
|
|
+ background-color: #337ecc;
|
|
|
+ border-color: #337ecc;
|
|
|
+}
|
|
|
+
|
|
|
+/* 确保选中行样式正确 */
|
|
|
+.product-row-active {
|
|
|
+ background-color: #e6f7ff !important;
|
|
|
+}
|
|
|
+</style>
|