weixin_52219567 1 lună în urmă
părinte
comite
183bdba8a8
52 a modificat fișierele cu 6105 adăugiri și 1 ștergeri
  1. 1 0
      package.json
  2. 18 0
      src/api/diy/index.ts
  3. BIN
      src/assets/images/figure.png
  4. BIN
      src/assets/images/pcdiy/layout2.png
  5. BIN
      src/assets/images/pcdiy/layout4.png
  6. BIN
      src/assets/images/pcdiy/style10-1.png
  7. BIN
      src/assets/images/pcdiy/style10-2.png
  8. BIN
      src/assets/images/pcdiy/style11-1.png
  9. BIN
      src/assets/images/pcdiy/style11-2.png
  10. BIN
      src/assets/images/pcdiy/style11-3.png
  11. BIN
      src/assets/images/pcdiy/style13-1.png
  12. BIN
      src/assets/images/pcdiy/style13-2.png
  13. BIN
      src/assets/images/pcdiy/titlle1.png
  14. BIN
      src/assets/images/pcdiy/titlle10.png
  15. BIN
      src/assets/images/pcdiy/titlle11.png
  16. BIN
      src/assets/images/pcdiy/titlle12.png
  17. BIN
      src/assets/images/pcdiy/titlle13.png
  18. BIN
      src/assets/images/pcdiy/titlle14.png
  19. BIN
      src/assets/images/pcdiy/titlle15.png
  20. BIN
      src/assets/images/pcdiy/titlle2.png
  21. BIN
      src/assets/images/pcdiy/titlle3.png
  22. BIN
      src/assets/images/pcdiy/titlle4.png
  23. BIN
      src/assets/images/pcdiy/titlle5.png
  24. BIN
      src/assets/images/pcdiy/titlle6.png
  25. BIN
      src/assets/images/pcdiy/titlle7.png
  26. BIN
      src/assets/images/pcdiy/titlle8.png
  27. BIN
      src/assets/images/pcdiy/titlle9.png
  28. 71 0
      src/assets/styles/common.scss
  29. 45 0
      src/components/ImagesForm/index.vue
  30. 2 0
      src/components/Pagination/index.vue
  31. 1 1
      src/components/upload-image/index.vue
  32. 374 0
      src/store/modules/pcdiy.ts
  33. 430 0
      src/views/diy/pcEdit.vue
  34. 226 0
      src/views/diy/pcEdit/advert-edit.vue
  35. 248 0
      src/views/diy/pcEdit/article-edit.vue
  36. 254 0
      src/views/diy/pcEdit/brand-edit.vue
  37. 187 0
      src/views/diy/pcEdit/carousel-edit.vue
  38. 443 0
      src/views/diy/pcEdit/goods-edit.vue
  39. 504 0
      src/views/diy/pcEdit/head-edit.vue
  40. 168 0
      src/views/diy/pcEdit/imageCube-edit.vue
  41. 226 0
      src/views/diy/pcEdit/navigation-edit.vue
  42. 280 0
      src/views/diy/pcEdit/textTitle-edit.vue
  43. 209 0
      src/views/diy/pcList.vue
  44. 173 0
      src/views/diy/pcPages/advert.vue
  45. 183 0
      src/views/diy/pcPages/article.vue
  46. 168 0
      src/views/diy/pcPages/brand.vue
  47. 102 0
      src/views/diy/pcPages/carousel.vue
  48. 216 0
      src/views/diy/pcPages/goods.vue
  49. 875 0
      src/views/diy/pcPages/head.vue
  50. 93 0
      src/views/diy/pcPages/imageCube.vue
  51. 151 0
      src/views/diy/pcPages/navigation.vue
  52. 457 0
      src/views/diy/pcPages/textTitle.vue

+ 1 - 0
package.json

@@ -46,6 +46,7 @@
     "vue-json-pretty": "2.4.0",
     "vue-router": "4.5.0",
     "vue-types": "6.0.0",
+    "vuedraggable": "^4.1.0",
     "vxe-table": "4.13.7"
   },
   "devDependencies": {

+ 18 - 0
src/api/diy/index.ts

@@ -44,3 +44,21 @@ export function editDiy(data: any) {
     data: data
   });
 }
+
+// pc自定义页面分页列表
+export function pcDiyList(query: any) {
+  return request({
+    url: '/mall/diyPcPage/list',
+    method: 'get',
+    params: query
+  });
+}
+
+// pc新增自定义页面
+export function pcAddDiy(data: any) {
+  return request({
+    url: '/mall/diyPcPage',
+    method: 'post',
+    data: data
+  });
+}

BIN
src/assets/images/figure.png


BIN
src/assets/images/pcdiy/layout2.png


BIN
src/assets/images/pcdiy/layout4.png


BIN
src/assets/images/pcdiy/style10-1.png


BIN
src/assets/images/pcdiy/style10-2.png


BIN
src/assets/images/pcdiy/style11-1.png


BIN
src/assets/images/pcdiy/style11-2.png


BIN
src/assets/images/pcdiy/style11-3.png


BIN
src/assets/images/pcdiy/style13-1.png


BIN
src/assets/images/pcdiy/style13-2.png


BIN
src/assets/images/pcdiy/titlle1.png


BIN
src/assets/images/pcdiy/titlle10.png


BIN
src/assets/images/pcdiy/titlle11.png


BIN
src/assets/images/pcdiy/titlle12.png


BIN
src/assets/images/pcdiy/titlle13.png


BIN
src/assets/images/pcdiy/titlle14.png


BIN
src/assets/images/pcdiy/titlle15.png


BIN
src/assets/images/pcdiy/titlle2.png


BIN
src/assets/images/pcdiy/titlle3.png


BIN
src/assets/images/pcdiy/titlle4.png


BIN
src/assets/images/pcdiy/titlle5.png


BIN
src/assets/images/pcdiy/titlle6.png


BIN
src/assets/images/pcdiy/titlle7.png


BIN
src/assets/images/pcdiy/titlle8.png


BIN
src/assets/images/pcdiy/titlle9.png


+ 71 - 0
src/assets/styles/common.scss

@@ -1 +1,72 @@
 
+/* 布局flex */
+.flex-row-between {
+  display: -webkit-flex;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+.flex-row-around {
+  display: -webkit-flex;
+  display: flex;
+  justify-content: space-around;
+  align-items: center;
+}
+.flex-row-center {
+  display: -webkit-flex;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+.flex-row-start {
+  display: -webkit-flex;
+  display: flex;
+  justify-content: flex-start;
+  align-items: center;
+}
+.flex-row-end {
+  display: -webkit-flex;
+  display: flex;
+  justify-content: flex-end;
+  align-items: center;
+}
+.flex-column-between {
+  display: -webkit-box;
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+}
+.flex-column-center {
+  display: -webkit-box;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+}
+.flex-1 {
+  flex: 1;
+}
+.flex-wrap {
+  flex-wrap: wrap;
+}
+.flex-start {
+  align-items: flex-start;
+}
+.inline-block {
+  display: inline-block;
+}
+
+.ellipsis {
+  overflow: hidden;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+}
+.ellipsis2 {
+ display: -webkit-box;
+  -webkit-line-clamp: 2;
+  line-clamp: 2;
+  /* 添加标准属性 */
+  -webkit-box-orient: vertical;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}

+ 45 - 0
src/components/ImagesForm/index.vue

@@ -0,0 +1,45 @@
+<template>
+  <div>
+    <el-dialog v-model="dialogShow" title="缩放模式设置" width="600px" append-to-body>
+      <el-form label-width="80px">
+        <el-form-item label="缩放模式">
+          <el-radio-group v-model="formData.imgType">
+            <el-radio :value="1">拉伸</el-radio>
+            <el-radio :value="2">缩放</el-radio>
+            <el-radio :value="3">填充</el-radio>
+          </el-radio-group>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="close">取消</el-button>
+        <el-button type="primary" @click="confirm">确定</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="ts">
+const type = ref<any>(null);
+const dialogShow = ref<any>(false);
+const formData = ref<any>({});
+
+const onOpen = (res: any, res2: any) => {
+  type.value = res2;
+  formData.value = res;
+  dialogShow.value = true;
+};
+
+const emit = defineEmits(['confirmCallBack']);
+
+const confirm = () => {
+  emit('confirmCallBack', formData.value, type.value);
+  dialogShow.value = false;
+};
+const close = () => {
+  dialogShow.value = false;
+};
+
+defineExpose({
+  onOpen
+});
+</script>

+ 2 - 0
src/components/Pagination/index.vue

@@ -1,5 +1,6 @@
 <template>
   <div :class="{ hidden: hidden }" class="pagination-container">
+    <slot name="slotDiv"></slot>
     <!-- 游标分页模式 -->
     <div v-if="cursorMode" class="cursor-pagination">
       <span class="pagination-info">每页 {{ pageSize }} 条</span>
@@ -108,6 +109,7 @@ function handleNextPage() {
 
 <style lang="scss" scoped>
 .pagination-container {
+  position: relative;
   .el-pagination {
     float: v-bind(float);
   }

+ 1 - 1
src/components/upload-image/index.vue

@@ -57,7 +57,7 @@
     </div>
     <FileSelector
       v-model="logoSelectorVisible"
-      title="选择品牌Logo"
+      title="选择图片"
       :allowed-types="[1]"
       :multiple="false"
       :allow-upload="true"

+ 374 - 0
src/store/modules/pcdiy.ts

@@ -0,0 +1,374 @@
+import { defineStore } from 'pinia';
+import { toRaw } from 'vue';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import { cloneDeep } from 'lodash-es';
+import { fa } from 'element-plus/es/locale/index.mjs';
+
+const usePcdiyStore = defineStore('pcdiy', {
+  state: () => {
+    return {
+      componentList: [],
+      uniqueIdCounter: 0,
+      currentIndex: -99, // 当前正在编辑的组件下标
+      currentKey: '',
+      editTab: 'content', // 编辑页面
+      predefineColors: [
+        '#F4391c',
+        '#ff4500',
+        '#ff8c00',
+        '#FFD009',
+        '#ffd700',
+        '#19C650',
+        '#90ee90',
+        '#00ced1',
+        '#1e90ff',
+        '#c71585',
+        '#FF407E',
+        '#CFAF70',
+        '#A253FF',
+        'rgba(255, 69, 0, 0.68)',
+        'rgb(255, 120, 0)',
+        'hsl(181, 100%, 37%)',
+        'hsla(209, 100%, 56%, 0.73)',
+        '#c7158577'
+      ],
+      global: {
+        title: '页面',
+        // 公共模板属性,所有组件都继承,无需重复定义,组件内部根据业务自行调用
+        template: {
+          textColor: '#303133', // 文字颜色
+          pageStartBgColor: '', // 组件底部背景颜色(开始)
+          pageEndBgColor: '', // 组件底部背景颜色(结束)
+          pageGradientAngle: 'to bottom', // 渐变角度,从上到下(to bottom)、从左到右(to right)
+
+          componentStartBgColor: '', // 组件背景颜色(开始)
+          componentEndBgColor: '', // 组件背景颜色(结束)
+          componentGradientAngle: 'to bottom', // 渐变角度,从上到下(to bottom)、从左到右(to right)
+
+          topRounded: 0, // 组件上圆角
+          bottomRounded: 0, // 组件下圆角
+
+          padding: {
+            top: 0, // 上边距
+            bottom: 0, // 下边距
+            both: 0 // 左右边距
+          },
+          isHidden: false // 是否隐藏该组件 true:是,false:否,增加问号说明:勾选后该组件将隐藏,适用于你不希望看到该组件字段又不希望删除的情况;
+        }
+      }
+    };
+  },
+  getters: {
+    editComponent: (state) => {
+      if (state.currentIndex == -99) {
+        return state.global;
+      } else {
+        return state.componentList[state.currentIndex];
+      }
+    }
+  },
+  actions: {
+    // 添加组件
+    addComponent(item: any, key: any) {
+      if (this.componentList.length == 0) {
+        this.uniqueIdCounter = 0;
+      }
+      this.uniqueIdCounter++;
+      const template = cloneDeep(this.global.template);
+      const newItem = {
+        ...item,
+        ...template,
+        itemKey: this.uniqueIdCounter,
+        ignore: []
+      };
+      // 头部组件
+      if (item.id == 1) {
+        newItem.ignore = ['pageBgColor', 'componentBgUrl', 'componentBgColor', 'componentBgColor', 'topRounded', 'bottomRounded', 'marginBoth'];
+        newItem.settings = 1;
+        newItem.carousel = 1;
+        newItem.topStyle = 1;
+        newItem.logo = '';
+        newItem.code = '';
+        newItem.classifyShow = true;
+        newItem.topType = 1;
+        newItem.topNav = [
+          {
+            title: '导航名称',
+            url: '',
+            id: Date.now()
+          }
+        ];
+        newItem.toplabel = [
+          {
+            title: '标签名称',
+            url: '',
+            id: Date.now()
+          }
+        ];
+
+        newItem.leftStyle = 1;
+        newItem.leftBackground = '#ffffff';
+        newItem.leftColor1 = '#101828';
+        newItem.leftColor2 = '#364153';
+
+        newItem.carouselStyle = false;
+        newItem.carouselType = 0;
+        newItem.carouselRadius = 10;
+        newItem.carouselInterval = 3000;
+
+        newItem.centreType = 1;
+        newItem.carouselList = [
+          {
+            imageUrl: '',
+            url: '',
+            imgType: 1,
+            show: true,
+            id: Date.now()
+          }
+        ];
+        newItem.advertNum = 0;
+        newItem.advertList = [
+          {
+            imageUrl: '',
+            url: '',
+            imgType: 1,
+            id: Date.now(),
+            show: false
+          },
+          {
+            imageUrl: '',
+            url: '',
+            imgType: 1,
+            id: Date.now(),
+            show: false
+          },
+          {
+            imageUrl: '',
+            url: '',
+            imgType: 1,
+            id: Date.now(),
+            show: false
+          },
+          {
+            imageUrl: '',
+            url: '',
+            imgType: 1,
+            id: Date.now(),
+            show: false
+          }
+        ];
+        newItem.realType = 1;
+        newItem.realDataType = 1;
+        newItem.realNumber = 5;
+        newItem.navlList = [
+          {
+            imageUrl: '',
+            title: '导航名称',
+            url: '',
+            imgType: 1,
+            id: Date.now()
+          }
+        ];
+        newItem.rightRadius = 5;
+      } else if (item.id == 2) {
+        // 文本标题
+        newItem.styleType = 1;
+
+        newItem.title = '标题名称';
+        newItem.titleUrl = '';
+        newItem.titleAlign = 'left';
+        newItem.titleSize = 18;
+        newItem.titleColor = '#101828';
+        newItem.titleWeight = 'bold';
+
+        newItem.subtitle = '副标题';
+        newItem.subtitleSize = 16;
+        newItem.subtitleColor = '#b7bcd2';
+
+        newItem.more = '更多';
+        newItem.moreShow = true;
+        newItem.moreSize = 14;
+        newItem.moreColor = '#b7bcd2';
+        newItem.moreUrl = '';
+
+        newItem.pageStartBgColor = '#ffffff';
+        newItem.padding.top = 10;
+        newItem.padding.bottom = 10;
+        newItem.padding.both = 20;
+      } else if (item.id == 3) {
+        //图文导航
+        newItem.styleType = 1;
+        newItem.number = 3;
+        newItem.count = 1;
+        newItem.navlList = [
+          {
+            imageUrl: '',
+            title: '标题名称',
+            subtitle: '副标题名称',
+            url: '',
+            imgType: 1,
+            id: Date.now()
+          }
+        ];
+
+        newItem.titleSize = 16;
+        newItem.titleColor = '#101828';
+        newItem.titleWeight = 'bold';
+        newItem.subtitleSize = 12;
+        newItem.subtitleColor = '#364153';
+        newItem.imageRadius = 10;
+        newItem.componentStartBgColor = '#ffffff';
+        newItem.topRounded = 10;
+        newItem.bottomRounded = 10;
+      } else if (item.id == 4) {
+        // 图片魔方
+        newItem.number = 1;
+        newItem.imageHeight = 150;
+        newItem.imagelList = [
+          {
+            imageUrl: '',
+            url: '',
+            imgType: 1,
+            id: Date.now()
+          },
+          {
+            imageUrl: '',
+            url: '',
+            imgType: 1,
+            id: Date.now()
+          },
+          {
+            imageUrl: '',
+            url: '',
+            imgType: 1,
+            id: Date.now()
+          },
+          {
+            imageUrl: '',
+            url: '',
+            imgType: 1,
+            id: Date.now()
+          },
+          {
+            imageUrl: '',
+            url: '',
+            imgType: 1,
+            id: Date.now()
+          }
+        ];
+        newItem.gap = 10;
+        newItem.imageTopRounded = 10;
+        newItem.imageBottomRoundedRounded = 10;
+      } else if (item.id == 6) {
+        //轮播图
+        newItem.imageHeight = 200;
+        newItem.imagelList = [
+          {
+            imageUrl: '',
+            imgType: 1,
+            id: Date.now()
+          }
+        ];
+        newItem.interval = 3000;
+        newItem.styleType = 1;
+        newItem.position = 2;
+        newItem.imageRadius = 10;
+        newItem.ignore = ['componentBgColor', 'topRounded', 'bottomRounded'];
+      } else if (item.id == 7) {
+        //文章咨询
+        newItem.dataType = 1;
+        newItem.dataNumber = 4;
+        newItem.dataIds = [];
+        newItem.border = 1;
+        newItem.borderColor = '#ffffff';
+        newItem.backgroundColor = '#ffffff';
+        newItem.boxTopRounded = 10;
+        newItem.boxBottomRounded = 10;
+      } else if (item.id == 8) {
+        //品牌组件
+        newItem.settings = 1;
+        newItem.imageUrl = '';
+        newItem.url = '';
+        newItem.imgType = 1;
+        newItem.brandList = [
+          {
+            imageUrl: '',
+            title: '品牌名称',
+            subtitle: '品牌介绍',
+            url: '',
+            imgType: 1,
+            id: Date.now()
+          }
+        ];
+        newItem.titleSize = 14;
+        newItem.titleColor = '#101828';
+        newItem.titleWeight = 'bold';
+        newItem.subtitleSize = 12;
+        newItem.subtitleColor = '#364153';
+        newItem.imageRadius = 10;
+        newItem.boxRadius = 5;
+      } else if (item.id == 9) {
+        //图文广告
+        newItem.styleType = 1;
+        newItem.number = 3;
+        newItem.count = 1;
+        newItem.navlList = [
+          {
+            imageUrl: '',
+            title: '标题名称',
+            subtitle: '副标题名称',
+            url: '',
+            imgType: 1,
+            id: Date.now()
+          }
+        ];
+
+        newItem.titleSize = 16;
+        newItem.titleColor = '#101828';
+        newItem.titleWeight = 'bold';
+        newItem.subtitleSize = 12;
+        newItem.subtitleColor = '#364153';
+        newItem.imageRadius = 10;
+        newItem.componentStartBgColor = '#ffffff';
+        newItem.topRounded = 10;
+        newItem.bottomRounded = 10;
+      } else if (item.id == 11) {
+        // 商品组件
+        newItem.styleType = 1;
+        newItem.btnShow = true;
+        newItem.btnStyle = 1;
+        newItem.btnText = '购买';
+        newItem.goodsShow = [1, 2, 3];
+        newItem.goodsType = 1;
+        newItem.goodsIds = [];
+        newItem.goodsClassify = '';
+        newItem.goodsNumber = 5;
+        newItem.goodsSort = 1;
+        newItem.goodsBrand = '';
+
+        newItem.goodsbackgroundColor = '#ffffff';
+        newItem.goodsTitleType = 1;
+        newItem.goodsTitleColor = '#101828';
+        newItem.imageRadius = 10;
+        newItem.goodstopRounded = 10;
+        newItem.goodsbottomRounded = 10;
+        newItem.priceColor = '#E7000B';
+        newItem.btnColor = '#ffffff';
+        newItem.btnbackgroundColor = '#E7000B';
+      }
+
+      this.componentList.push(newItem);
+      this.currentIndex = this.componentList.length - 1;
+      this.currentKey = newItem.itemKey;
+
+      console.log(this.componentList, '4564654');
+    },
+    //点击组件
+    onComponent(item: any, index: any) {
+      this.currentKey = item.itemKey;
+      this.currentIndex = index;
+    }
+  }
+});
+
+export default usePcdiyStore;

+ 430 - 0
src/views/diy/pcEdit.vue

@@ -0,0 +1,430 @@
+<template>
+  <div class="pcEdit">
+    <div class="pcEdit-pages">
+      <el-header class="flex items-center h-[50px] bg-primary px-[20px]">
+        <div class="text-white cursor-pointer flex items-center" @click="goBack">
+          <el-icon size="14">
+            <ArrowLeft />
+          </el-icon>
+          <span class="pl-[5px] text-[14px]">返回</span>
+        </div>
+        <div class="text-white ml-[10px] mr-[20px] flex items-center">
+          <span class="mr-[5px] text-[rgba(255,255,255,.5)]">|</span>
+          <span class="mr-[5px] text-[14px]">正在装修:页面名字</span>
+        </div>
+        <div class="flex-1"></div>
+        <el-button @click="preview()">保存并预览</el-button>
+        <el-button @click="save()">保存</el-button>
+      </el-header>
+      <div class="full-container flex flex-row flex-1 bg-page">
+        <div class="component-list w-[192px]">
+          <!-- 组件列表区域 -->
+          <el-collapse v-model="activeNames" @change="handleChange">
+            <el-collapse-item v-for="(item, key) in collapse" :key="key" :title="item.name" :name="key">
+              <ul class="flex flex-row flex-wrap">
+                <li
+                  v-for="(compItem, compKey) in item.list"
+                  :key="compKey"
+                  class="w-2/4 text-center cursor-pointer h-[65px]"
+                  :title="compItem.name"
+                  @click="diyStore.addComponent(compItem, compKey)"
+                >
+                  <icon v-if="compItem.icon" :name="compItem.icon" size="20px" class="inline-block mt-[3px]" />
+                  <icon v-else name="iconfont iconkaifazujian" size="20px" class="inline-block mt-[3px]" />
+                  <span class="block text-[12px] truncate">{{ compItem.name }}</span>
+                </li>
+              </ul>
+            </el-collapse-item>
+          </el-collapse>
+        </div>
+        <div class="preview-wrap">
+          <!-- 组件编辑区域 -->
+          <div class="preview-pages shadow-lg">
+            <!-- @end="onDragEnd" -->
+            <draggable v-model="diyStore.componentList" item-key="itemKey" class="drag-area">
+              <template #item="{ element, index }">
+                <div @click="diyStore.onComponent(element, index)" class="component-bos">
+                  <!-- <div class="component-box" :style="{ borderWidth: diyStore.currentIndex == index ? '2px' : '0px' }"></div> -->
+                  <component :is="element.components" :key="element.itemKey" :index="index"></component>
+                </div>
+              </template>
+            </draggable>
+          </div>
+        </div>
+        <!-- 编辑组件属性区域 -->
+        <div class="edit-attribute-wrap w-[400px]">
+          <!-- 编辑组件属性区域 -->
+          <el-scrollbar>
+            <el-card class="box-card" shadow="never">
+              <template #header>
+                <div class="card-header flex justify-between items-center">
+                  <span class="title flex-1">{{ diyStore.currentIndex == -99 ? '页面设置' : diyStore.editComponent.name }}</span>
+                  <div class="tab-wrap flex rounded-[50px] bg-gray-100 text-[14px]">
+                    <span
+                      class="cursor-pointer rounded-[50px] py-[5px] px-[15px]"
+                      :class="{ 'bg-primary text-white': diyStore.editTab == 'content' }"
+                      @click="diyStore.editTab = 'content'"
+                      >内容</span
+                    >
+                    <span
+                      class="cursor-pointer rounded-[50px] py-[5px] px-[15px]"
+                      :class="{ 'bg-primary text-white': diyStore.editTab == 'style' }"
+                      @click="diyStore.editTab = 'style'"
+                      >样式</span
+                    >
+                  </div>
+                </div>
+              </template>
+
+              <div class="edit-component-wrap">
+                <component
+                  v-if="diyStore.currentKey"
+                  :is="diyStore.editComponent.edit"
+                  :key="diyStore.currentIndex"
+                  :value="diyStore.componentList[diyStore.currentIndex]"
+                >
+                  <template #style>
+                    <div class="edit-attr-item-wrap">
+                      <h3 class="mb-[10px]">组件样式</h3>
+                      <el-form label-width="90px" class="px-[10px]">
+                        <template v-if="diyStore.editComponent.ignore.indexOf('pageBgColor') == -1">
+                          <el-form-item label="底部背景">
+                            <el-color-picker v-model="diyStore.editComponent.pageStartBgColor" show-alpha :predefine="diyStore.predefineColors" />
+                            <icon name="iconfont iconmap-connect" size="20px" class="block !text-gray-400 mx-[5px]" />
+                            <el-color-picker v-model="diyStore.editComponent.pageEndBgColor" show-alpha :predefine="diyStore.predefineColors" />
+                          </el-form-item>
+                          <div class="text-sm text-gray-400 ml-[90px] mb-[10px]">底部背景包含边距和圆角</div>
+                        </template>
+                        <el-form-item label="渐变角度" v-if="diyStore.editComponent.ignore.indexOf('pageBgColor') == -1">
+                          <el-radio-group v-model="diyStore.editComponent.pageGradientAngle">
+                            <el-radio value="to bottom">从上到下</el-radio>
+                            <el-radio value="to right">从左到右</el-radio>
+                          </el-radio-group>
+                        </el-form-item>
+                        <el-form-item label="组件背景色" v-if="diyStore.editComponent.ignore.indexOf('componentBgColor') == -1">
+                          <el-color-picker v-model="diyStore.editComponent.componentStartBgColor" show-alpha :predefine="diyStore.predefineColors" />
+                          <icon name="iconfont iconmap-connect" size="20px" class="block !text-gray-400 mx-[5px]" />
+                          <el-color-picker v-model="diyStore.editComponent.componentEndBgColor" show-alpha :predefine="diyStore.predefineColors" />
+                        </el-form-item>
+                        <el-form-item label="渐变角度" v-if="diyStore.editComponent.ignore.indexOf('componentBgColor') == -1">
+                          <el-radio-group v-model="diyStore.editComponent.componentGradientAngle">
+                            <el-radio value="to bottom">从上到下</el-radio>
+                            <el-radio value="to right">从左到右</el-radio>
+                          </el-radio-group>
+                        </el-form-item>
+                        <el-form-item label="上边距" v-if="diyStore.editComponent.ignore.indexOf('marginTop') == -1">
+                          <el-slider
+                            v-model="diyStore.editComponent.padding.top"
+                            show-input
+                            size="small"
+                            :min="-100"
+                            class="ml-[10px] diy-nav-slider"
+                          />
+                        </el-form-item>
+                        <el-form-item label="下边距" v-if="diyStore.editComponent.ignore.indexOf('marginBottom') == -1">
+                          <el-slider
+                            v-model="diyStore.editComponent.padding.bottom"
+                            show-input
+                            size="small"
+                            class="ml-[10px] diy-nav-slider"
+                            :min="-100"
+                          />
+                        </el-form-item>
+                        <el-form-item label="左右边距" v-if="diyStore.editComponent.ignore.indexOf('marginBoth') == -1">
+                          <el-slider v-model="diyStore.editComponent.padding.both" show-input size="small" class="ml-[10px] diy-nav-slider" />
+                        </el-form-item>
+                        <el-form-item label="上圆角" v-if="diyStore.editComponent.ignore.indexOf('topRounded') == -1">
+                          <el-slider
+                            v-model="diyStore.editComponent.topRounded"
+                            show-input
+                            size="small"
+                            class="ml-[10px] diy-nav-slider"
+                            :max="100"
+                          />
+                        </el-form-item>
+                        <el-form-item label="下圆角" v-if="diyStore.editComponent.ignore.indexOf('bottomRounded') == -1">
+                          <el-slider
+                            v-model="diyStore.editComponent.bottomRounded"
+                            show-input
+                            size="small"
+                            class="ml-[10px] diy-nav-slider"
+                            :max="100"
+                          />
+                        </el-form-item>
+                      </el-form>
+                    </div>
+                  </template>
+                </component>
+              </div>
+            </el-card>
+          </el-scrollbar>
+          <!-- <div v-for="(item, index) in diyStore.componentList" :key="index">
+          <component v-if="item.itemKey == diyStore.currentKey" :is="item.edit" :value="item"> </component>
+        </div> -->
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup name="Index" lang="ts">
+import { pcAddDiy } from '@/api/diy/index';
+import icon from '@/components/icon/index.vue';
+import draggable from 'vuedraggable';
+
+import usePcdiyStore from '@/store/modules/pcdiy';
+const diyStore = usePcdiyStore();
+
+// 头部组件
+import head from '@/views/diy/pcPages/head.vue';
+const headRef = shallowRef(head);
+import headEdit from '@/views/diy/pcEdit/head-edit.vue';
+const headEditRef = shallowRef(headEdit);
+//文本标题
+import textTitle from '@/views/diy/pcPages/textTitle.vue';
+const ctextTitleRef = shallowRef(textTitle);
+import textTitleEdit from '@/views/diy/pcEdit/textTitle-edit.vue';
+const textTitleEditRef = shallowRef(textTitleEdit);
+//图文导航
+import navigation from '@/views/diy/pcPages/navigation.vue';
+const navigationRef = shallowRef(navigation);
+import navigationEdit from '@/views/diy/pcEdit/navigation-edit.vue';
+const navigationEditRef = shallowRef(navigationEdit);
+//图片魔方
+import imageCube from '@/views/diy/pcPages/imageCube.vue';
+const imageCubeRef = shallowRef(imageCube);
+import imageCubeEdit from '@/views/diy/pcEdit/imageCube-edit.vue';
+const imageCubeEditRef = shallowRef(imageCubeEdit);
+//轮播图
+import carousel from '@/views/diy/pcPages/carousel.vue';
+const carouselRef = shallowRef(carousel);
+import carouselEdit from '@/views/diy/pcEdit/carousel-edit.vue';
+const carouselEditRef = shallowRef(carouselEdit);
+//文章咨询
+import article from '@/views/diy/pcPages/article.vue';
+const articleRef = shallowRef(article);
+import articleEdit from '@/views/diy/pcEdit/article-edit.vue';
+const articleEditRef = shallowRef(articleEdit);
+//品牌组件
+import brand from '@/views/diy/pcPages/brand.vue';
+const brandeRef = shallowRef(brand);
+import brandEdit from '@/views/diy/pcEdit/brand-edit.vue';
+const brandEditRef = shallowRef(brandEdit);
+//图文广告
+import advert from '@/views/diy/pcPages/advert.vue';
+const advertRef = shallowRef(advert);
+import advertEdit from '@/views/diy/pcEdit/advert-edit.vue';
+const advertEditRef = shallowRef(advertEdit);
+//商品组件
+import goods from '@/views/diy/pcPages/goods.vue';
+const goodsRef = shallowRef(goods);
+import goodsEdit from '@/views/diy/pcEdit/goods-edit.vue';
+const goodsEditRef = shallowRef(goodsEdit);
+
+const itemKey = ref<any>(0);
+//左边得组件
+const uniqueIdCounter = ref<any>(0);
+const activeNames = ref<any>([0]);
+const collapse = ref<any>([
+  {
+    name: '基础组件',
+    list: [
+      {
+        name: '头部组件',
+        icon: 'iconfont iconfuwenbenpc',
+        id: 1,
+        components: markRaw(headRef.value),
+        edit: markRaw(headEditRef.value)
+      },
+      {
+        name: '文本标题',
+        icon: 'iconfont iconbiaotipc',
+        id: 2,
+        components: markRaw(ctextTitleRef.value),
+        edit: markRaw(textTitleEditRef.value)
+      },
+      {
+        name: '图文导航',
+        icon: 'iconfont icontuwendaohangpc',
+        id: 3,
+        components: markRaw(navigationRef.value),
+        edit: markRaw(navigationEditRef.value)
+      },
+      {
+        name: '图片魔方',
+        icon: 'iconfont iconmofangpc',
+        id: 4,
+        components: markRaw(imageCubeRef.value),
+        edit: markRaw(imageCubeEditRef.value)
+      },
+      {
+        name: '活动魔方',
+        icon: 'iconfont iconmofangpc',
+        id: 5
+      },
+      {
+        name: '轮播图',
+        icon: 'iconfont icona-tupianzhanbopc302',
+        id: 6,
+        components: markRaw(carouselRef.value),
+        edit: markRaw(carouselEditRef.value)
+      },
+      {
+        name: '文章咨询',
+        icon: 'iconfont icongonggaopc',
+        id: 7,
+        components: markRaw(articleRef.value),
+        edit: markRaw(articleEditRef.value)
+      },
+      {
+        name: '品牌组件',
+        icon: 'iconfont iconmiaoshashangpin',
+        id: 8,
+        components: markRaw(brandeRef.value),
+        edit: markRaw(brandEditRef.value)
+      },
+      {
+        name: '图文广告',
+        icon: 'iconfont icontupiandaohangpc',
+        id: 9,
+        components: markRaw(advertRef.value),
+        edit: markRaw(advertEditRef.value)
+      },
+      {
+        name: '楼层组件',
+        icon: 'iconfont iconshangpinliebiaopc',
+        id: 10
+      },
+      {
+        name: '商品组件',
+        icon: 'iconfont icona-shangpintuijianpc30',
+        id: 11,
+        components: markRaw(goodsRef.value),
+        edit: markRaw(goodsEditRef.value)
+      },
+      {
+        name: '多商品组',
+        icon: 'iconfont iconduoshangpinzupc',
+        id: 12
+      },
+      {
+        name: '发现组件',
+        icon: 'iconfont iconrequpc',
+        id: 13
+      }
+    ]
+  }
+]);
+
+const componentList = ref<any>([
+  // {
+  //   components: ctextTitleRef
+  // }
+]);
+const handleChange = (val: string[]) => {};
+// 返回上一页
+const goBack = () => {};
+
+// 预览
+const preview = () => {};
+
+// 保存
+const save = () => {
+  const datas = {
+    name: '首页',
+    siteId: '',
+    clientId: '',
+    type: 1,
+    remark: '',
+    previewPicUrls: '',
+    property: JSON.stringify(diyStore.componentList),
+    isHome: 1
+  };
+  const api = pcAddDiy;
+  api(datas)
+    .then((res: any) => {
+      if (res.code == 200) {
+      }
+    })
+    .catch(() => {});
+  console.log(diyStore.componentList, '?????????????');
+};
+</script>
+
+<style lang="scss" scoped>
+.pcEdit {
+  width: 100%;
+  overflow: auto;
+}
+.pcEdit-pages {
+  min-height: calc(100vh - 84px);
+  min-width: 1900px;
+
+  .full-container {
+    height: calc(100vh - 134px);
+    background-color: #f2f2f2;
+
+    .component-list {
+      height: 100%;
+      background-color: #ffffff;
+      padding: 0 10px;
+    }
+
+    .component-list ul li {
+      &:not(.disabled):hover {
+        color: var(--el-color-primary);
+        background: var(--el-color-primary-light-9);
+      }
+    }
+
+    .preview-wrap {
+      flex: 1;
+
+      .preview-pages {
+        margin: 30px auto;
+        width: 1300px;
+        height: calc(100vh - 194px);
+        background: var(--el-bg-color-page);
+        overflow: auto;
+      }
+
+      .component-bos {
+        position: relative;
+        .component-box {
+          position: absolute;
+          width: 100%;
+          height: 100%;
+          top: 0;
+          left: 0;
+          border: 2px solid var(--el-color-primary);
+          z-index: 2;
+          cursor: move;
+        }
+      }
+    }
+
+    //编辑组件属性区域
+    .edit-attribute-wrap {
+      background: var(--el-bg-color);
+    }
+
+    .edit-attribute-wrap .box-card {
+      border: none;
+    }
+
+    .edit-attr-item-wrap {
+      border-top: 2px solid var(--el-color-info-light-8);
+      padding-top: 20px;
+
+      &:first-of-type {
+        border-top: none;
+        padding-top: 0;
+      }
+    }
+  }
+
+  :deep(.el-header) {
+    height: 50px;
+  }
+}
+</style>

+ 226 - 0
src/views/diy/pcEdit/advert-edit.vue

@@ -0,0 +1,226 @@
+<template>
+  <div class="pc-edit">
+    <div class="content-wrap" v-show="diyStore.editTab == 'content'">
+      <div class="edit-attr-item-wrap">
+        <h3 class="mb-[10px]">导航模式</h3>
+        <el-form label-width="80px" class="px-[10px]">
+          <el-form-item label="展示风格">
+            <el-radio-group v-model="diyStore.editComponent.styleType">
+              <el-radio :value="1">固定显示</el-radio>
+              <el-radio :value="2">单行滑动</el-radio>
+              <el-radio :value="3">分页滑动</el-radio>
+            </el-radio-group>
+          </el-form-item>
+          <el-form-item label="每行数量">
+            <el-radio-group v-model="diyStore.editComponent.number">
+              <el-radio :value="3">3个</el-radio>
+              <el-radio :value="4">4个</el-radio>
+              <el-radio :value="5">5个</el-radio>
+            </el-radio-group>
+          </el-form-item>
+          <el-form-item label="每页行数" v-if="diyStore.editComponent.styleType == 3">
+            <el-radio-group v-model="diyStore.editComponent.count">
+              <el-radio :value="1">1个</el-radio>
+              <el-radio :value="2">2个</el-radio>
+            </el-radio-group>
+          </el-form-item>
+        </el-form>
+      </div>
+      <div class="edit-attr-item-wrap">
+        <div class="edit-attr-title flex-row-between">
+          <div>
+            <span>导航设置</span>
+            <span class="title2">鼠标拖拽可以改变顺序</span>
+          </div>
+        </div>
+        <el-form label-width="86px" class="px-[10px]">
+          <draggable v-model="diyStore.editComponent.navlList" item-key="id">
+            <template #item="{ element, index }">
+              <div class="edit-attr-box">
+                <el-icon @click="onDel(index)" color="#F56C6C" size="18px" class="circleClose">
+                  <CircleCloseFilled />
+                </el-icon>
+                <el-form-item label="图片上传">
+                  <div class="flex-row-start">
+                    <upload-image v-model="element.imageUrl" :limit="1" />
+                    <div class="flex-column-between images-bos">
+                      <div class="annotation3">(建议上传尺寸相同图片,推荐尺寸150*150)</div>
+                      <div class="flex-row-between images-box">
+                        <div>缩放模式</div>
+                        <div class="flex-row-start" @click="openImageType(element, index)">
+                          <span style="margin-top: 2px" class="text-primary flex-1 cursor-pointer">{{
+                            element.imgType == 1 ? '拉伸' : element.imgType == 2 ? '缩放' : '填充'
+                          }}</span>
+                          <el-icon class="cursor-pointer">
+                            <ArrowRight />
+                          </el-icon>
+                        </div>
+                      </div>
+                    </div>
+                  </div>
+                </el-form-item>
+                <el-form-item label="标题名称">
+                  <el-input v-model="element.title" placeholder="请输入标签名称" :maxlength="10" show-word-limit />
+                </el-form-item>
+                <el-form-item label="副标题名称">
+                  <el-input v-model="element.subtitle" placeholder="请输入标签名称" />
+                </el-form-item>
+                <el-form-item label="链接地址">
+                  <el-input v-model="element.url" placeholder="请输入链接地址" />
+                </el-form-item>
+              </div>
+            </template>
+          </draggable>
+          <el-button @click="onAdd" style="width: 100%; margin-top: 10px">新增导航</el-button>
+        </el-form>
+      </div>
+    </div>
+    <!-- 样式 -->
+    <div class="style-wrap" v-show="diyStore.editTab == 'style'">
+      <div class="edit-attr-item-wrap">
+        <h3 class="mb-[10px]">样式设置</h3>
+        <el-form label-width="80px" class="px-[10px]">
+          <el-form-item label="文字大小">
+            <el-slider size="small" v-model="diyStore.editComponent.titleSize" show-input :min="1" :max="50" />
+          </el-form-item>
+          <el-form-item label="文字加粗">
+            <el-radio-group size="small" v-model="diyStore.editComponent.titleWeight" fill="#409eff">
+              <el-radio-button label="加粗" :value="'bold'" />
+              <el-radio-button label="不加粗" :value="'normal'" />
+            </el-radio-group>
+          </el-form-item>
+          <el-form-item label="文字颜色">
+            <span class="mr-[10px]">{{ diyStore.editComponent.titleColor }}</span>
+            <el-color-picker class="mr-[10px]" v-model="diyStore.editComponent.titleColor" />
+            <el-button @click="diyStore.editComponent.titleColor = '#101828'" size="small">重置</el-button>
+          </el-form-item>
+        </el-form>
+      </div>
+      <div class="edit-attr-item-wrap">
+        <h3 class="mb-[10px]">副标题样式</h3>
+        <el-form label-width="80px" class="px-[10px]">
+          <el-form-item label="文字大小">
+            <el-slider size="small" v-model="diyStore.editComponent.subtitleSize" show-input :min="1" :max="50" />
+          </el-form-item>
+          <el-form-item label="文字颜色">
+            <span class="mr-[10px]">{{ diyStore.editComponent.subtitleColor }}</span>
+            <el-color-picker class="mr-[10px]" v-model="diyStore.editComponent.subtitleColor" />
+            <el-button @click="diyStore.editComponent.subtitleColor = '#101828'" size="small">重置</el-button>
+          </el-form-item>
+        </el-form>
+      </div>
+      <div class="edit-attr-item-wrap">
+        <h3 class="mb-[10px]">图片设置</h3>
+        <el-form label-width="80px" class="px-[10px]">
+          <el-form-item label="图片圆角">
+            <el-slider size="small" v-model="diyStore.editComponent.imageRadius" show-input :min="1" :max="50" />
+          </el-form-item>
+        </el-form>
+      </div>
+      <!-- 组件样式 -->
+      <slot name="style"></slot>
+    </div>
+    <ImagesForm ref="ImagesFormRef" @confirmCallBack="confirmCallBack"></ImagesForm>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import draggable from 'vuedraggable';
+import usePcdiyStore from '@/store/modules/pcdiy';
+import uploadImage from '@/components/upload-image/index.vue';
+import ImagesForm from '@/components/ImagesForm/index.vue';
+const diyStore = usePcdiyStore();
+const ImagesFormRef = ref();
+
+// 打开图片类型
+const openImageType = (element: any, type: any) => {
+  const datas = JSON.parse(JSON.stringify(element));
+  ImagesFormRef.value.onOpen(datas, type);
+};
+//图片类型返回
+const confirmCallBack = (res: any, index: any) => {
+  diyStore.editComponent.navlList[index].imgType = res.imgType;
+};
+const onAdd = () => {
+  diyStore.editComponent.navlList.push({
+    imageUrl: '',
+    title: '标题名称',
+    subtitle: '副标题名称',
+    url: '',
+    imgType: 1,
+    id: Date.now()
+  });
+};
+
+const onDel = (index: any) => {
+  diyStore.editComponent.navlList.splice(index, 1);
+};
+</script>
+
+<style lang="scss" scoped>
+.pc-edit {
+  .edit-attr-item-wrap {
+    border-top: 2px solid var(--el-color-info-light-8);
+    padding-top: 20px;
+
+    &:first-of-type {
+      border-top: none;
+      padding-top: 0;
+    }
+
+    .edit-attr-title {
+      display: flex;
+
+      .title2 {
+        font-size: 12px;
+        color: #666;
+        margin-left: 6px;
+      }
+    }
+
+    .edit-attr-box {
+      padding: 18px 10px 0 10px;
+      border: 1px solid #e5e6eb;
+      border-radius: 4px;
+      position: relative;
+      margin-top: 18px;
+
+      .images-bos {
+        flex: 1;
+        height: 98px;
+        padding: 5px 0;
+      }
+
+      .images-box {
+        font-size: 13px;
+        color: #666;
+      }
+
+      .circleClose {
+        position: absolute;
+        top: -9px;
+        right: -9px;
+        cursor: pointer;
+      }
+    }
+  }
+
+  .annotation3 {
+    font-size: 12px;
+    color: #666;
+    line-height: 14px;
+  }
+
+  :deep(.el-form-item__label) {
+    font-weight: 400;
+  }
+
+  :deep(.file-selector) {
+    display: none;
+  }
+
+  // :deep(.el-radio){
+  //   margin-right: 10px;
+  // }
+}
+</style>

+ 248 - 0
src/views/diy/pcEdit/article-edit.vue

@@ -0,0 +1,248 @@
+<template>
+  <div class="pc-edit">
+    <!-- 内容 -->
+    <div class="content-wrap" v-show="diyStore.editTab == 'content'">
+      <div class="edit-attr-item-wrap">
+        <h3 class="mb-[10px]">文章数据</h3>
+        <el-form label-width="90px" class="px-[10px]">
+          <el-form-item label="数据来源">
+            <el-radio-group v-model="diyStore.editComponent.dataType">
+              <el-radio :value="1">默认</el-radio>
+              <el-radio :value="2">手动</el-radio>
+            </el-radio-group>
+          </el-form-item>
+          <el-form-item label="文章数量" v-if="diyStore.editComponent.dataType == 1">
+            <el-slider size="small" v-model="diyStore.editComponent.dataNumber" show-input :min="1" :max="16" />
+          </el-form-item>
+          <el-form-item label="手动选择" v-else>
+            <div class="data-num" @click="showStyle">
+              <span v-if="diyStore.editComponent.dataIds.length == 0">请选择</span>
+              <span v-else>已选择{{ diyStore.editComponent.dataIds.length }}个</span>
+              <el-icon><ArrowRight /></el-icon>
+            </div>
+          </el-form-item>
+        </el-form>
+      </div>
+    </div>
+    <!-- 样式 -->
+    <div class="style-wrap" v-show="diyStore.editTab == 'style'">
+      <div class="edit-attr-item-wrap">
+        <h3 class="mb-[10px]">样式设置</h3>
+        <el-form label-width="80px" class="px-[10px]">
+          <el-form-item label="边框">
+            <el-radio-group v-model="diyStore.editComponent.border">
+              <el-radio :value="1">默认</el-radio>
+              <el-radio :value="2">投影</el-radio>
+              <el-radio :value="3">描边</el-radio>
+            </el-radio-group>
+          </el-form-item>
+          <el-form-item label="边框颜色" v-if="diyStore.editComponent.border != 1">
+            <span class="mr-[10px]">{{ diyStore.editComponent.borderColor }}</span>
+            <el-color-picker class="mr-[10px]" v-model="diyStore.editComponent.borderColor" />
+            <el-button @click="diyStore.editComponent.borderColor = '#ffffff'" size="small">重置</el-button>
+          </el-form-item>
+          <el-form-item label="文章背景">
+            <span class="mr-[10px]">{{ diyStore.editComponent.backgroundColor }}</span>
+            <el-color-picker class="mr-[10px]" v-model="diyStore.editComponent.backgroundColor" />
+            <el-button @click="diyStore.editComponent.backgroundColor = '#ffffff'" size="small">重置</el-button>
+          </el-form-item>
+          <el-form-item label="上圆角">
+            <el-slider size="small" v-model="diyStore.editComponent.boxTopRounded" show-input :min="1" :max="50" />
+          </el-form-item>
+          <el-form-item label="下圆角">
+            <el-slider size="small" v-model="diyStore.editComponent.boxBottomRounded" show-input :min="1" :max="50" />
+          </el-form-item>
+        </el-form>
+      </div>
+      <!-- 组件样式 -->
+      <slot name="style"></slot>
+    </div>
+    <!-- 手动选择 -->
+    <el-dialog v-model="showDialog" title="选择文章">
+      <div class="data-bos">
+        <el-input
+          v-model="queryParams.caseTitle"
+          placeholder="请输入服务标题"
+          clearable
+          style="width: 300px; margin-bottom: 10px"
+          @keyup.enter="handleQuery"
+        >
+          <template #append>
+            <el-button :icon="Search" />
+          </template>
+        </el-input>
+        <el-table ref="multipleTableRef" v-loading="loading" :data="tableData" border @selection-change="handleSelectionChange">
+          <el-table-column type="selection" width="55" />
+          <el-table-column label="封面图片" align="center" width="120">
+            <template #default="{ row }">
+              <el-image
+                :src="row.caseImage"
+                fit="cover"
+                style="width: 80px; height: 60px; border-radius: 4px"
+                :preview-src-list="[row.caseImage]"
+                preview-teleported
+                lazy
+              >
+                <template #error>
+                  <div class="image-placeholder">
+                    <el-icon><Picture /></el-icon>
+                  </div>
+                </template>
+              </el-image>
+            </template>
+          </el-table-column>
+          <el-table-column label="标题" prop="caseTitle" align="center" min-width="200" show-overflow-tooltip />
+          <el-table-column label="分类" prop="projectTypeName" align="center" width="120" />
+        </el-table>
+        <pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList">
+          <template #slotDiv>
+            <div class="selected">已选择 {{ multipleSelection.length }} 个</div>
+          </template>
+        </pagination>
+      </div>
+
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="showDialog = false">取消</el-button>
+          <el-button type="primary" @click="onConfirm">确认</el-button>
+        </span>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { listServiceCase } from '@/api/product/serviceCase';
+import usePcdiyStore from '@/store/modules/pcdiy';
+import { Search } from '@element-plus/icons-vue';
+import type { TableInstance } from 'element-plus';
+const diyStore = usePcdiyStore();
+
+const multipleTableRef = ref<TableInstance>();
+const showDialog = ref(false);
+const loading = ref(false);
+const tableData = ref<any[]>([]);
+const multipleSelection: any = ref([]); // 选中数据
+const total = ref(0);
+const queryParams = reactive({
+  pageNum: 1,
+  pageSize: 10,
+  caseTitle: '',
+  projectTypeId: undefined as number | undefined
+});
+const resultList = ref<any>([]); //单页之前被选中的数据
+
+/** 搜索 */
+const handleQuery = () => {
+  queryParams.pageNum = 1;
+  getList();
+};
+
+/** 获取列表 */
+const getList = async () => {
+  loading.value = true;
+  try {
+    const res = await listServiceCase(queryParams);
+    tableData.value = res.rows || [];
+    const result = tableData.value.filter((item: any) => diyStore.editComponent.dataIds.includes(item.id));
+    resultList.value = result;
+    nextTick(() => {
+      result.forEach((item: any) => {
+        multipleTableRef.value?.toggleRowSelection(item, true);
+      });
+    });
+
+    console.log('result', result);
+    total.value = res.total || 0;
+  } finally {
+    loading.value = false;
+  }
+};
+
+//打开弹窗
+const showStyle = () => {
+  showDialog.value = true;
+  getList();
+};
+
+// 监听表格单行选中
+const handleSelectionChange = (val: []) => {
+  multipleSelection.value = val;
+};
+//确定
+const onConfirm = () => {
+  const newIds = calculateNewIds(diyStore.editComponent.dataIds, tableData.value, multipleSelection.value);
+  diyStore.editComponent.dataIds = newIds;
+  showDialog.value = false;
+};
+const calculateNewIds = (cacheIds: any, allPageItems: any, selectedItems: any) => {
+  // 1. 获取当前页所有存在的 ID 集合 (用于识别哪些旧数据属于当前页)
+  const currentPageIdSet = new Set(allPageItems.map((item) => item.id));
+  // 2. 获取最终选中项的 ID 集合
+  const selectedIdSet = new Set(selectedItems.map((item) => item.id));
+  // 3. 过滤旧的缓存 IDs
+  const retainedOldIds = cacheIds.filter((id) => {
+    // 情况 A: 该 ID 不在当前页数据中 (说明是其他页的数据,必须无条件保留)
+    if (!currentPageIdSet.has(id)) {
+      return true;
+    }
+    // 情况 B: 该 ID 在当前页数据中,且也在最终选中列表中 (说明用户保持了选中)
+    if (selectedIdSet.has(id)) {
+      return true;
+    }
+    // 情况 C: 该 ID 在当前页数据中,但不在最终选中列表中 (说明用户取消了选中,如 ID 4)
+    // 返回 false,将其剔除
+    return false;
+  });
+  // 4. 合并:保留的旧数据 + 当前页新选中的数据
+  // 使用 Set 去重,虽然逻辑上 retainedOldIds 和 selectedIdSet 不会有交集,但以防万一
+  const newIdsSet = new Set([...retainedOldIds, ...selectedIdSet]);
+  // 转回数组 (如果需要保持原有顺序或特定排序,可以在此处调整)
+  // 这里简单转为数组,通常建议按数字大小排序以便阅读,或者保持插入顺序
+  return Array.from(newIdsSet).sort((a, b) => a - b);
+};
+</script>
+
+<style lang="scss" scoped>
+.pc-edit {
+  .edit-attr-item-wrap {
+    border-top: 2px solid var(--el-color-info-light-8);
+    padding-top: 20px;
+
+    &:first-of-type {
+      border-top: none;
+      padding-top: 0;
+    }
+
+    .edit-attr-title {
+      display: flex;
+
+      .title2 {
+        font-size: 12px;
+        color: #666;
+        margin-left: 6px;
+      }
+    }
+
+    .data-num {
+      width: 100%;
+      font-size: 14px;
+      display: flex;
+      align-items: center;
+      justify-content: flex-end;
+      color: var(--el-color-primary);
+      cursor: pointer;
+    }
+  }
+
+  .selected {
+    line-height: 32px;
+    position: absolute;
+    left: 0px;
+  }
+
+  :deep(.el-form-item__label) {
+    font-weight: 400;
+  }
+}
+</style>

+ 254 - 0
src/views/diy/pcEdit/brand-edit.vue

@@ -0,0 +1,254 @@
+<template>
+  <div class="pc-edit">
+    <!-- 内容 -->
+    <div class="content-wrap" v-show="diyStore.editTab == 'content'">
+      <div class="edit-attr-item-wrap">
+        <h3 class="mb-[10px]">品牌设置</h3>
+        <el-form label-width="80px" class="px-[10px]">
+          <el-form-item label="设置">
+            <el-radio-group v-model="diyStore.editComponent.settings" fill="#409eff">
+              <el-radio-button label="图片" :value="1" />
+              <el-radio-button label="品牌" :value="2" />
+            </el-radio-group>
+          </el-form-item>
+        </el-form>
+      </div>
+      <template v-if="diyStore.editComponent.settings == 1">
+        <div class="edit-attr-item-wrap">
+          <h3 class="mb-[10px]">图片设置</h3>
+          <el-form label-width="80px" class="px-[10px]">
+            <div class="edit-attr-box">
+              <el-form-item label="图片上传">
+                <div class="flex-row-start">
+                  <upload-image v-model="diyStore.editComponent.imageUrl" :limit="1" />
+                  <div class="flex-column-between images-bos">
+                    <div class="annotation3">(建议上传尺寸相同图片,推荐尺寸230*340)</div>
+                    <div class="flex-row-between images-box">
+                      <div>缩放模式</div>
+                      <div class="flex-row-start" @click="openImageType({ imgType: diyStore.editComponent.imgType }, 1)">
+                        <span style="margin-top: 2px" class="text-primary flex-1 cursor-pointer">{{
+                          diyStore.editComponent.imgType == 1 ? '拉伸' : diyStore.editComponent.imgType == 2 ? '缩放' : '填充'
+                        }}</span>
+                        <el-icon class="cursor-pointer">
+                          <ArrowRight />
+                        </el-icon>
+                      </div>
+                    </div>
+                  </div>
+                </div>
+              </el-form-item>
+              <el-form-item label="链接地址">
+                <el-input v-model="diyStore.editComponent.url" placeholder="请输入链接地址" />
+              </el-form-item>
+            </div>
+          </el-form>
+        </div>
+      </template>
+      <template v-if="diyStore.editComponent.settings == 2">
+        <div class="edit-attr-item-wrap">
+          <div class="edit-attr-title flex-row-between">
+            <div>
+              <span>品牌设置</span>
+              <span class="title2">鼠标拖拽可以改变顺序</span>
+            </div>
+          </div>
+          <draggable v-model="diyStore.editComponent.brandList" item-key="id">
+            <template #item="{ element, index }">
+              <el-form label-width="90px" class="px-[10px]">
+                <div class="edit-attr-box">
+                  <el-icon @click="onDel(index)" color="#F56C6C" size="18px" class="circleClose">
+                    <CircleCloseFilled />
+                  </el-icon>
+                  <el-form-item label="图片上传">
+                    <div class="flex-row-start">
+                      <upload-image v-model="element.imageUrl" :limit="1" />
+                      <div class="flex-column-between images-bos">
+                        <div class="annotation3">(建议上传尺寸相同图片,推荐尺寸150*150)</div>
+                        <div class="flex-row-between images-box">
+                          <div>缩放模式</div>
+                          <div class="flex-row-start" @click="openImageType(element, 2)">
+                            <span style="margin-top: 2px" class="text-primary flex-1 cursor-pointer">{{
+                              element.imgType == 1 ? '拉伸' : element.imgType == 2 ? '缩放' : '填充'
+                            }}</span>
+                            <el-icon class="cursor-pointer">
+                              <ArrowRight />
+                            </el-icon>
+                          </div>
+                        </div>
+                      </div>
+                    </div>
+                  </el-form-item>
+                  <el-form-item label="品牌名称">
+                    <el-input v-model="element.title" placeholder="请输入品牌名称" :maxlength="10" show-word-limit />
+                  </el-form-item>
+                  <el-form-item label="副标题名称">
+                    <el-input v-model="element.subtitle" placeholder="请输入副标题内容" />
+                  </el-form-item>
+                  <el-form-item label="链接地址">
+                    <el-input v-model="element.url" placeholder="请输入链接地址" />
+                  </el-form-item>
+                </div>
+              </el-form>
+            </template>
+          </draggable>
+          <el-button @click="onAdd" style="width: 100%; margin-top: 10px">新增品牌</el-button>
+        </div>
+      </template>
+    </div>
+    <!-- 样式 -->
+    <div class="style-wrap" v-show="diyStore.editTab == 'style'">
+      <div class="edit-attr-item-wrap">
+        <h3 class="mb-[10px]">样式设置</h3>
+        <el-form label-width="80px" class="px-[10px]">
+          <el-form-item label="文字大小">
+            <el-slider size="small" v-model="diyStore.editComponent.titleSize" show-input :min="1" :max="50" />
+          </el-form-item>
+          <el-form-item label="文字加粗">
+            <el-radio-group size="small" v-model="diyStore.editComponent.titleWeight" fill="#409eff">
+              <el-radio-button label="加粗" :value="'bold'" />
+              <el-radio-button label="不加粗" :value="'normal'" />
+            </el-radio-group>
+          </el-form-item>
+          <el-form-item label="文字颜色">
+            <span class="mr-[10px]">{{ diyStore.editComponent.titleColor }}</span>
+            <el-color-picker class="mr-[10px]" v-model="diyStore.editComponent.titleColor" />
+            <el-button @click="diyStore.editComponent.titleColor = '#101828'" size="small">重置</el-button>
+          </el-form-item>
+        </el-form>
+      </div>
+      <div class="edit-attr-item-wrap">
+        <h3 class="mb-[10px]">副标题样式</h3>
+        <el-form label-width="80px" class="px-[10px]">
+          <el-form-item label="文字大小">
+            <el-slider size="small" v-model="diyStore.editComponent.subtitleSize" show-input :min="1" :max="50" />
+          </el-form-item>
+          <el-form-item label="文字颜色">
+            <span class="mr-[10px]">{{ diyStore.editComponent.subtitleColor }}</span>
+            <el-color-picker class="mr-[10px]" v-model="diyStore.editComponent.subtitleColor" />
+            <el-button @click="diyStore.editComponent.subtitleColor = '#101828'" size="small">重置</el-button>
+          </el-form-item>
+        </el-form>
+      </div>
+      <div class="edit-attr-item-wrap">
+        <h3 class="mb-[10px]">图片设置</h3>
+        <el-form label-width="80px" class="px-[10px]">
+          <el-form-item label="图片圆角">
+            <el-slider size="small" v-model="diyStore.editComponent.imageRadius" show-input :min="0" :max="100" />
+          </el-form-item>
+          <el-form-item label="模块圆角">
+            <el-slider size="small" v-model="diyStore.editComponent.boxRadius" show-input :min="0" :max="100" />
+          </el-form-item>
+        </el-form>
+      </div>
+      <!-- 组件样式 -->
+      <slot name="style"></slot>
+    </div>
+    <ImagesForm ref="ImagesFormRef" @confirmCallBack="confirmCallBack"></ImagesForm>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import draggable from 'vuedraggable';
+import usePcdiyStore from '@/store/modules/pcdiy';
+import uploadImage from '@/components/upload-image/index.vue';
+import ImagesForm from '@/components/ImagesForm/index.vue';
+const diyStore = usePcdiyStore();
+const ImagesFormRef = ref();
+
+// 打开图片类型
+const openImageType = (element: any, type: any) => {
+  const datas = JSON.parse(JSON.stringify(element));
+  ImagesFormRef.value.onOpen(datas, type);
+};
+// 图片类型返回
+const confirmCallBack = (res: any, type: any) => {
+  if (type == 1) {
+    diyStore.editComponent.imgType = res.imgType;
+  } else {
+    diyStore.editComponent.brandList.forEach((item: any) => {
+      if (item.id == res.id) {
+        item.imgType = res.imgType;
+      }
+    });
+  }
+};
+
+const onAdd = () => {
+  diyStore.editComponent.brandList.push({
+    imageUrl: '',
+    title: '品牌名称',
+    subtitle: '品牌介绍',
+    url: '',
+    imgType: 1,
+    id: Date.now()
+  });
+};
+
+const onDel = (index: any) => {
+  diyStore.editComponent.brandList.splice(index, 1);
+};
+</script>
+
+<style lang="scss" scoped>
+.pc-edit {
+  .edit-attr-item-wrap {
+    border-top: 2px solid var(--el-color-info-light-8);
+    padding-top: 20px;
+
+    &:first-of-type {
+      border-top: none;
+      padding-top: 0;
+    }
+
+    .edit-attr-title {
+      display: flex;
+
+      .title2 {
+        font-size: 12px;
+        color: #666;
+        margin-left: 6px;
+      }
+    }
+
+    .edit-attr-box {
+      padding: 18px 10px 0 10px;
+      border: 1px solid #e5e6eb;
+      border-radius: 4px;
+      position: relative;
+      margin-top: 18px;
+
+      .images-bos {
+        flex: 1;
+        height: 98px;
+        padding: 5px 0;
+      }
+
+      .images-box {
+        font-size: 13px;
+        color: #666;
+      }
+
+      .circleClose {
+        position: absolute;
+        top: -9px;
+        right: -9px;
+        cursor: pointer;
+      }
+
+      .annotation3 {
+        font-size: 12px;
+        color: #666;
+        line-height: 14px;
+      }
+    }
+  }
+
+  :deep(.el-form-item__label) {
+    font-weight: 400;
+  }
+
+  :deep(.file-selector) {
+    display: none;
+  }
+}
+</style>

+ 187 - 0
src/views/diy/pcEdit/carousel-edit.vue

@@ -0,0 +1,187 @@
+<template>
+  <div class="pc-edit">
+    <!-- 内容 -->
+    <div class="content-wrap" v-show="diyStore.editTab == 'content'">
+      <div class="edit-attr-item-wrap">
+        <h3 class="mb-[10px]">魔方设置</h3>
+        <el-form label-width="90px" class="px-[10px]">
+          <el-form-item label="图片高度">
+            <el-input-number v-model="diyStore.editComponent.imageHeight" :min="1" :max="300">
+              <template #suffix>
+                <span>px</span>
+              </template>
+            </el-input-number>
+          </el-form-item>
+        </el-form>
+      </div>
+      <div class="edit-attr-item-wrap">
+        <div class="edit-attr-title flex-row-between">
+          <div>
+            <span>魔方布局</span>
+            <span class="title2">鼠标拖拽可以改变顺序</span>
+          </div>
+        </div>
+        <draggable v-model="diyStore.editComponent.imagelList" item-key="id">
+          <template #item="{ element, index }">
+            <div class="edit-attr-box">
+              <el-icon @click="onDel(index)" color="#F56C6C" size="18px" class="circleClose">
+                <CircleCloseFilled />
+              </el-icon>
+              <el-form-item label="图片上传">
+                <div class="flex-row-start">
+                  <upload-image v-model="element.imageUrl" :limit="1" />
+                  <div class="flex-column-between images-bos">
+                    <div class="annotation3">
+                      (建议上传尺寸相同图片,推荐尺寸{{ Math.floor(1200 / diyStore.editComponent.number) }}*{{ diyStore.editComponent.imageHeight }})
+                    </div>
+                    <div class="flex-row-between images-box">
+                      <div>缩放模式</div>
+                      <div class="flex-row-start" @click="openImageType(element, index)">
+                        <span style="margin-top: 2px" class="text-primary flex-1 cursor-pointer">{{
+                          element.imgType == 1 ? '拉伸' : element.imgType == 2 ? '缩放' : '填充'
+                        }}</span>
+                        <el-icon class="cursor-pointer">
+                          <ArrowRight />
+                        </el-icon>
+                      </div>
+                    </div>
+                  </div>
+                </div>
+              </el-form-item>
+            </div>
+          </template>
+        </draggable>
+        <el-button @click="onAdd" style="width: 100%; margin-top: 10px">新增轮播图</el-button>
+      </div>
+    </div>
+    <!-- 样式 -->
+    <div class="style-wrap" v-show="diyStore.editTab == 'style'">
+      <div class="edit-attr-item-wrap">
+        <h3 class="mb-[10px]">图片间隙</h3>
+        <el-form label-width="80px" class="px-[10px]">
+          <el-form-item label="切换间隔ms" label-width="100px">
+            <el-slider size="small" v-model="diyStore.editComponent.interval" show-input :min="1000" :max="5000" />
+          </el-form-item>
+          <el-form-item label="样式">
+            <el-radio-group v-model="diyStore.editComponent.styleType">
+              <el-radio :value="1">直线</el-radio>
+              <el-radio :value="2">圆点</el-radio>
+            </el-radio-group>
+          </el-form-item>
+          <el-form-item label="位置">
+            <el-radio-group v-model="diyStore.editComponent.position">
+              <el-radio :value="1">居左</el-radio>
+              <el-radio :value="2">居中</el-radio>
+              <el-radio :value="3">居右</el-radio>
+            </el-radio-group>
+          </el-form-item>
+        </el-form>
+      </div>
+      <div class="edit-attr-item-wrap">
+        <h3 class="mb-[10px]">图片设置</h3>
+        <el-form label-width="80px" class="px-[10px]">
+          <el-form-item label="图片圆角">
+            <el-slider size="small" v-model="diyStore.editComponent.imageRadius" show-input :min="0" :max="100" />
+          </el-form-item>
+        </el-form>
+      </div>
+      <!-- 组件样式 -->
+      <slot name="style"></slot>
+    </div>
+    <ImagesForm ref="ImagesFormRef" @confirmCallBack="confirmCallBack"></ImagesForm>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import draggable from 'vuedraggable';
+import usePcdiyStore from '@/store/modules/pcdiy';
+import uploadImage from '@/components/upload-image/index.vue';
+import ImagesForm from '@/components/ImagesForm/index.vue';
+const diyStore = usePcdiyStore();
+const ImagesFormRef = ref();
+
+// 打开图片类型
+const openImageType = (element: any, type: any) => {
+  const datas = JSON.parse(JSON.stringify(element));
+  ImagesFormRef.value.onOpen(datas, type);
+};
+//图片类型返回
+const confirmCallBack = (res: any, index: any) => {
+  diyStore.editComponent.imagelList[index].imgType = res.imgType;
+};
+
+const onAdd = () => {
+  diyStore.editComponent.imagelList.push({
+    imageUrl: '',
+    imgType: 1,
+    id: Date.now()
+  });
+};
+
+const onDel = (index: any) => {
+  diyStore.editComponent.imagelList.splice(index, 1);
+};
+</script>
+
+<style lang="scss" scoped>
+.pc-edit {
+  .edit-attr-item-wrap {
+    border-top: 2px solid var(--el-color-info-light-8);
+    padding-top: 20px;
+
+    &:first-of-type {
+      border-top: none;
+      padding-top: 0;
+    }
+
+    .edit-attr-title {
+      display: flex;
+
+      .title2 {
+        font-size: 12px;
+        color: #666;
+        margin-left: 6px;
+      }
+    }
+
+    .edit-attr-box {
+      padding: 18px 10px 0 10px;
+      border: 1px solid #e5e6eb;
+      border-radius: 4px;
+      position: relative;
+      margin-top: 18px;
+
+      .images-bos {
+        flex: 1;
+        height: 98px;
+        padding: 5px 0;
+      }
+
+      .images-box {
+        font-size: 13px;
+        color: #666;
+      }
+
+      .circleClose {
+        position: absolute;
+        top: -9px;
+        right: -9px;
+        cursor: pointer;
+      }
+
+      .annotation3 {
+        font-size: 12px;
+        color: #666;
+        line-height: 14px;
+      }
+    }
+  }
+
+  :deep(.el-form-item__label) {
+    font-weight: 400;
+  }
+  :deep(.file-selector) {
+    display: none;
+  }
+}
+</style>

+ 443 - 0
src/views/diy/pcEdit/goods-edit.vue

@@ -0,0 +1,443 @@
+<template>
+  <div class="pc-edit">
+    <!-- 内容 -->
+    <div class="content-wrap" v-show="diyStore.editTab == 'content'">
+      <div class="edit-attr-item-wrap">
+        <h3 class="mb-[10px]">样式设置</h3>
+        <el-form label-width="90px" class="px-[10px]">
+          <el-form-item label="数据来源">
+            <el-radio-group v-model="diyStore.editComponent.styleType">
+              <el-radio :value="1">固定显示</el-radio>
+              <el-radio :value="2">单行滑动</el-radio>
+            </el-radio-group>
+          </el-form-item>
+        </el-form>
+      </div>
+      <div class="edit-attr-item-wrap">
+        <h3 class="mb-[10px]">购买按钮</h3>
+        <el-form label-width="90px" class="px-[10px]">
+          <el-form-item label="是否显示">
+            <el-switch v-model="diyStore.editComponent.btnShow" />
+          </el-form-item>
+          <el-form-item label="样式">
+            <div class="flex-row-start">
+              <div
+                @click="diyStore.editComponent.btnStyle = 1"
+                class="btnStyle flex-row-center"
+                :class="{ 'btnStyle1': diyStore.editComponent.btnStyle == 1 }"
+              >
+                <div class="btn1">购买</div>
+              </div>
+              <div
+                @click="diyStore.editComponent.btnStyle = 2"
+                class="btnStyle flex-row-center"
+                :class="{ 'btnStyle1': diyStore.editComponent.btnStyle == 2 }"
+              >
+                <div class="btn2 flex-row-center">
+                  <el-icon size="14"><Plus /></el-icon>
+                </div>
+              </div>
+              <div
+                @click="diyStore.editComponent.btnStyle = 3"
+                class="btnStyle flex-row-center"
+                :class="{ 'btnStyle1': diyStore.editComponent.btnStyle == 3 }"
+              >
+                <div class="btn2 flex-row-center">
+                  <icon name="iconfont icongouwuche" size="14px" />
+                </div>
+              </div>
+            </div>
+          </el-form-item>
+          <el-form-item label="按钮文字">
+            <el-input v-model="diyStore.editComponent.btnText" placeholder="请输入按钮文字" :maxlength="4" show-word-limit />
+          </el-form-item>
+        </el-form>
+      </div>
+      <div class="edit-attr-item-wrap">
+        <h3 class="mb-[10px]">商品数据</h3>
+        <el-form label-width="90px" class="px-[10px]">
+          <el-form-item label="显示内容">
+            <el-checkbox-group v-model="diyStore.editComponent.goodsShow">
+              <el-checkbox label="商品名称" :value="1" />
+              <el-checkbox label="销售价格" :value="2" />
+              <el-checkbox label="划线价格" :value="3" />
+            </el-checkbox-group>
+          </el-form-item>
+          <el-form-item label="选择方式">
+            <el-radio-group v-model="diyStore.editComponent.goodsType">
+              <el-radio :value="1">指定商品</el-radio>
+              <el-radio :value="2">商品分类</el-radio>
+              <el-radio :value="3">商品品牌</el-radio>
+            </el-radio-group>
+          </el-form-item>
+          <el-form-item label="指定商品" v-if="diyStore.editComponent.goodsType == 1">
+            <div class="data-num" @click="openDialog">
+              <span v-if="diyStore.editComponent.goodsIds.length == 0">请选择</span>
+              <span v-else>已选择{{ diyStore.editComponent.goodsIds.length }}个</span>
+              <el-icon><ArrowRight /></el-icon>
+            </div>
+          </el-form-item>
+          <el-form-item label="商品分类" v-if="diyStore.editComponent.goodsType == 2">
+            <el-tree-select
+              v-model="diyStore.editComponent.goodsClassify"
+              :data="categoryOptions"
+              :props="treeProps"
+              value-key="id"
+              placeholder="请选择商品分类"
+              clearable
+              check-strictly
+            />
+          </el-form-item>
+          <el-form-item label="商品数量" v-if="diyStore.editComponent.goodsType != 1">
+            <el-input-number v-model="diyStore.editComponent.goodsNumber" :min="0" />
+            <span class="annotation3">(0代表不限)</span>
+          </el-form-item>
+          <el-form-item label="商品排序" v-if="diyStore.editComponent.goodsType != 1">
+            <el-radio-group v-model="diyStore.editComponent.goodsSort" fill="#409eff">
+              <el-radio-button label="综合" :value="1" />
+              <el-radio-button label="销量" :value="2" />
+              <el-radio-button label="价格" :value="3" />
+            </el-radio-group>
+          </el-form-item>
+        </el-form>
+      </div>
+    </div>
+    <!-- 样式 -->
+    <div class="style-wrap" v-show="diyStore.editTab == 'style'">
+      <div class="edit-attr-item-wrap">
+        <h3 class="mb-[10px]">商品样式</h3>
+        <el-form label-width="80px" class="px-[10px]">
+          <el-form-item label="商品背景">
+            <span class="mr-[10px]">{{ diyStore.editComponent.goodsbackgroundColor }}</span>
+            <el-color-picker class="mr-[10px]" v-model="diyStore.editComponent.goodsbackgroundColor" />
+            <el-button @click="diyStore.editComponent.goodsbackgroundColor = '#ffffff'" size="small">重置</el-button>
+          </el-form-item>
+          <el-form-item label="商品名称">
+            <el-radio-group v-model="diyStore.editComponent.goodsTitleType">
+              <el-radio :value="1">加粗</el-radio>
+              <el-radio :value="2">单行</el-radio>
+              <el-radio :value="3">多行</el-radio>
+            </el-radio-group>
+          </el-form-item>
+          <el-form-item label="名称颜色">
+            <span class="mr-[10px]">{{ diyStore.editComponent.goodsTitleColor }}</span>
+            <el-color-picker class="mr-[10px]" v-model="diyStore.editComponent.goodsTitleColor" />
+            <el-button @click="diyStore.editComponent.goodsTitleColor = '#101828'" size="small">重置</el-button>
+          </el-form-item>
+          <el-form-item label="图片圆角">
+            <el-slider size="small" v-model="diyStore.editComponent.imageRadius" show-input :min="1" :max="50" />
+          </el-form-item>
+          <el-form-item label="销售价">
+            <span class="mr-[10px]">{{ diyStore.editComponent.priceColor }}</span>
+            <el-color-picker class="mr-[10px]" v-model="diyStore.editComponent.priceColor" />
+            <el-button @click="diyStore.editComponent.priceColor = '#E7000B'" size="small">重置</el-button>
+          </el-form-item>
+          <el-form-item label="上圆角">
+            <el-slider size="small" v-model="diyStore.editComponent.goodstopRounded" show-input :min="1" :max="50" />
+          </el-form-item>
+          <el-form-item label="下圆角">
+            <el-slider size="small" v-model="diyStore.editComponent.goodsbottomRounded" show-input :min="1" :max="50" />
+          </el-form-item>
+        </el-form>
+      </div>
+      <div class="edit-attr-item-wrap">
+        <h3 class="mb-[10px]">商品样式</h3>
+        <el-form label-width="80px" class="px-[10px]">
+          <el-form-item label="购买按钮">
+            <span class="mr-[10px]">{{ diyStore.editComponent.btnColor }}</span>
+            <el-color-picker class="mr-[10px]" v-model="diyStore.editComponent.btnColor" />
+            <el-button @click="diyStore.editComponent.btnColor = '#ffffff'" size="small">重置</el-button>
+          </el-form-item>
+          <el-form-item label="背景颜色">
+            <span class="mr-[10px]">{{ diyStore.editComponent.btnbackgroundColor }}</span>
+            <el-color-picker class="mr-[10px]" v-model="diyStore.editComponent.btnbackgroundColor" />
+            <el-button @click="diyStore.editComponent.btnbackgroundColor = '#E7000B'" size="small">重置</el-button>
+          </el-form-item>
+        </el-form>
+      </div>
+      <!-- 组件样式 -->
+      <slot name="style"></slot>
+    </div>
+    <!-- 手动选择 -->
+    <el-dialog v-model="showDialog" title="选择商品" width="1400">
+      <div class="dialog-bos">
+        <el-input v-model="queryParams.itemName" placeholder="请输入商品名称" clearable style="width: 300px; margin-bottom: 10px">
+          <template #append>
+            <el-button :icon="Search" @click="handleQuery" />
+          </template>
+        </el-input>
+        <div class="flex">
+          <div class="tree-bos">
+            <el-tree :data="categoryOptions" :props="defaultProps" @node-click="handleNodeClick" :highlight-current="true" />
+          </div>
+          <el-table ref="multipleTableRef" v-loading="loading" :data="tableData" border @selection-change="handleSelectionChange">
+            <el-table-column type="selection" width="55" />
+            <el-table-column label="商品图片" align="center" prop="productImage" width="100">
+              <template #default="scope">
+                <image-preview :src="scope.row.productImage" :width="60" :height="60" />
+              </template>
+            </el-table-column>
+            <el-table-column label="商品信息" align="center" minWidth="250" show-overflow-tooltip>
+              <template #default="scope">
+                <div class="text-left">
+                  <div>{{ scope.row.itemName }}</div>
+                  <div class="text-gray-500" style="font-size: 12px">品牌: {{ scope.row.brandName || '-' }}</div>
+                </div>
+              </template>
+            </el-table-column>
+            <el-table-column label="SKU价格" align="center" width="180">
+              <template #default="scope">
+                <div class="text-left" style="font-size: 12px">
+                  <div>
+                    <span class="text-gray-500">市场价:</span>
+                    <span class="text-red-500">¥{{ scope.row.marketPrice || '0.00' }}</span>
+                  </div>
+                  <div>
+                    <span class="text-gray-500">会员价:</span>
+                    <span class="text-red-500">¥{{ scope.row.memberPrice || '0.00' }}</span>
+                  </div>
+                  <div>
+                    <span class="text-gray-500">最低价:</span>
+                    <span class="text-red-500">¥{{ scope.row.minSellingPrice || '0.00' }}</span>
+                  </div>
+                </div>
+              </template>
+            </el-table-column>
+            <el-table-column label="成本情况" align="center" width="150">
+              <template #default="scope">
+                <div class="text-left" style="font-size: 12px">
+                  <div>
+                    <span class="text-gray-500">采购价:</span>
+                    <span>¥{{ scope.row.purchasingPrice || '0.00' }}</span>
+                  </div>
+                  <div>
+                    <span class="text-gray-500">暂估毛利率:</span>
+                    <span>{{ scope.row.tempGrossMargin || '0.0000' }}%</span>
+                  </div>
+                </div>
+              </template>
+            </el-table-column>
+          </el-table>
+        </div>
+        <pagination v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList">
+          <template #slotDiv>
+            <div class="selected">已选择 {{ multipleSelection.length }} 个</div>
+          </template>
+        </pagination>
+      </div>
+
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="showDialog = false">取消</el-button>
+          <el-button type="primary" @click="onConfirm">确认</el-button>
+        </span>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { categoryTree, listBase } from '@/api/pmsProduct/base';
+import usePcdiyStore from '@/store/modules/pcdiy';
+import { Search } from '@element-plus/icons-vue';
+import type { TableInstance } from 'element-plus';
+const diyStore = usePcdiyStore();
+
+const multipleTableRef = ref<TableInstance>();
+const showDialog = ref(false);
+const loading = ref(false);
+const tableData = ref<any[]>([]);
+const multipleSelection: any = ref([]); // 选中数据
+const total = ref(0);
+const queryParams = reactive({
+  pageNum: 1,
+  pageSize: 10,
+  itemName: '',
+  bottomCategoryId: ''
+});
+const resultList = ref<any>([]); //单页之前被选中的数据
+const categoryOptions = ref<any>([]);
+const defaultProps = {
+  children: 'children',
+  label: 'label'
+};
+const treeProps = {
+  value: 'id',
+  label: 'label',
+  children: 'children'
+};
+
+onMounted(() => {
+  getCategoryTree();
+});
+
+/** 搜索 */
+const handleQuery = () => {
+  queryParams.pageNum = 1;
+  getList();
+};
+
+/** 获取列表 */
+const getList = async () => {
+  loading.value = true;
+  try {
+    const res = await listBase(queryParams);
+    tableData.value = res.rows || [];
+    const result = tableData.value.filter((item: any) => diyStore.editComponent.goodsIds.includes(item.id));
+    resultList.value = result;
+    nextTick(() => {
+      result.forEach((item: any) => {
+        multipleTableRef.value?.toggleRowSelection(item, true);
+      });
+    });
+    total.value = res.total || 0;
+  } finally {
+    loading.value = false;
+  }
+};
+
+/** 查询分类树 */
+const getCategoryTree = async () => {
+  const res = await categoryTree();
+  categoryOptions.value = res.data || [];
+  categoryOptions.value.unshift({
+    id: '',
+    label: '全部'
+  });
+};
+
+//打开弹窗
+const openDialog = () => {
+  showDialog.value = true;
+  getList();
+};
+
+const handleNodeClick = (data: any) => {
+  queryParams.bottomCategoryId = data.id;
+  handleQuery();
+};
+// 监听表格单行选中
+const handleSelectionChange = (val: []) => {
+  multipleSelection.value = val;
+};
+//确定
+const onConfirm = () => {
+  const newIds = calculateNewIds(diyStore.editComponent.goodsIds, tableData.value, multipleSelection.value);
+  diyStore.editComponent.goodsIds = newIds;
+  showDialog.value = false;
+};
+const calculateNewIds = (cacheIds: any, allPageItems: any, selectedItems: any) => {
+  // 1. 获取当前页所有存在的 ID 集合 (用于识别哪些旧数据属于当前页)
+  const currentPageIdSet = new Set(allPageItems.map((item) => item.id));
+  // 2. 获取最终选中项的 ID 集合
+  const selectedIdSet = new Set(selectedItems.map((item) => item.id));
+  // 3. 过滤旧的缓存 IDs
+  const retainedOldIds = cacheIds.filter((id) => {
+    // 情况 A: 该 ID 不在当前页数据中 (说明是其他页的数据,必须无条件保留)
+    if (!currentPageIdSet.has(id)) {
+      return true;
+    }
+    // 情况 B: 该 ID 在当前页数据中,且也在最终选中列表中 (说明用户保持了选中)
+    if (selectedIdSet.has(id)) {
+      return true;
+    }
+    // 情况 C: 该 ID 在当前页数据中,但不在最终选中列表中 (说明用户取消了选中,如 ID 4)
+    // 返回 false,将其剔除
+    return false;
+  });
+  // 4. 合并:保留的旧数据 + 当前页新选中的数据
+  // 使用 Set 去重,虽然逻辑上 retainedOldIds 和 selectedIdSet 不会有交集,但以防万一
+  const newIdsSet = new Set([...retainedOldIds, ...selectedIdSet]);
+  // 转回数组 (如果需要保持原有顺序或特定排序,可以在此处调整)
+  // 这里简单转为数组,通常建议按数字大小排序以便阅读,或者保持插入顺序
+  return Array.from(newIdsSet).sort((a, b) => a - b);
+};
+</script>
+
+<style lang="scss" scoped>
+.pc-edit {
+  .edit-attr-item-wrap {
+    border-top: 2px solid var(--el-color-info-light-8);
+    padding-top: 20px;
+
+    &:first-of-type {
+      border-top: none;
+      padding-top: 0;
+    }
+
+    .edit-attr-title {
+      display: flex;
+
+      .title2 {
+        font-size: 12px;
+        color: #666;
+        margin-left: 6px;
+      }
+    }
+
+    .btnStyle {
+      min-width: 60px;
+      padding: 5px;
+      cursor: pointer;
+      border-radius: 4px;
+      margin-right: 4px;
+
+      &.btnStyle1 {
+        border: 1px solid var(--el-color-primary);
+      }
+
+      .btn1 {
+        background-color: var(--el-color-primary);
+        padding: 5px 15px;
+        border-radius: 15px;
+        font-size: 12px;
+        color: #ffffff;
+        line-height: 1;
+      }
+
+      .btn2 {
+        color: var(--el-color-primary);
+        border: 1px solid var(--el-color-primary);
+        height: 26px;
+        width: 26px;
+        border-radius: 50%;
+      }
+    }
+
+    .data-num {
+      width: 100%;
+      font-size: 14px;
+      display: flex;
+      align-items: center;
+      justify-content: flex-end;
+      color: var(--el-color-primary);
+      cursor: pointer;
+    }
+  }
+
+  .selected {
+    line-height: 32px;
+    position: absolute;
+    left: 0px;
+  }
+
+  :deep(.el-form-item__label) {
+    font-weight: 400;
+  }
+
+  .annotation3 {
+    font-size: 12px;
+    color: #666;
+    line-height: 14px;
+  }
+
+  .dialog-bos {
+    .tree-bos {
+      max-height: 900px;
+      overflow: auto;
+      width: 240px;
+      margin-right: 10px;
+    }
+  }
+}
+</style>

+ 504 - 0
src/views/diy/pcEdit/head-edit.vue

@@ -0,0 +1,504 @@
+<template>
+  <div class="pc-edit">
+    <!-- 内容 -->
+    <div class="content-wrap" v-show="diyStore.editTab == 'content'">
+      <div class="edit-attr-item-wrap">
+        <h3 class="mb-[10px]">头部组件</h3>
+        <el-form label-width="80px" class="px-[10px]">
+          <el-form-item label="设置">
+            <el-radio-group v-model="diyStore.editComponent.settings" fill="#409eff">
+              <el-radio-button label="顶部" :value="1" />
+              <el-radio-button label="左侧" :value="2" />
+              <el-radio-button label="中间" :value="3" />
+              <el-radio-button label="右侧" :value="4" />
+            </el-radio-group>
+          </el-form-item>
+          <!-- <el-form-item label="轮播区域">
+            <el-radio-group v-model="diyStore.editComponent.carousel">
+              <el-radio :value="1">仅轮播图</el-radio>
+              <el-radio :value="2">内嵌广告</el-radio>
+            </el-radio-group>
+          </el-form-item> -->
+        </el-form>
+      </div>
+      <template v-if="diyStore.editComponent.settings == 1">
+        <div class="edit-attr-item-wrap">
+          <h3 class="mb-[10px]">顶部设置</h3>
+          <el-form label-width="80px" class="px-[10px]">
+            <el-form-item label="样式选择">
+              <el-radio-group v-model="diyStore.editComponent.topStyle">
+                <el-radio :value="1">样式1</el-radio>
+                <el-radio :value="2">样式2</el-radio>
+              </el-radio-group>
+            </el-form-item>
+            <el-form-item label="logo">
+              <div>
+                <upload-image v-model="diyStore.editComponent.logo" :limit="1" />
+                <div class="annotation">建议尺寸:宽度{{ diyStore.editComponent.topStyle == 1 ? '185' : '390' }}px*90px</div>
+              </div>
+            </el-form-item>
+            <el-form-item label="二维码">
+              <div>
+                <upload-image v-model="diyStore.editComponent.code" :limit="1" />
+                <div class="annotation">建议尺寸:宽度100px*100px</div>
+              </div>
+            </el-form-item>
+            <el-form-item label="全部商品分类" label-width="120">
+              <el-checkbox v-model="diyStore.editComponent.classifyShow" label="显示" />
+            </el-form-item>
+          </el-form>
+        </div>
+        <div class="edit-attr-item-wrap">
+          <div class="edit-attr-title flex-row-between">
+            <div>
+              <span>导航栏</span>
+              <span class="title2">鼠标拖拽可以改变顺序</span>
+            </div>
+
+            <el-radio-group v-model="diyStore.editComponent.topType" fill="#409eff">
+              <el-radio-button label="顶部导航" :value="1" />
+              <el-radio-button label="标签" :value="2" />
+            </el-radio-group>
+          </div>
+          <el-form label-width="80px" class="px-[10px]">
+            <template v-if="diyStore.editComponent.topType == 1">
+              <draggable v-model="diyStore.editComponent.topNav" item-key="id">
+                <template #item="{ element, index }">
+                  <div class="edit-attr-box">
+                    <el-icon @click="onDel(1, index)" color="#F56C6C" size="18px" class="circleClose">
+                      <CircleCloseFilled />
+                    </el-icon>
+                    <el-form-item label="导航名称">
+                      <el-input v-model="element.title" placeholder="请输入导航名称" />
+                    </el-form-item>
+                    <el-form-item label="链接地址">
+                      <el-input v-model="element.url" placeholder="请输入链接地址" />
+                    </el-form-item>
+                  </div>
+                </template>
+              </draggable>
+              <el-button @click="onAdd(1)" style="width: 100%; margin-top: 10px">新增顶部导航</el-button>
+            </template>
+            <template v-if="diyStore.editComponent.topType == 2">
+              <draggable v-model="diyStore.editComponent.toplabel" item-key="id">
+                <template #item="{ element, index }">
+                  <div class="edit-attr-box">
+                    <el-icon @click="onDel(2, index)" color="#F56C6C" size="18px" class="circleClose">
+                      <CircleCloseFilled />
+                    </el-icon>
+                    <el-form-item label="标签名称">
+                      <el-input v-model="element.title" placeholder="请输入标签名称" />
+                    </el-form-item>
+                    <el-form-item label="链接地址">
+                      <el-input v-model="element.url" placeholder="请输入链接地址" />
+                    </el-form-item>
+                  </div>
+                </template>
+              </draggable>
+              <el-button @click="onAdd(2)" style="width: 100%; margin-top: 10px">新增标签</el-button>
+            </template>
+          </el-form>
+        </div>
+      </template>
+      <template v-if="diyStore.editComponent.settings == 2">
+        <div class="edit-attr-item-wrap">
+          <h3 class="mb-[10px]">左侧导航设置</h3>
+          <el-form label-width="80px" class="px-[10px]">
+            <el-form-item label="样式选择">
+              <el-radio-group v-model="diyStore.editComponent.leftStyle">
+                <el-radio :value="1">样式1</el-radio>
+                <el-radio :value="2">样式2</el-radio>
+              </el-radio-group>
+            </el-form-item>
+          </el-form>
+        </div>
+      </template>
+      <template v-if="diyStore.editComponent.settings == 3">
+        <div class="edit-attr-item-wrap">
+          <h3 class="mb-[10px]">中间轮播图</h3>
+          <el-form label-width="80px" class="px-[10px]">
+            <el-form-item label="背景全屏">
+              <el-checkbox v-model="diyStore.editComponent.carouselStyle" label="开启" />
+              <span class="annotation2">(开启后轮播图背景随轮播图变化)</span>
+            </el-form-item>
+            <el-form-item label="轮播左右键" label-width="120">
+              <el-radio-group v-model="diyStore.editComponent.carouselType">
+                <el-radio :value="1">圆形</el-radio>
+                <el-radio :value="2">方形</el-radio>
+              </el-radio-group>
+            </el-form-item>
+          </el-form>
+        </div>
+        <div class="edit-attr-item-wrap">
+          <div class="edit-attr-title flex-row-between">
+            <div>
+              <span>广告图片</span>
+              <span class="title2">鼠标拖拽可以改变顺序</span>
+            </div>
+            <el-radio-group v-model="diyStore.editComponent.centreType" fill="#409eff">
+              <el-radio-button label="轮播图" :value="1" />
+              <el-radio-button label="内嵌广告" :value="2" />
+            </el-radio-group>
+          </div>
+          <template v-if="diyStore.editComponent.centreType == 1">
+            <draggable v-model="diyStore.editComponent.carouselList" item-key="id">
+              <template #item="{ element, index }">
+                <div class="edit-attr-box">
+                  <el-icon @click="onDel(3, index)" color="#F56C6C" size="18px" class="circleClose">
+                    <CircleCloseFilled />
+                  </el-icon>
+                  <el-form-item label="图片上传">
+                    <div class="flex-row-start">
+                      <upload-image v-model="element.imageUrl" :limit="1" />
+                      <div class="flex-column-between images-bos">
+                        <div class="annotation3">(建议上传尺寸相同图片,推荐尺寸756*{{ diyStore.editComponent.advertNum == 0 ? '540' : '400' }})</div>
+                        <div class="flex-row-between images-box">
+                          <div>缩放模式</div>
+                          <div class="flex-row-start" @click="openImageType(element, 1)">
+                            <span style="margin-top: 2px" class="text-primary flex-1 cursor-pointer">{{
+                              element.imgType == 1 ? '拉伸' : element.imgType == 2 ? '缩放' : '填充'
+                            }}</span>
+                            <el-icon class="cursor-pointer">
+                              <ArrowRight />
+                            </el-icon>
+                          </div>
+                        </div>
+                      </div>
+                    </div>
+                  </el-form-item>
+                  <el-form-item label="链接地址">
+                    <el-input v-model="element.url" placeholder="请输入链接地址" />
+                  </el-form-item>
+                  <el-form-item label="是否显示">
+                    <el-switch v-model="element.show" />
+                  </el-form-item>
+                </div>
+              </template>
+            </draggable>
+            <el-button @click="onAdd(3)" style="width: 100%; margin-top: 10px">新增轮播图</el-button>
+          </template>
+          <template v-if="diyStore.editComponent.centreType == 2">
+            <el-form label-width="80px" class="px-[10px]">
+              <el-form-item label="广告数量" style="margin-top: 10px">
+                <el-radio-group @change="onAdvertNum" v-model="diyStore.editComponent.advertNum">
+                  <el-radio :value="0">0张</el-radio>
+                  <el-radio :value="1">1张</el-radio>
+                  <el-radio :value="2">2张</el-radio>
+                  <el-radio :value="3">3张</el-radio>
+                  <el-radio :value="4">4张</el-radio>
+                </el-radio-group>
+              </el-form-item>
+            </el-form>
+            <draggable v-model="diyStore.editComponent.advertList" item-key="id">
+              <template #item="{ element }">
+                <div class="edit-attr-box" v-show="element.show">
+                  <el-form-item label="图片上传">
+                    <div class="flex-row-start">
+                      <upload-image v-model="element.imageUrl" :limit="1" />
+                      <div class="flex-column-between images-bos">
+                        <div class="annotation3">(建议上传尺寸相同图片,推荐尺寸{{ Math.floor(756 / diyStore.editComponent.advertNum) }}*130)</div>
+                        <div class="flex-row-between images-box">
+                          <div>缩放模式</div>
+                          <div class="flex-row-start" @click="openImageType(element, 2)">
+                            <span style="margin-top: 2px" class="text-primary flex-1 cursor-pointer">{{
+                              element.imgType == 1 ? '拉伸' : element.imgType == 2 ? '缩放' : '填充'
+                            }}</span>
+                            <el-icon class="cursor-pointer">
+                              <ArrowRight />
+                            </el-icon>
+                          </div>
+                        </div>
+                      </div>
+                    </div>
+                  </el-form-item>
+                  <el-form-item label="链接地址">
+                    <el-input v-model="element.url" placeholder="请输入链接地址" />
+                  </el-form-item>
+                </div>
+              </template>
+            </draggable>
+          </template>
+        </div>
+      </template>
+      <template v-if="diyStore.editComponent.settings == 4">
+        <div class="edit-attr-item-wrap">
+          <h3 class="mb-[10px]">右侧设置</h3>
+          <el-form label-width="80px" class="px-[10px]">
+            <el-form-item label="资讯位置">
+              <el-radio-group v-model="diyStore.editComponent.realType">
+                <el-radio :value="1">在上</el-radio>
+                <el-radio :value="2">在下</el-radio>
+              </el-radio-group>
+            </el-form-item>
+            <el-form-item label="数据来源">
+              <el-radio-group v-model="diyStore.editComponent.realDataType">
+                <el-radio :value="1">默认</el-radio>
+                <el-radio :value="2">手动</el-radio>
+              </el-radio-group>
+            </el-form-item>
+            <el-form-item label="文章数量">
+              <el-slider v-model="diyStore.editComponent.realNumber" show-input :min="1" :max="10" />
+            </el-form-item>
+          </el-form>
+        </div>
+        <div class="edit-attr-item-wrap">
+          <div class="edit-attr-title flex-row-between">
+            <div>
+              <span>导航设置</span>
+              <span class="title2">鼠标拖拽可以改变顺序</span>
+            </div>
+          </div>
+          <el-form label-width="80px" class="px-[10px]">
+            <draggable v-model="diyStore.editComponent.navlList" item-key="id">
+              <template #item="{ element, index }">
+                <div class="edit-attr-box">
+                  <el-icon @click="onDel(4, index)" color="#F56C6C" size="18px" class="circleClose">
+                    <CircleCloseFilled />
+                  </el-icon>
+                  <el-form-item label="图片上传">
+                    <div class="flex-row-start">
+                      <upload-image v-model="element.imageUrl" :limit="1" />
+                      <div class="flex-column-between images-bos">
+                        <div class="annotation3">(建议上传尺寸相同图片,推荐尺寸150*150)</div>
+                        <div class="flex-row-between images-box">
+                          <div>缩放模式</div>
+                          <div class="flex-row-start" @click="openImageType(element, 3)">
+                            <span style="margin-top: 2px" class="text-primary flex-1 cursor-pointer">{{
+                              element.imgType == 1 ? '拉伸' : element.imgType == 2 ? '缩放' : '填充'
+                            }}</span>
+                            <el-icon class="cursor-pointer">
+                              <ArrowRight />
+                            </el-icon>
+                          </div>
+                        </div>
+                      </div>
+                    </div>
+                  </el-form-item>
+                  <el-form-item label="标签名称">
+                    <el-input v-model="element.title" placeholder="请输入标签名称" :maxlength="4" show-word-limit />
+                  </el-form-item>
+                  <el-form-item label="链接地址">
+                    <el-input v-model="element.url" placeholder="请输入链接地址" />
+                  </el-form-item>
+                </div>
+              </template>
+            </draggable>
+            <el-button @click="onAdd(4)" style="width: 100%; margin-top: 10px">新增导航</el-button>
+          </el-form>
+        </div>
+      </template>
+    </div>
+    <!-- 样式 -->
+    <div class="style-wrap" v-show="diyStore.editTab == 'style'">
+      <div class="edit-attr-item-wrap">
+        <h3 class="mb-[10px]">轮播区域设置</h3>
+        <el-form label-width="80px" class="px-[10px]">
+          <el-form-item label="切换间隔ms" label-width="100px">
+            <el-slider v-model="diyStore.editComponent.carouselInterval" show-input :min="1000" :max="6000" />
+          </el-form-item>
+          <el-form-item label="图片圆角">
+            <el-slider v-model="diyStore.editComponent.carouselRadius" show-input :min="0" :max="100" />
+          </el-form-item>
+        </el-form>
+      </div>
+      <div class="edit-attr-item-wrap">
+        <h3 class="mb-[10px]">左侧区域设置</h3>
+        <el-form class="px-[10px]">
+          <el-row>
+            <el-col :span="12">
+              <el-form-item label="主文字颜色">
+                <el-color-picker v-model="diyStore.editComponent.leftColor1" />
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="背景颜色">
+                <el-color-picker v-model="diyStore.editComponent.leftBackground" />
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="副文字颜色">
+                <el-color-picker v-model="diyStore.editComponent.leftColor2" />
+              </el-form-item>
+            </el-col>
+          </el-row>
+        </el-form>
+      </div>
+      <div class="edit-attr-item-wrap">
+        <h3 class="mb-[10px]">右侧区域导航</h3>
+        <el-form label-width="80px" class="px-[10px]">
+          <el-form-item label="图片圆角">
+            <el-slider v-model="diyStore.editComponent.rightRadius" show-input :min="0" :max="50" />
+          </el-form-item>
+        </el-form>
+      </div>
+      <!-- 组件样式 -->
+      <slot name="style"></slot>
+    </div>
+    <ImagesForm ref="ImagesFormRef" @confirmCallBack="confirmCallBack"></ImagesForm>
+  </div>
+</template>
+
+<script setup name="Index" lang="ts">
+import draggable from 'vuedraggable';
+import usePcdiyStore from '@/store/modules/pcdiy';
+import uploadImage from '@/components/upload-image/index.vue';
+import ImagesForm from '@/components/ImagesForm/index.vue';
+const diyStore = usePcdiyStore();
+const ImagesFormRef = ref();
+
+const onAdd = (res: any) => {
+  if (res == 1) {
+    //顶部导航
+    diyStore.editComponent.topNav.push({
+      title: '',
+      url: ''
+    });
+  } else if (res == 2) {
+    //标签
+    diyStore.editComponent.toplabel.push({
+      title: '',
+      url: ''
+    });
+  } else if (res == 3) {
+    //轮播图
+    diyStore.editComponent.carouselList.push({
+      imageUrl: '',
+      url: '',
+      imgType: 1,
+      show: true,
+      id: Date.now()
+    });
+  } else if (res == 4) {
+    //导航
+    diyStore.editComponent.navlList.push({
+      imageUrl: '',
+      title: '',
+      url: '',
+      imgType: 1,
+      id: Date.now()
+    });
+  }
+};
+
+const onDel = (res: any, index: any) => {
+  if (res == 1) {
+    //顶部导航
+    diyStore.editComponent.topNav.splice(index, 1);
+  } else if (res == 2) {
+    //标签
+    diyStore.editComponent.toplabel.splice(index, 1);
+  } else if (res == 3) {
+    //轮播图
+    diyStore.editComponent.carouselList.splice(index, 1);
+  } else if (res == 4) {
+    //导航
+    diyStore.editComponent.navlList.splice(index, 1);
+  }
+};
+
+const onAdvertNum = (res: any) => {
+  diyStore.editComponent.advertList.forEach((item: any, index: any) => {
+    item.show = false;
+    if (index < res) {
+      item.show = true;
+    }
+  });
+};
+
+// 打开图片类型
+const openImageType = (element: any, type: any) => {
+  const datas = JSON.parse(JSON.stringify(element));
+  ImagesFormRef.value.onOpen(datas, type);
+};
+
+// 图片类型返回
+const confirmCallBack = (res: any, type: any) => {
+  let list = [];
+  if (type == 1) list = diyStore.editComponent.carouselList;
+  if (type == 2) list = diyStore.editComponent.advertList;
+  if (type == 3) list = diyStore.editComponent.navlList;
+  list.forEach((item: any) => {
+    if (item.id == res.id) {
+      item.imgType = res.imgType;
+    }
+  });
+};
+</script>
+
+<style lang="scss" scoped>
+.pc-edit {
+  .edit-attr-item-wrap {
+    border-top: 2px solid var(--el-color-info-light-8);
+    padding-top: 20px;
+
+    &:first-of-type {
+      border-top: none;
+      padding-top: 0;
+    }
+
+    .edit-attr-title {
+      display: flex;
+
+      .title2 {
+        font-size: 12px;
+        color: #666;
+        margin-left: 6px;
+      }
+    }
+
+    .edit-attr-box {
+      padding: 18px 10px 0 10px;
+      border: 1px solid #e5e6eb;
+      border-radius: 4px;
+      position: relative;
+      margin-top: 18px;
+
+      .images-bos {
+        flex: 1;
+        height: 98px;
+        padding: 5px 0;
+      }
+
+      .images-box {
+        font-size: 13px;
+        color: #666;
+      }
+
+      .circleClose {
+        position: absolute;
+        top: -9px;
+        right: -9px;
+        cursor: pointer;
+      }
+    }
+  }
+
+  .annotation {
+    // position: absolute;
+    // bottom: 0;
+    // left: 0;
+    font-size: 12px;
+    color: #666;
+  }
+
+  .annotation2 {
+    font-size: 12px;
+    color: #666;
+    margin-left: 10px;
+  }
+
+  .annotation3 {
+    font-size: 12px;
+    color: #666;
+    line-height: 14px;
+  }
+
+  :deep(.el-form-item__label) {
+    font-weight: 400;
+  }
+
+  :deep(.file-selector) {
+    display: none;
+  }
+}
+</style>

+ 168 - 0
src/views/diy/pcEdit/imageCube-edit.vue

@@ -0,0 +1,168 @@
+<template>
+  <div class="pc-edit">
+    <!-- 内容 -->
+    <div class="content-wrap" v-show="diyStore.editTab == 'content'">
+      <div class="edit-attr-item-wrap">
+        <h3 class="mb-[10px]">魔方设置</h3>
+        <el-form label-width="90px" class="px-[10px]">
+          <el-form-item label="一行数量">
+            <el-radio-group v-model="diyStore.editComponent.number">
+              <el-radio :value="1">1张</el-radio>
+              <el-radio :value="2">2张</el-radio>
+              <el-radio :value="3">3张</el-radio>
+              <el-radio :value="4">4张</el-radio>
+              <el-radio :value="5">5张</el-radio>
+            </el-radio-group>
+          </el-form-item>
+          <el-form-item label="图片高度">
+            <el-input-number v-model="diyStore.editComponent.imageHeight" :min="1" :max="300">
+              <template #suffix>
+                <span>px</span>
+              </template>
+            </el-input-number>
+          </el-form-item>
+        </el-form>
+      </div>
+      <div class="edit-attr-item-wrap">
+        <div class="edit-attr-title flex-row-between">
+          <div>
+            <span>魔方布局</span>
+            <span class="title2">鼠标拖拽可以改变顺序</span>
+          </div>
+        </div>
+        <draggable v-model="diyStore.editComponent.imagelList" item-key="id">
+          <template #item="{ element, index }">
+            <div class="edit-attr-box" v-show="index < diyStore.editComponent.number">
+              <el-form-item label="图片上传">
+                <div class="flex-row-start">
+                  <upload-image v-model="element.imageUrl" :limit="1" />
+                  <div class="flex-column-between images-bos">
+                    <div class="annotation3">
+                      (建议上传尺寸相同图片,推荐尺寸{{ Math.floor(1200 / diyStore.editComponent.number) }}*{{ diyStore.editComponent.imageHeight }})
+                    </div>
+                    <div class="flex-row-between images-box">
+                      <div>缩放模式</div>
+                      <div class="flex-row-start" @click="openImageType(element, index)">
+                        <span style="margin-top: 2px" class="text-primary flex-1 cursor-pointer">{{
+                          element.imgType == 1 ? '拉伸' : element.imgType == 2 ? '缩放' : '填充'
+                        }}</span>
+                        <el-icon class="cursor-pointer">
+                          <ArrowRight />
+                        </el-icon>
+                      </div>
+                    </div>
+                  </div>
+                </div>
+              </el-form-item>
+              <el-form-item label="链接地址">
+                <el-input v-model="element.url" placeholder="请输入链接地址" />
+              </el-form-item>
+            </div>
+          </template>
+        </draggable>
+      </div>
+    </div>
+    <!-- 样式 -->
+    <div class="style-wrap" v-show="diyStore.editTab == 'style'">
+      <div class="edit-attr-item-wrap">
+        <h3 class="mb-[10px]">图片间隙</h3>
+        <el-form label-width="80px" class="px-[10px]">
+          <el-form-item label="图片间隙">
+            <el-slider size="small" v-model="diyStore.editComponent.gap" show-input :min="1" :max="50" />
+          </el-form-item>
+          <el-form-item label="上圆角">
+            <el-slider size="small" v-model="diyStore.editComponent.imageTopRounded" show-input :min="1" :max="50" />
+          </el-form-item>
+          <el-form-item label="下圆角">
+            <el-slider size="small" v-model="diyStore.editComponent.imageBottomRoundedRounded" show-input :min="1" :max="50" />
+          </el-form-item>
+        </el-form>
+      </div>
+      <!-- 组件样式 -->
+      <slot name="style"></slot>
+    </div>
+    <ImagesForm ref="ImagesFormRef" @confirmCallBack="confirmCallBack"></ImagesForm>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import draggable from 'vuedraggable';
+import usePcdiyStore from '@/store/modules/pcdiy';
+import uploadImage from '@/components/upload-image/index.vue';
+import ImagesForm from '@/components/ImagesForm/index.vue';
+const diyStore = usePcdiyStore();
+const ImagesFormRef = ref();
+
+// 打开图片类型
+const openImageType = (element: any, type: any) => {
+  const datas = JSON.parse(JSON.stringify(element));
+  ImagesFormRef.value.onOpen(datas, type);
+};
+//图片类型返回
+const confirmCallBack = (res: any, index: any) => {
+  diyStore.editComponent.imagelList[index].imgType = res.imgType;
+};
+</script>
+
+<style lang="scss" scoped>
+.pc-edit {
+  .edit-attr-item-wrap {
+    border-top: 2px solid var(--el-color-info-light-8);
+    padding-top: 20px;
+
+    &:first-of-type {
+      border-top: none;
+      padding-top: 0;
+    }
+
+    .edit-attr-title {
+      display: flex;
+
+      .title2 {
+        font-size: 12px;
+        color: #666;
+        margin-left: 6px;
+      }
+    }
+
+    .edit-attr-box {
+      padding: 18px 10px 0 10px;
+      border: 1px solid #e5e6eb;
+      border-radius: 4px;
+      position: relative;
+      margin-top: 18px;
+
+      .images-bos {
+        flex: 1;
+        height: 98px;
+        padding: 5px 0;
+      }
+
+      .images-box {
+        font-size: 13px;
+        color: #666;
+      }
+
+      .circleClose {
+        position: absolute;
+        top: -9px;
+        right: -9px;
+        cursor: pointer;
+      }
+
+      .annotation3 {
+        font-size: 12px;
+        color: #666;
+        line-height: 14px;
+      }
+    }
+  }
+
+  :deep(.el-form-item__label) {
+    font-weight: 400;
+  }
+  :deep(.file-selector) {
+    display: none;
+  }
+}
+</style>

+ 226 - 0
src/views/diy/pcEdit/navigation-edit.vue

@@ -0,0 +1,226 @@
+<template>
+  <div class="pc-edit">
+    <div class="content-wrap" v-show="diyStore.editTab == 'content'">
+      <div class="edit-attr-item-wrap">
+        <h3 class="mb-[10px]">导航模式</h3>
+        <el-form label-width="80px" class="px-[10px]">
+          <el-form-item label="展示风格">
+            <el-radio-group v-model="diyStore.editComponent.styleType">
+              <el-radio :value="1">固定显示</el-radio>
+              <el-radio :value="2">单行滑动</el-radio>
+              <el-radio :value="3">分页滑动</el-radio>
+            </el-radio-group>
+          </el-form-item>
+          <el-form-item label="每行数量">
+            <el-radio-group v-model="diyStore.editComponent.number">
+              <el-radio :value="3">3个</el-radio>
+              <el-radio :value="4">4个</el-radio>
+              <el-radio :value="5">5个</el-radio>
+            </el-radio-group>
+          </el-form-item>
+          <el-form-item label="每页行数" v-if="diyStore.editComponent.styleType == 3">
+            <el-radio-group v-model="diyStore.editComponent.count">
+              <el-radio :value="1">1个</el-radio>
+              <el-radio :value="2">2个</el-radio>
+            </el-radio-group>
+          </el-form-item>
+        </el-form>
+      </div>
+      <div class="edit-attr-item-wrap">
+        <div class="edit-attr-title flex-row-between">
+          <div>
+            <span>导航设置</span>
+            <span class="title2">鼠标拖拽可以改变顺序</span>
+          </div>
+        </div>
+        <el-form label-width="86px" class="px-[10px]">
+          <draggable v-model="diyStore.editComponent.navlList" item-key="id">
+            <template #item="{ element, index }">
+              <div class="edit-attr-box">
+                <el-icon @click="onDel(index)" color="#F56C6C" size="18px" class="circleClose">
+                  <CircleCloseFilled />
+                </el-icon>
+                <el-form-item label="图片上传">
+                  <div class="flex-row-start">
+                    <upload-image v-model="element.imageUrl" :limit="1" />
+                    <div class="flex-column-between images-bos">
+                      <div class="annotation3">(建议上传尺寸相同图片,推荐尺寸150*150)</div>
+                      <div class="flex-row-between images-box">
+                        <div>缩放模式</div>
+                        <div class="flex-row-start" @click="openImageType(element, index)">
+                          <span style="margin-top: 2px" class="text-primary flex-1 cursor-pointer">{{
+                            element.imgType == 1 ? '拉伸' : element.imgType == 2 ? '缩放' : '填充'
+                          }}</span>
+                          <el-icon class="cursor-pointer">
+                            <ArrowRight />
+                          </el-icon>
+                        </div>
+                      </div>
+                    </div>
+                  </div>
+                </el-form-item>
+                <el-form-item label="标题名称">
+                  <el-input v-model="element.title" placeholder="请输入标签名称" :maxlength="10" show-word-limit />
+                </el-form-item>
+                <el-form-item label="副标题名称">
+                  <el-input v-model="element.subtitle" placeholder="请输入标签名称" />
+                </el-form-item>
+                <el-form-item label="链接地址">
+                  <el-input v-model="element.url" placeholder="请输入链接地址" />
+                </el-form-item>
+              </div>
+            </template>
+          </draggable>
+          <el-button @click="onAdd" style="width: 100%; margin-top: 10px">新增导航</el-button>
+        </el-form>
+      </div>
+    </div>
+    <!-- 样式 -->
+    <div class="style-wrap" v-show="diyStore.editTab == 'style'">
+      <div class="edit-attr-item-wrap">
+        <h3 class="mb-[10px]">样式设置</h3>
+        <el-form label-width="80px" class="px-[10px]">
+          <el-form-item label="文字大小">
+            <el-slider size="small" v-model="diyStore.editComponent.titleSize" show-input :min="1" :max="50" />
+          </el-form-item>
+          <el-form-item label="文字加粗">
+            <el-radio-group size="small" v-model="diyStore.editComponent.titleWeight" fill="#409eff">
+              <el-radio-button label="加粗" :value="'bold'" />
+              <el-radio-button label="不加粗" :value="'normal'" />
+            </el-radio-group>
+          </el-form-item>
+          <el-form-item label="文字颜色">
+            <span class="mr-[10px]">{{ diyStore.editComponent.titleColor }}</span>
+            <el-color-picker class="mr-[10px]" v-model="diyStore.editComponent.titleColor" />
+            <el-button @click="diyStore.editComponent.titleColor = '#101828'" size="small">重置</el-button>
+          </el-form-item>
+        </el-form>
+      </div>
+      <div class="edit-attr-item-wrap">
+        <h3 class="mb-[10px]">副标题样式</h3>
+        <el-form label-width="80px" class="px-[10px]">
+          <el-form-item label="文字大小">
+            <el-slider size="small" v-model="diyStore.editComponent.subtitleSize" show-input :min="1" :max="50" />
+          </el-form-item>
+          <el-form-item label="文字颜色">
+            <span class="mr-[10px]">{{ diyStore.editComponent.subtitleColor }}</span>
+            <el-color-picker class="mr-[10px]" v-model="diyStore.editComponent.subtitleColor" />
+            <el-button @click="diyStore.editComponent.subtitleColor = '#101828'" size="small">重置</el-button>
+          </el-form-item>
+        </el-form>
+      </div>
+      <div class="edit-attr-item-wrap">
+        <h3 class="mb-[10px]">图片设置</h3>
+        <el-form label-width="80px" class="px-[10px]">
+          <el-form-item label="图片圆角">
+            <el-slider size="small" v-model="diyStore.editComponent.imageRadius" show-input :min="1" :max="50" />
+          </el-form-item>
+        </el-form>
+      </div>
+      <!-- 组件样式 -->
+      <slot name="style"></slot>
+    </div>
+    <ImagesForm ref="ImagesFormRef" @confirmCallBack="confirmCallBack"></ImagesForm>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import draggable from 'vuedraggable';
+import usePcdiyStore from '@/store/modules/pcdiy';
+import uploadImage from '@/components/upload-image/index.vue';
+import ImagesForm from '@/components/ImagesForm/index.vue';
+const diyStore = usePcdiyStore();
+const ImagesFormRef = ref();
+
+// 打开图片类型
+const openImageType = (element: any, type: any) => {
+  const datas = JSON.parse(JSON.stringify(element));
+  ImagesFormRef.value.onOpen(datas, type);
+};
+//图片类型返回
+const confirmCallBack = (res: any, index: any) => {
+  diyStore.editComponent.navlList[index].imgType = res.imgType;
+};
+const onAdd = () => {
+  diyStore.editComponent.navlList.push({
+    imageUrl: '',
+    title: '标题名称',
+    subtitle: '副标题名称',
+    url: '',
+    imgType: 1,
+    id: Date.now()
+  });
+};
+
+const onDel = (index: any) => {
+  diyStore.editComponent.navlList.splice(index, 1);
+};
+</script>
+
+<style lang="scss" scoped>
+.pc-edit {
+  .edit-attr-item-wrap {
+    border-top: 2px solid var(--el-color-info-light-8);
+    padding-top: 20px;
+
+    &:first-of-type {
+      border-top: none;
+      padding-top: 0;
+    }
+
+    .edit-attr-title {
+      display: flex;
+
+      .title2 {
+        font-size: 12px;
+        color: #666;
+        margin-left: 6px;
+      }
+    }
+
+    .edit-attr-box {
+      padding: 18px 10px 0 10px;
+      border: 1px solid #e5e6eb;
+      border-radius: 4px;
+      position: relative;
+      margin-top: 18px;
+
+      .images-bos {
+        flex: 1;
+        height: 98px;
+        padding: 5px 0;
+      }
+
+      .images-box {
+        font-size: 13px;
+        color: #666;
+      }
+
+      .circleClose {
+        position: absolute;
+        top: -9px;
+        right: -9px;
+        cursor: pointer;
+      }
+    }
+  }
+
+  .annotation3 {
+    font-size: 12px;
+    color: #666;
+    line-height: 14px;
+  }
+
+  :deep(.el-form-item__label) {
+    font-weight: 400;
+  }
+
+  :deep(.file-selector) {
+    display: none;
+  }
+
+  // :deep(.el-radio){
+  //   margin-right: 10px;
+  // }
+}
+</style>

+ 280 - 0
src/views/diy/pcEdit/textTitle-edit.vue

@@ -0,0 +1,280 @@
+<template>
+  <div class="pc-edit">
+    <!-- 内容 -->
+    <div class="content-wrap" v-show="diyStore.editTab == 'content'">
+      <div class="edit-attr-item-wrap">
+        <h3 class="mb-[10px]">风格设置</h3>
+        <el-form label-width="90px" class="px-[10px]">
+          <el-form-item label="风格选择" class="flex">
+            <span class="text-primary flex-1 cursor-pointer" @click="showStyle">风格{{ diyStore.editComponent.styleType }}</span>
+            <el-icon @click="showStyle" class="cursor-pointer">
+              <ArrowRight />
+            </el-icon>
+          </el-form-item>
+        </el-form>
+      </div>
+      <div class="edit-attr-item-wrap">
+        <h3 class="mb-[10px]">标题内容</h3>
+        <el-form label-width="90px" class="px-[10px]">
+          <el-form-item label="标题名称" class="flex">
+            <el-input v-model="diyStore.editComponent.title" placeholder="请输入标题内容" />
+          </el-form-item>
+          <el-form-item label="链接地址" class="flex">
+            <el-input v-model="diyStore.editComponent.titleUrl" placeholder="请输入链接地址" />
+          </el-form-item>
+          <el-form-item label="对齐方式" class="flex" v-if="diyStore.editComponent.styleType == 1">
+            <el-radio-group v-model="diyStore.editComponent.titleAlign">
+              <el-radio :value="'left'">居左</el-radio>
+              <el-radio :value="'center'">居中</el-radio>
+            </el-radio-group>
+          </el-form-item>
+        </el-form>
+      </div>
+      <div
+        class="edit-attr-item-wrap"
+        v-if="
+          diyStore.editComponent.styleType == 10 ||
+          diyStore.editComponent.styleType == 11 ||
+          diyStore.editComponent.styleType == 12 ||
+          diyStore.editComponent.styleType == 14
+        "
+      >
+        <h3 class="mb-[10px]">副标题内容</h3>
+        <el-form label-width="90px" class="px-[10px]">
+          <el-form-item label="副标题名称" class="flex">
+            <el-input v-model="diyStore.editComponent.subtitle" placeholder="请输入副标题内容" />
+          </el-form-item>
+        </el-form>
+      </div>
+      <div class="edit-attr-item-wrap" v-if="diyStore.editComponent.styleType == 12 || diyStore.editComponent.styleType == 14">
+        <h3 class="mb-[10px]">"更多"按钮</h3>
+        <el-form label-width="90px" class="px-[10px]">
+          <el-form-item label="按钮文字" class="flex">
+            <el-input v-model="diyStore.editComponent.more" placeholder="请输入按钮文字" />
+          </el-form-item>
+          <el-form-item label="链接地址" class="flex">
+            <el-input v-model="diyStore.editComponent.moreUrl" placeholder="请输入链接地址" />
+          </el-form-item>
+        </el-form>
+      </div>
+    </div>
+    <!-- 样式 -->
+    <div class="style-wrap" v-show="diyStore.editTab == 'style'">
+      <div class="edit-attr-item-wrap">
+        <h3 class="mb-[10px]">标题样式</h3>
+        <el-form label-width="80px" class="px-[10px]">
+          <el-form-item label="文字大小">
+            <el-slider size="small" v-model="diyStore.editComponent.titleSize" show-input :min="1" :max="50" />
+          </el-form-item>
+          <el-form-item label="文字加粗">
+            <el-radio-group size="small" v-model="diyStore.editComponent.titleWeight" fill="#409eff">
+              <el-radio-button label="加粗" :value="'bold'" />
+              <el-radio-button label="不加粗" :value="'normal'" />
+            </el-radio-group>
+          </el-form-item>
+          <el-form-item label="文字颜色">
+            <span class="mr-[10px]">{{ diyStore.editComponent.titleColor }}</span>
+            <el-color-picker class="mr-[10px]" v-model="diyStore.editComponent.titleColor" />
+            <el-button @click="diyStore.editComponent.titleColor = '#101828'" size="small">重置</el-button>
+          </el-form-item>
+        </el-form>
+      </div>
+      <div
+        class="edit-attr-item-wrap"
+        v-if="
+          diyStore.editComponent.styleType == 10 ||
+          diyStore.editComponent.styleType == 11 ||
+          diyStore.editComponent.styleType == 12 ||
+          diyStore.editComponent.styleType == 14
+        "
+      >
+        <h3 class="mb-[10px]">副标题样式</h3>
+        <el-form label-width="80px" class="px-[10px]">
+          <el-form-item label="文字大小">
+            <el-slider size="small" v-model="diyStore.editComponent.subtitleSize" show-input :min="1" :max="50" />
+          </el-form-item>
+          <el-form-item label="文字颜色">
+            <span class="mr-[10px]">{{ diyStore.editComponent.subtitleColor }}</span>
+            <el-color-picker class="mr-[10px]" v-model="diyStore.editComponent.subtitleColor" />
+            <el-button @click="diyStore.editComponent.subtitleColor = '#b7bcd2'" size="small">重置</el-button>
+          </el-form-item>
+        </el-form>
+      </div>
+      <div class="edit-attr-item-wrap" v-if="diyStore.editComponent.styleType == 12 || diyStore.editComponent.styleType == 14">
+        <h3 class="mb-[10px]">"更多"按钮样式</h3>
+        <el-form label-width="80px" class="px-[10px]">
+          <el-form-item label="文字大小">
+            <el-slider size="small" v-model="diyStore.editComponent.moreSize" show-input :min="1" :max="50" />
+          </el-form-item>
+          <el-form-item label="文字颜色">
+            <span class="mr-[10px]">{{ diyStore.editComponent.moreColor }}</span>
+            <el-color-picker class="mr-[10px]" v-model="diyStore.editComponent.moreColor" />
+            <el-button @click="diyStore.editComponent.moreColor = '#b7bcd2'" size="small">重置</el-button>
+          </el-form-item>
+          <el-form-item label="是否显示">
+            <el-switch v-model="diyStore.editComponent.moreShow" />
+          </el-form-item>
+        </el-form>
+      </div>
+      <!-- 组件样式 -->
+      <slot name="style"></slot>
+    </div>
+    <!-- 风格弹窗 -->
+    <el-dialog v-model="showDialog" title="风格选择">
+      <div class="data-bos">
+        <template v-for="(item, index) in styleList" :key="index">
+          <div
+            :class="{ 'border-primary': styleId == item.id }"
+            @click="changeTitleStyle(item)"
+            class="data-list flex items-center justify-center overflow-hidden w-[200px] h-[100px] mr-[12px] mb-[12px] cursor-pointer border bg-[#eee]"
+          >
+            <img :src="item.img" />
+          </div>
+        </template>
+      </div>
+
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="showDialog = false">取消</el-button>
+          <el-button type="primary" @click="confirmTitleStyle">确认</el-button>
+        </span>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="Index" lang="ts">
+import usePcdiyStore from '@/store/modules/pcdiy';
+import titlle1 from '@/assets/images/pcdiy/titlle1.png';
+import titlle2 from '@/assets/images/pcdiy/titlle2.png';
+import titlle3 from '@/assets/images/pcdiy/titlle3.png';
+import titlle4 from '@/assets/images/pcdiy/titlle4.png';
+import titlle5 from '@/assets/images/pcdiy/titlle5.png';
+import titlle6 from '@/assets/images/pcdiy/titlle6.png';
+import titlle7 from '@/assets/images/pcdiy/titlle7.png';
+import titlle8 from '@/assets/images/pcdiy/titlle8.png';
+import titlle9 from '@/assets/images/pcdiy/titlle9.png';
+import titlle10 from '@/assets/images/pcdiy/titlle10.png';
+import titlle11 from '@/assets/images/pcdiy/titlle11.png';
+import titlle12 from '@/assets/images/pcdiy/titlle12.png';
+import titlle13 from '@/assets/images/pcdiy/titlle13.png';
+import titlle14 from '@/assets/images/pcdiy/titlle14.png';
+import titlle15 from '@/assets/images/pcdiy/titlle15.png';
+
+const diyStore = usePcdiyStore();
+const styleId = ref<any>(1);
+const showDialog = ref(false);
+
+const styleList = [
+  {
+    img: titlle1,
+    id: 1
+  },
+  {
+    img: titlle2,
+    id: 2
+  },
+  {
+    img: titlle3,
+    id: 3
+  },
+  {
+    img: titlle4,
+    id: 4
+  },
+  {
+    img: titlle5,
+    id: 5
+  },
+  {
+    img: titlle6,
+    id: 6
+  },
+  {
+    img: titlle7,
+    id: 7
+  },
+  {
+    img: titlle8,
+    id: 8
+  },
+  {
+    img: titlle9,
+    id: 9
+  },
+  {
+    img: titlle10,
+    id: 10
+  },
+  {
+    img: titlle11,
+    id: 11
+  },
+  {
+    img: titlle12,
+    id: 12
+  },
+  {
+    img: titlle13,
+    id: 13
+  },
+  {
+    img: titlle14,
+    id: 14
+  },
+  {
+    img: titlle15,
+    id: 15
+  }
+];
+
+//打开弹窗
+const showStyle = () => {
+  showDialog.value = true;
+  styleId.value = diyStore.editComponent.styleType;
+};
+//选择弹窗
+const changeTitleStyle = (item: any) => {
+  styleId.value = item.id;
+};
+
+//确定弹窗
+const confirmTitleStyle = () => {
+  diyStore.editComponent.styleType = styleId.value;
+  showDialog.value = false;
+};
+</script>
+
+<style lang="scss" scoped>
+.pc-edit {
+  .edit-attr-item-wrap {
+    border-top: 2px solid var(--el-color-info-light-8);
+    padding-top: 20px;
+
+    &:first-of-type {
+      border-top: none;
+      padding-top: 0;
+    }
+  }
+
+  .data-bos {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 0 12px;
+    .data-list {
+      background-color: #f9fafb;
+      border: 1px solid #e5e7eb;
+      &.border-primary {
+        border-color: var(--el-color-primary);
+      }
+      img {
+        width: 100%;
+      }
+    }
+  }
+
+  :deep(.el-form-item__label) {
+    font-weight: 400;
+  }
+}
+</style>

+ 209 - 0
src/views/diy/pcList.vue

@@ -0,0 +1,209 @@
+<template>
+  <div class="p-2">
+    <div class="head-card">
+      <el-card shadow="hover">
+        <el-form ref="queryFormRef" :model="queryParams" :inline="true">
+          <el-form-item label="页面名称">
+            <el-input v-model="queryParams.title" placeholder="请输入页面名称" clearable @keyup.enter="handleQuery" />
+          </el-form-item>
+          <el-form-item label="所属应用">
+            <el-input v-model="queryParams.addon_name" placeholder="请输入所属应用" clearable @keyup.enter="handleQuery" />
+          </el-form-item>
+          <el-form-item label="页面类型">
+            <el-input v-model="queryParams.type" placeholder="请输入页面类型" clearable @keyup.enter="handleQuery" />
+          </el-form-item>
+          <el-form-item>
+            <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+            <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+          </el-form-item>
+        </el-form>
+      </el-card>
+    </div>
+
+    <el-card shadow="hover">
+      <template #header>
+        <el-row :gutter="10" class="mb8">
+          <el-col :span="1.5">
+            <el-button v-hasPermi="['system:dict:add']" type="primary" plain icon="Plus" @click="handleAdd">添加页面</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button v-hasPermi="['system:dict:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
+              删除
+            </el-button>
+          </el-col>
+        </el-row>
+      </template>
+
+      <el-table v-loading="loading" border :data="dataList">
+        <el-table-column label="页面名称" align="center" prop="dictName" :show-overflow-tooltip="true" />
+        <el-table-column label="所属应用" align="center" :show-overflow-tooltip="true">
+          <template #default="scope">
+            {{ scope.row.dictType }}
+          </template>
+        </el-table-column>
+        <el-table-column label="页面类型" align="center" :show-overflow-tooltip="true">
+          <template #default="scope">
+            {{ scope.row.dictType }}
+          </template>
+        </el-table-column>
+        <el-table-column label="状态" align="center" :show-overflow-tooltip="true">
+          <template #default="scope">
+            {{ scope.row.dictType }}
+          </template>
+        </el-table-column>
+        <el-table-column label="更新时间" align="center" :show-overflow-tooltip="true">
+          <template #default="scope">
+            {{ scope.row.dictType }}
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" align="center" width="160" class-name="small-padding fixed-width">
+          <template #default="scope">
+            <el-tooltip content="修改" placement="top">
+              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
+            </el-tooltip>
+            <el-tooltip content="删除" placement="top">
+              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
+            </el-tooltip>
+          </template>
+        </el-table-column>
+      </el-table>
+      <pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
+    </el-card>
+
+    <!--添加页面-->
+    <el-dialog v-model="dialogVisible" title="创建新页面" width="400px">
+      <el-form :model="formData" label-width="90px" ref="formRef" :rules="formRules">
+        <el-form-item label="页面名称" prop="title">
+          <el-input v-model.trim="formData.title" placeholder="请输入标题" clearable maxlength="12" show-word-limit class="w-full" />
+        </el-form-item>
+        <el-form-item label="页面类型" prop="type">
+          <el-select v-model="formData.type" placeholder="请选择页面类型" class="!w-full">
+            <el-option v-for="(item, key) in pageType" :label="item.title" :value="key" :key="key" />
+          </el-select>
+        </el-form-item>
+      </el-form>
+
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="dialogVisible = false">取消</el-button>
+          <el-button type="primary" @click="addEvent(formRef)">确认</el-button>
+        </span>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { pcDiyList, template } from '@/api/diy/index';
+import { FormInstance } from 'element-plus';
+const dataList = ref<any[]>([]);
+const loading = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+
+const pageType: any = reactive({}); // 页面类型
+const dialogVisible = ref(false);
+const formRef = ref<FormInstance>();
+// 添加自定义页面
+const formData = reactive({
+  title: '',
+  type: ''
+});
+// 表单验证规则
+const formRules = computed(() => {
+  return {
+    title: [{ required: true, message: '请输入标题', trigger: 'blur' }],
+    type: [{ required: true, message: '请选择页面类型', trigger: 'blur' }]
+  };
+});
+
+const queryParams = ref<any>({
+  pageNum: 1,
+  pageSize: 10,
+  title: '',
+  addon_name: '',
+  type: ''
+});
+
+/** 查询字典类型列表 */
+const getList = () => {
+  loading.value = true;
+  pcDiyList(queryParams.value).then((res) => {
+    if (res.code == 200) {
+      dataList.value = res.rows;
+      total.value = res.total;
+      loading.value = false;
+    }
+  });
+};
+
+/** 表单重置 */
+const reset = () => {};
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+};
+/** 重置按钮操作 */
+const resetQuery = () => {
+  handleQuery();
+};
+/** 新增按钮操作 */
+const router = useRouter();
+const handleAdd = () => {
+  dialogVisible.value = true;
+  // const url = router.resolve({
+  //   path: '/diy/edit',
+  //   query: {}
+  // });
+  // window.open(url.href);
+};
+/** 修改按钮操作 */
+const handleUpdate = async (row?: any) => {};
+/** 删除按钮操作 */
+const handleDelete = async (row?: any) => {};
+
+const getTemplate = () => {
+  template({ mode: '', addon: '' }).then((res) => {
+    if (res.code == 200) {
+      for (const key in pageType) {
+        delete pageType[key];
+      }
+
+      for (const key in res.data) {
+        pageType[key] = res.data[key];
+      }
+    }
+  });
+};
+const addEvent = async (formEl: FormInstance | undefined) => {
+  if (!formEl) return;
+
+  await formEl.validate(async (valid) => {
+    if (valid) {
+      const query = { type: formData.type, title: formData.title };
+      const url = router.resolve({
+        path: '/diy/edit',
+        query
+      });
+      window.open(url.href);
+      dialogVisible.value = false;
+      formData.title = '';
+      formData.type = '';
+    }
+  });
+};
+onMounted(() => {
+  getList();
+  getTemplate();
+});
+</script>
+
+<style lang="scss" scoped>
+.head-card {
+  margin-bottom: 20px;
+  :deep(.el-card__body) {
+    padding-bottom: 0px !important;
+  }
+}
+</style>

+ 173 - 0
src/views/diy/pcPages/advert.vue

@@ -0,0 +1,173 @@
+<template>
+  <div class="pcPages">
+    <div class="carousel-bos" :style="warpCss" v-if="componentData.styleType == 3">
+      <el-carousel :height="270 * componentData.count + (componentData.count == 2 ? 10 : 0) + 'px'" :autoplay="false" arrow="always">
+        <el-carousel-item v-for="(item1, index1) in dataList" :key="index1" class="w100% h100%">
+          <div class="carousel-list">
+            <div v-for="(item, index) in item1" :key="index" class="data-list flex-column-between" :style="boxCss">
+              <el-image
+                class="img"
+                :src="item.imageUrl ? item.imageUrl : figure"
+                :fit="item.imgType == 1 ? 'fill' : item.imgType == 2 ? 'contain' : 'cover'"
+                :style="componentData.imageRadius ? { borderRadius: componentData.imageRadius + 'px' } : {}"
+              />
+              <div :style="titleCss" class="title">{{ item.title || '' }}</div>
+              <div :style="subtitleCss" class="mt-[2px] mb-[12px] subtitle ellipsis">{{ item.subtitle || '' }}</div>
+            </div>
+          </div>
+        </el-carousel-item>
+      </el-carousel>
+    </div>
+    <div v-else :style="warpCss" class="data-bos">
+      <div v-for="(item, index) in componentData.navlList" :key="index" class="data-list flex-column-between" :style="boxCss">
+        <el-image
+          class="img"
+          :src="item.imageUrl ? item.imageUrl : figure"
+          :fit="item.imgType == 1 ? 'fill' : item.imgType == 2 ? 'contain' : 'cover'"
+          :style="componentData.imageRadius ? { borderRadius: componentData.imageRadius + 'px' } : {}"
+        />
+        <div :style="titleCss" class="title">{{ item.title || '' }}</div>
+        <div :style="subtitleCss" class="mt-[2px] mb-[12px] subtitle ellipsis">{{ item.subtitle || '' }}</div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import usePcdiyStore from '@/store/modules/pcdiy';
+import figure from '@/assets/images/figure.png';
+const diyStore = usePcdiyStore();
+const props = defineProps<{
+  index: number; // 确保声明 index 为可选属性
+}>();
+const componentData = diyStore.componentList[props.index];
+
+const dataList = computed(() => {
+  const chunkSize = componentData.number * componentData.count;
+  const result = [];
+  for (let i = 0; i < componentData.navlList.length; i += chunkSize) {
+    const chunk = componentData.navlList.slice(i, i + chunkSize);
+    result.push(chunk);
+  }
+  return result;
+});
+
+const warpCss = computed(() => {
+  let style = '';
+  style += 'position:relative;';
+  //背景颜色
+  if (componentData.pageStartBgColor) {
+    if (componentData.pageStartBgColor && componentData.pageEndBgColor)
+      style += `background:linear-gradient(${componentData.pageGradientAngle},${componentData.pageStartBgColor},${componentData.pageEndBgColor});`;
+    else if (componentData.pageStartBgColor) style += `background: ${componentData.pageStartBgColor};`;
+    else if (componentData.pageEndBgColor) style += `background: ${componentData.pageEndBgColor};`;
+  }
+  //边距
+  if (componentData.padding) {
+    if (componentData.padding.top > 0) {
+      style += 'padding-top:' + componentData.padding.top + 'px' + ';';
+    }
+    if (componentData.padding.bottom > 0) {
+      style += 'padding-bottom:' + componentData.padding.bottom + 'px' + ';';
+    }
+    style += 'padding-right:' + componentData.padding.both + 'px' + ';';
+    style += 'padding-left:' + componentData.padding.both + 'px' + ';';
+    if (componentData.styleType == 1) style += 'flex-wrap:wrap' + ';';
+  }
+  return style;
+});
+
+//组件样式
+const boxCss = computed(() => {
+  let style = '';
+  if (componentData.componentStartBgColor && componentData.componentEndBgColor)
+    style += `background:linear-gradient(${componentData.componentGradientAngle},${componentData.componentStartBgColor},${componentData.componentEndBgColor});`;
+  else if (componentData.componentStartBgColor) style += 'background-color:' + componentData.componentStartBgColor + ';';
+  else if (componentData.componentEndBgColor) style += 'background-color:' + componentData.componentEndBgColor + ';';
+  if (componentData.number) style += 'flex:' + `0 0 calc((100% - ${(componentData.number - 1) * 10}px) / ${componentData.number})` + ';';
+  //圆角
+  if (componentData.topRounded) style += 'border-top-left-radius:' + componentData.topRounded + 'px;';
+  if (componentData.topRounded) style += 'border-top-right-radius:' + componentData.topRounded + 'px;';
+  if (componentData.bottomRounded) style += 'border-bottom-left-radius:' + componentData.bottomRounded + 'px;';
+  if (componentData.bottomRounded) style += 'border-bottom-right-radius:' + componentData.bottomRounded + 'px;';
+  return style;
+});
+
+// 标题样式
+const titleCss = computed(() => {
+  let style = '';
+  if (componentData.titleColor) style += 'color:' + componentData.titleColor + ';';
+  if (componentData.titleSize) style += 'font-size:' + componentData.titleSize + 'px;';
+  if (componentData.titleWeight) style += 'font-weight:' + componentData.titleWeight + ';';
+  return style;
+});
+
+// 副标题样式
+const subtitleCss = computed(() => {
+  let style = '';
+  if (componentData.subtitleColor) style += 'color:' + componentData.subtitleColor + ';';
+  if (componentData.subtitleSize) style += 'font-size:' + componentData.subtitleSize + 'px;';
+  return style;
+});
+</script>
+
+<style lang="scss" scoped>
+.pcPages {
+  width: 1200px;
+  margin: 0 auto;
+  .data-bos {
+    display: flex;
+    gap: 10px;
+    width: 100%;
+    overflow-x: auto;
+    .data-list {
+      min-height: 270px;
+      width: 0;
+
+      .title {
+        text-align: center;
+        padding: 0 15px;
+      }
+
+      .subtitle {
+        text-align: center;
+        padding: 0 15px;
+      }
+
+      .img {
+        height: 200px;
+        width: 100%;
+      }
+    }
+  }
+
+  .carousel-bos {
+    .carousel-list {
+      width: 100%;
+      height: 100%;
+      display: flex;
+      gap: 10px;
+      flex-wrap: wrap;
+      .data-list {
+        height: 270px;
+        width: 0;
+
+        .title {
+          text-align: center;
+          padding: 0 15px;
+        }
+
+        .subtitle {
+          text-align: center;
+          padding: 0 15px;
+        }
+
+        .img {
+          height: 200px;
+          width: 100%;
+        }
+      }
+    }
+  }
+}
+</style>

+ 183 - 0
src/views/diy/pcPages/article.vue

@@ -0,0 +1,183 @@
+<template>
+  <div class="pcPages" :style="warpCss">
+    <div class="article-bos" :style="boxCss">
+      <template v-for="(item, index) in dataList" :key="index">
+        <div class="article-list" :style="dataCss" v-if="componentData.dataType == 2 ? true : index < componentData.dataNumber">
+          <img :src="item.caseImage ? item.caseImage : figure" alt="" />
+          <div class="caseTitle">{{ item.caseTitle }}</div>
+          <div class="projectBrief">{{ item.projectBrief }}</div>
+        </div>
+      </template>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { listServiceCase } from '@/api/product/serviceCase';
+import figure from '@/assets/images/figure.png';
+import usePcdiyStore from '@/store/modules/pcdiy';
+const diyStore = usePcdiyStore();
+
+const props = defineProps<{
+  index: number; // 确保声明 index 为可选属性
+}>();
+const componentData = diyStore.componentList[props.index];
+const dataList = ref<any>([]);
+
+onMounted(() => {
+  getDataList();
+});
+const getDataList = () => {
+  dataList.value = [];
+  // 默认
+  if (componentData.dataType == 1) {
+    listServiceCase({ pageSize: 16 }).then((res) => {
+      if (res.code == 200) {
+        dataList.value = res.rows;
+      }
+    });
+  } else {
+    //手动选择
+    listServiceCase({ pageSize: 16 }).then((res) => {
+      if (res.code == 200) {
+        const result = res.rows.filter((item: any) => componentData.dataIds.includes(item.id));
+        dataList.value = result;
+        console.log('result', result);
+      }
+    });
+  }
+};
+
+// 监听 componentData 变化,重新请求数据
+watch(
+  () => componentData.dataType,
+  () => {
+    getDataList();
+  }
+);
+
+watch(
+  () => componentData.dataIds,
+  () => {
+    getDataList();
+  },
+  { deep: true } // 5. 数组变化需要 deep 监听
+);
+
+const warpCss = computed(() => {
+  let style = '';
+  style += 'position:relative;';
+  //背景颜色
+  if (componentData.pageStartBgColor) {
+    if (componentData.pageStartBgColor && componentData.pageEndBgColor)
+      style += `background:linear-gradient(${componentData.pageGradientAngle},${componentData.pageStartBgColor},${componentData.pageEndBgColor});`;
+    else if (componentData.pageStartBgColor) style += `background: ${componentData.pageStartBgColor};`;
+    else if (componentData.pageEndBgColor) style += `background: ${componentData.pageEndBgColor};`;
+  }
+  //背景图片
+  if (componentData.componentBgUrl) {
+    style += `background-image:url('${componentData.componentBgUrl}');`;
+    style += 'background-size: cover;background-repeat: no-repeat;';
+  }
+  //边距
+  if (componentData.padding) {
+    if (componentData.padding.top > 0) {
+      style += 'padding-top:' + componentData.padding.top + 'px' + ';';
+    }
+    if (componentData.padding.bottom > 0) {
+      style += 'padding-bottom:' + componentData.padding.bottom + 'px' + ';';
+    }
+    style += 'padding-right:' + componentData.padding.both + 'px' + ';';
+    style += 'padding-left:' + componentData.padding.both + 'px' + ';';
+  }
+  //圆角
+  if (componentData.topRounded) style += 'border-top-left-radius:' + componentData.topRounded + 'px;';
+  if (componentData.topRounded) style += 'border-top-right-radius:' + componentData.topRounded + 'px;';
+  if (componentData.bottomRounded) style += 'border-bottom-left-radius:' + componentData.bottomRounded + 'px;';
+  if (componentData.bottomRounded) style += 'border-bottom-right-radius:' + componentData.bottomRounded + 'px;';
+
+  return style;
+});
+
+//组件样式
+const boxCss = computed(() => {
+  let style = '';
+  if (componentData.componentStartBgColor && componentData.componentEndBgColor)
+    style += `background:linear-gradient(${componentData.componentGradientAngle},${componentData.componentStartBgColor},${componentData.componentEndBgColor});`;
+  else if (componentData.componentStartBgColor) style += 'background-color:' + componentData.componentStartBgColor + ';';
+  else if (componentData.componentEndBgColor) style += 'background-color:' + componentData.componentEndBgColor + ';';
+  return style;
+});
+
+//样式
+const dataCss = computed(() => {
+  let style = '';
+  //背景颜色
+  if (componentData.backgroundColor) style += 'background-color:' + componentData.backgroundColor + ';';
+  //圆角
+  if (componentData.boxTopRounded) style += 'border-top-left-radius:' + componentData.boxTopRounded + 'px;';
+  if (componentData.boxTopRounded) style += 'border-top-right-radius:' + componentData.boxTopRounded + 'px;';
+  if (componentData.boxBottomRounded) style += 'border-bottom-left-radius:' + componentData.boxBottomRounded + 'px;';
+  if (componentData.boxBottomRounded) style += 'border-bottom-right-radius:' + componentData.boxBottomRounded + 'px;';
+  //投影
+  if (componentData.border == 2 && componentData.borderColor) style += 'box-shadow:' + componentData.borderColor + ' 0px 0px 5px';
+  //描边
+  if (componentData.border == 3 && componentData.borderColor) style += 'border:' + componentData.borderColor + ' 1px solid;';
+  return style;
+});
+</script>
+
+<style lang="scss" scoped>
+.pcPages {
+  width: 1200px;
+  margin: 0 auto;
+
+  .article-bos {
+    width: 100%;
+    display: flex;
+    flex-wrap: wrap;
+    gap: 15px;
+
+    .article-list {
+      flex: 0 0 calc((100% - 45px) / 4);
+      width: 0;
+      overflow: hidden;
+
+      .caseTitle {
+        font-size: 14px;
+        color: #333333;
+        height: 20px;
+        overflow: hidden;
+        white-space: nowrap;
+        text-overflow: ellipsis;
+        width: 100%;
+        margin: 5px 0;
+        padding: 0 15px;
+      }
+
+      .projectBrief {
+        font-size: 12px;
+        color: #666666;
+        height: 50px;
+        display: -webkit-box;
+        -webkit-line-clamp: 3;
+        line-clamp: 3;
+        /* 添加标准属性 */
+        -webkit-box-orient: vertical;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        width: 100%;
+        padding: 0 15px;
+        margin-bottom: 15px;
+      }
+
+      img {
+        width: 100%;
+        height: 165px;
+      }
+
+      // flex: 0 0 calc((100% - ${(componentData.number - 1) * 10}px);
+    }
+  }
+}
+</style>

+ 168 - 0
src/views/diy/pcPages/brand.vue

@@ -0,0 +1,168 @@
+<template>
+  <div class="pcPages" :style="warpCss">
+    <div class="big-brand" :style="boxCss">
+      <el-image
+        class="bigBrand-one"
+        :src="componentData.imageUrl ? componentData.imageUrl : figure"
+        :fit="componentData.imgType == 1 ? 'fill' : componentData.imgType == 2 ? 'contain' : 'cover'"
+        :style="componentData.boxRadius ? { borderRadius: componentData.boxRadius + 'px' } : {}"
+      />
+      <div class="bigBrand-bos">
+        <template v-for="(item, index) in componentData.brandList" :key="index">
+          <div
+            class="bigBrand-list"
+            v-if="Number(index) < 10"
+            :style="componentData.boxRadius ? { borderRadius: componentData.boxRadius + 'px' } : {}"
+          >
+            <el-image
+              class="img"
+              :src="item.imageUrl ? item.imageUrl : figure"
+              :fit="item.imgType == 1 ? 'fill' : item.imgType == 2 ? 'contain' : 'cover'"
+              :style="componentData.imageRadius ? { borderRadius: componentData.imageRadius + 'px' } : {}"
+            />
+            <div :style="titleCss" class="bigBrand1">{{ item.title || '' }}</div>
+            <div :style="subtitleCss" class="bigBrand2">
+              {{ item.subtitle || '' }}
+            </div>
+          </div>
+        </template>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import figure from '@/assets/images/figure.png';
+import usePcdiyStore from '@/store/modules/pcdiy';
+const diyStore = usePcdiyStore();
+
+const props = defineProps<{
+  index: number; // 确保声明 index 为可选属性
+}>();
+const componentData = diyStore.componentList[props.index];
+
+const warpCss = computed(() => {
+  let style = '';
+  style += 'position:relative;';
+  //背景颜色
+  if (componentData.pageStartBgColor) {
+    if (componentData.pageStartBgColor && componentData.pageEndBgColor)
+      style += `background:linear-gradient(${componentData.pageGradientAngle},${componentData.pageStartBgColor},${componentData.pageEndBgColor});`;
+    else if (componentData.pageStartBgColor) style += `background: ${componentData.pageStartBgColor};`;
+    else if (componentData.pageEndBgColor) style += `background: ${componentData.pageEndBgColor};`;
+  }
+  //背景图片
+  if (componentData.componentBgUrl) {
+    style += `background-image:url('${componentData.componentBgUrl}');`;
+    style += 'background-size: cover;background-repeat: no-repeat;';
+  }
+  //边距
+  if (componentData.padding) {
+    if (componentData.padding.top > 0) {
+      style += 'padding-top:' + componentData.padding.top + 'px' + ';';
+    }
+    if (componentData.padding.bottom > 0) {
+      style += 'padding-bottom:' + componentData.padding.bottom + 'px' + ';';
+    }
+    style += 'padding-right:' + componentData.padding.both + 'px' + ';';
+    style += 'padding-left:' + componentData.padding.both + 'px' + ';';
+  }
+  //圆角
+  if (componentData.topRounded) style += 'border-top-left-radius:' + componentData.topRounded + 'px;';
+  if (componentData.topRounded) style += 'border-top-right-radius:' + componentData.topRounded + 'px;';
+  if (componentData.bottomRounded) style += 'border-bottom-left-radius:' + componentData.bottomRounded + 'px;';
+  if (componentData.bottomRounded) style += 'border-bottom-right-radius:' + componentData.bottomRounded + 'px;';
+
+  return style;
+});
+
+//组件样式
+const boxCss = computed(() => {
+  let style = '';
+  if (componentData.componentStartBgColor && componentData.componentEndBgColor)
+    style += `background:linear-gradient(${componentData.componentGradientAngle},${componentData.componentStartBgColor},${componentData.componentEndBgColor});`;
+  else if (componentData.componentStartBgColor) style += 'background-color:' + componentData.componentStartBgColor + ';';
+  else if (componentData.componentEndBgColor) style += 'background-color:' + componentData.componentEndBgColor + ';';
+  if (componentData.number) style += 'flex:' + `0 0 calc((100% - ${(componentData.number - 1) * 10}px) / ${componentData.number})` + ';';
+  return style;
+});
+
+// 标题样式
+const titleCss = computed(() => {
+  let style = '';
+  if (componentData.titleColor) style += 'color:' + componentData.titleColor + ';';
+  if (componentData.titleSize) style += 'font-size:' + componentData.titleSize + 'px;';
+  if (componentData.titleWeight) style += 'font-weight:' + componentData.titleWeight + ';';
+  return style;
+});
+
+// 副标题样式
+const subtitleCss = computed(() => {
+  let style = '';
+  if (componentData.subtitleColor) style += 'color:' + componentData.subtitleColor + ';';
+  if (componentData.subtitleSize) style += 'font-size:' + componentData.subtitleSize + 'px;';
+  return style;
+});
+</script>
+
+<style lang="scss" scoped>
+.pcPages {
+  width: 1200px;
+  margin: 0 auto;
+  // 大牌推荐
+  .big-brand {
+    height: 334px;
+    display: flex;
+    gap: 10px;
+    width: 100%;
+
+    .bigBrand-one {
+      width: 230px;
+      height: 340px;
+    }
+
+    .bigBrand-bos {
+      flex: 1;
+      display: flex;
+      flex-wrap: wrap;
+      gap: 10px;
+      overflow: hidden;
+
+      .bigBrand-list {
+        flex: 0 0 calc((100% - 40px) / 5);
+        height: 162px;
+        background: #ffffff;
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        padding: 32px 20px 0 20px;
+        width: 0;
+
+        .img {
+          width: 150px;
+          height: 30px;
+        }
+
+        .bigBrand1 {
+          font-weight: 600;
+          font-size: 14px;
+          color: #101828;
+          margin: 10px 0 4px 0;
+        }
+
+        .bigBrand2 {
+          font-weight: 400;
+          font-size: 12px;
+          color: #364153;
+          display: -webkit-box;
+          -webkit-line-clamp: 2;
+          line-clamp: 2;
+          -webkit-box-orient: vertical;
+          overflow: hidden;
+          text-overflow: ellipsis;
+        }
+      }
+    }
+  }
+}
+</style>

+ 102 - 0
src/views/diy/pcPages/carousel.vue

@@ -0,0 +1,102 @@
+<template>
+  <div class="pcPages" :style="warpCss" :class="['position' + componentData.position, 'styleType' + componentData.styleType]">
+    <el-carousel :interval="componentData.interval" :height="componentData.imageHeight + 'px'" class="carousel-bos" :style="boxCss">
+      <el-carousel-item v-for="(item, index) in componentData.imagelList" :key="index" class="carousel-item">
+        <el-image
+          class="img"
+          :src="item.imageUrl ? item.imageUrl : figure"
+          :fit="item.imgType == 1 ? 'fill' : item.imgType == 2 ? 'contain' : 'cover'"
+        />
+      </el-carousel-item>
+    </el-carousel>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import usePcdiyStore from '@/store/modules/pcdiy';
+const diyStore = usePcdiyStore();
+
+const props = defineProps<{
+  index: number; // 确保声明 index 为可选属性
+}>();
+const componentData = diyStore.componentList[props.index];
+import figure from '@/assets/images/figure.png';
+
+const warpCss = computed(() => {
+  let style = '';
+  style += 'position:relative;';
+  //背景颜色
+  if (componentData.pageStartBgColor) {
+    if (componentData.pageStartBgColor && componentData.pageEndBgColor)
+      style += `background:linear-gradient(${componentData.pageGradientAngle},${componentData.pageStartBgColor},${componentData.pageEndBgColor});`;
+    else if (componentData.pageStartBgColor) style += `background: ${componentData.pageStartBgColor};`;
+    else if (componentData.pageEndBgColor) style += `background: ${componentData.pageEndBgColor};`;
+  }
+  //边距
+  if (componentData.padding) {
+    if (componentData.padding.top > 0) {
+      style += 'padding-top:' + componentData.padding.top + 'px' + ';';
+    }
+    if (componentData.padding.bottom > 0) {
+      style += 'padding-bottom:' + componentData.padding.bottom + 'px' + ';';
+    }
+    style += 'padding-right:' + componentData.padding.both + 'px' + ';';
+    style += 'padding-left:' + componentData.padding.both + 'px' + ';';
+  }
+
+  return style;
+});
+
+//组件样式
+const boxCss = computed(() => {
+  let style = '';
+  //圆角
+  if (componentData.imageRadius) style += 'border-radius:' + componentData.imageRadius + 'px;';
+  return style;
+});
+</script>
+
+<style lang="scss" scoped>
+.pcPages {
+  width: 1200px;
+  margin: 0 auto;
+
+  .carousel-bos {
+    width: 100%;
+    overflow: hidden;
+
+    .carousel-item {
+      width: 100%;
+      height: 100%;
+
+      .img {
+        width: 100%;
+        height: 100%;
+      }
+    }
+  }
+
+  &.position1 {
+    :deep(.el-carousel__indicators--horizontal) {
+      left: 5%;
+    }
+  }
+  &.position2 {
+    :deep(.el-carousel__indicators--horizontal) {
+      left: 50%;
+    }
+  }
+  &.position3 {
+    :deep(.el-carousel__indicators--horizontal) {
+      left: 95%;
+    }
+  }
+  &.styleType2 {
+    :deep(.el-carousel__button) {
+      height: 10px;
+      width: 10px;
+      border-radius: 10px;
+    }
+  }
+}
+</style>

+ 216 - 0
src/views/diy/pcPages/goods.vue

@@ -0,0 +1,216 @@
+<template>
+  <div class="pcPages" :style="warpCss">
+    <div class="goods-bos" :style="boxCss">
+      <div v-for="(item, index) in dataList" :key="index" class="goods-list flex-column-between" :style="goodsCss">
+        <img
+          class="goods-img"
+          :src="item.productImage ? item.productImage : figure"
+          alt=""
+          :style="componentData.imageRadius ? { borderRadius: componentData.imageRadius + 'px' } : {}"
+        />
+        <div v-if="componentData.goodsShow.includes(1)" :style="titleCss" class="itemName">{{ item.itemName || '' }}</div>
+        <div class="flex-row-between">
+          <div>
+            <span v-if="componentData.goodsShow.includes(2)" class="memberPrice" :style="{ color: componentData.priceColor }"
+              >¥{{ item.memberPrice }}</span
+            >
+            <span v-if="componentData.goodsShow.includes(3)" class="marketPrice">¥{{ item.marketPrice }}</span>
+          </div>
+          <template v-if="componentData.btnShow">
+            <div v-if="componentData.btnStyle == 1" :style="btnCss1" class="btn1">{{ componentData.btnText }}</div>
+            <div v-if="componentData.btnStyle == 2" :style="btnCss2" class="btn2 flex-row-center">
+              <el-icon size="14"><Plus /></el-icon>
+            </div>
+            <div v-if="componentData.btnStyle == 3" :style="btnCss2" class="btn2 flex-row-center">
+              <icon name="iconfont icongouwuche" size="14px" />
+            </div>
+          </template>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import figure from '@/assets/images/figure.png';
+import usePcdiyStore from '@/store/modules/pcdiy';
+import { listBase } from '@/api/pmsProduct/base';
+const diyStore = usePcdiyStore();
+
+const props = defineProps<{
+  index: number; // 确保声明 index 为可选属性
+}>();
+const componentData = diyStore.componentList[props.index];
+const dataList = ref<any>([]);
+
+onMounted(() => {
+  getDataList();
+});
+
+const getDataList = () => {
+  dataList.value = [];
+  listBase({ pageNum: 1, pageSize: 16 }).then((res) => {
+    if (res.code == 200) {
+      dataList.value = res.rows;
+    }
+  });
+  // // 默认
+  // if (componentData.dataType == 1) {
+  //   listBase({ pageNum: 1, pageSize: 16 }).then((res) => {
+  //     if (res.code == 200) {
+  //       dataList.value = res.rows;
+  //     }
+  //   });
+  // } else {
+  //   //手动选择
+  //   listBase({ pageSize: 16 }).then((res) => {
+  //     if (res.code == 200) {
+  //       const result = res.rows.filter((item: any) => componentData.dataIds.includes(item.id));
+  //       dataList.value = result;
+  //       console.log('result', result);
+  //     }
+  //   });
+  // }
+};
+
+const warpCss = computed(() => {
+  let style = '';
+  style += 'position:relative;';
+  //背景颜色
+  if (componentData.pageStartBgColor) {
+    if (componentData.pageStartBgColor && componentData.pageEndBgColor)
+      style += `background:linear-gradient(${componentData.pageGradientAngle},${componentData.pageStartBgColor},${componentData.pageEndBgColor});`;
+    else if (componentData.pageStartBgColor) style += `background: ${componentData.pageStartBgColor};`;
+    else if (componentData.pageEndBgColor) style += `background: ${componentData.pageEndBgColor};`;
+  }
+  //背景图片
+  if (componentData.componentBgUrl) {
+    style += `background-image:url('${componentData.componentBgUrl}');`;
+    style += 'background-size: cover;background-repeat: no-repeat;';
+  }
+  //边距
+  if (componentData.padding) {
+    if (componentData.padding.top > 0) {
+      style += 'padding-top:' + componentData.padding.top + 'px' + ';';
+    }
+    if (componentData.padding.bottom > 0) {
+      style += 'padding-bottom:' + componentData.padding.bottom + 'px' + ';';
+    }
+    style += 'padding-right:' + componentData.padding.both + 'px' + ';';
+    style += 'padding-left:' + componentData.padding.both + 'px' + ';';
+  }
+  //圆角
+  if (componentData.topRounded) style += 'border-top-left-radius:' + componentData.topRounded + 'px;';
+  if (componentData.topRounded) style += 'border-top-right-radius:' + componentData.topRounded + 'px;';
+  if (componentData.bottomRounded) style += 'border-bottom-left-radius:' + componentData.bottomRounded + 'px;';
+  if (componentData.bottomRounded) style += 'border-bottom-right-radius:' + componentData.bottomRounded + 'px;';
+
+  return style;
+});
+
+//组件样式
+const boxCss = computed(() => {
+  let style = '';
+  if (componentData.componentStartBgColor && componentData.componentEndBgColor)
+    style += `background:linear-gradient(${componentData.componentGradientAngle},${componentData.componentStartBgColor},${componentData.componentEndBgColor});`;
+  else if (componentData.componentStartBgColor) style += 'background-color:' + componentData.componentStartBgColor + ';';
+  else if (componentData.componentEndBgColor) style += 'background-color:' + componentData.componentEndBgColor + ';';
+
+  if (componentData.styleType == 1) style += 'flex-wrap:wrap' + ';';
+  return style;
+});
+
+const goodsCss = computed(() => {
+  let style = '';
+  if (componentData.goodsbackgroundColor) style += 'background-color:' + componentData.goodsbackgroundColor + ';';
+  //圆角
+  if (componentData.goodstopRounded) style += 'border-top-left-radius:' + componentData.goodstopRounded + 'px;';
+  if (componentData.goodstopRounded) style += 'border-top-right-radius:' + componentData.goodstopRounded + 'px;';
+  if (componentData.goodsbottomRounded) style += 'border-bottom-left-radius:' + componentData.goodsbottomRounded + 'px;';
+  if (componentData.goodsbottomRounded) style += 'border-bottom-right-radius:' + componentData.goodsbottomRounded + 'px;';
+  return style;
+});
+
+const titleCss = computed(() => {
+  let style = '';
+  if (componentData.goodsTitleColor) style += 'color:' + componentData.goodsTitleColor + ';';
+  if (componentData.goodsTitleType == 1) style += 'font-weight:bold' + ';';
+  if (componentData.goodsTitleType == 3) {
+    style += 'display: -webkit-box' + ';';
+    style += '-webkit-line-clamp: 2' + ';';
+    style += 'line-clamp: 2' + ';';
+    style += '-webkit-box-orient: vertical' + ';';
+  } else {
+    style += 'white-space:nowrap' + ';';
+  }
+  return style;
+});
+
+const btnCss1 = computed(() => {
+  let style = '';
+  if (componentData.btnbackgroundColor) style += 'background-color:' + componentData.btnbackgroundColor + ';';
+  if (componentData.btnColor) style += 'color:' + componentData.btnColor + ';';
+  return style;
+});
+
+const btnCss2 = computed(() => {
+  let style = '';
+  if (componentData.btnbackgroundColor) style += 'color:' + componentData.btnbackgroundColor + ';';
+  if (componentData.btnbackgroundColor) style += 'border-color:' + componentData.btnbackgroundColor + ';';
+  return style;
+});
+</script>
+
+<style lang="scss" scoped>
+.pcPages {
+  width: 1200px;
+  margin: 0 auto;
+  .goods-bos {
+    display: flex;
+    gap: 10px;
+    overflow: auto;
+    .goods-list {
+      padding: 20px 15px;
+      height: 300px;
+      width: 0;
+      flex: 0 0 calc((100% - 40px) / 5);
+      overflow: hidden;
+      .goods-img {
+        width: 100%;
+        height: 180px;
+      }
+      .itemName {
+        font-size: 14px;
+        height: 40px;
+        overflow: hidden;
+        text-overflow: ellipsis;
+      }
+      .memberPrice {
+        font-size: 16px;
+        font-weight: bold;
+      }
+      .marketPrice {
+        font-size: 12px;
+        color: #99a1af;
+        line-height: 20px;
+        text-decoration-line: line-through;
+        text-transform: none;
+        margin-left: 6px;
+      }
+
+      .btn1 {
+        padding: 5px 15px;
+        font-size: 12px;
+        border-radius: 15px;
+      }
+      .btn2 {
+        color: var(--el-color-primary);
+        border: 1px solid var(--el-color-primary);
+        height: 26px;
+        width: 26px;
+        border-radius: 50%;
+      }
+    }
+  }
+}
+</style>

+ 875 - 0
src/views/diy/pcPages/head.vue

@@ -0,0 +1,875 @@
+<template>
+  <div class="head-bos" :style="warpCss">
+    <!-- 搜索组件 -->
+    <div class="search-bos">
+      <img
+        class="logo"
+        :src="componentData.logo ? componentData.logo : figure"
+        alt=""
+        :style="{ 'width': componentData.topStyle == 1 ? '185px' : '390px' }"
+      />
+      <div class="search-box">
+        <div class="search-div flex-row-start">
+          <div class="search-input flex-row-center">
+            <el-input class="el-input" readonly placeholder="搜索商品、品牌、分类..." />
+            <div class="bnt flex-row-center">
+              <el-icon color="#ffffff" size="20">
+                <Search />
+              </el-icon>
+            </div>
+          </div>
+          <el-badge :value="5">
+            <div class="cat-bos flex-row-center">
+              <img src="@/assets/images/pcdiy/layout4.png" alt="" />
+              <span>我的购物车</span>
+            </div>
+          </el-badge>
+        </div>
+        <div class="search-text">
+          <div v-for="(item, index) in componentData.toplabel" :key="index">{{ item.title }}</div>
+        </div>
+      </div>
+      <img class="code" :src="componentData.code ? componentData.code : figure" alt="" />
+    </div>
+    <!-- 分类 -->
+    <div class="nav-pages">
+      <div class="nav-bos">
+        <div class="nav-all flex-row-center" v-if="componentData.classifyShow">
+          <img src="@/assets/images/pcdiy/layout2.png" alt="" />
+          <div>全部商品分类</div>
+        </div>
+        <div v-for="(item, index) in componentData.topNav" :key="index" class="nav-list" :class="index == 0 ? 'hig' : ''">
+          {{ item.title }}
+        </div>
+      </div>
+    </div>
+
+    <!--  头部 -->
+    <div class="head-pages">
+      <div class="bg-img" v-if="componentData.carouselStyle">
+        <img :src="componentData.carouselList && componentData.carouselList[0].imageUrl" alt="" />
+      </div>
+      <div class="home-head">
+        <div
+          class="classify"
+          v-if="componentData.leftStyle == 1"
+          :style="{
+            backgroundColor: componentData.leftBackground
+          }"
+        >
+          <div class="classify-list" v-for="(item, index) in classifyList" :key="index">
+            <div :style="{ 'color': componentData.leftColor1 }" class="label ellipsis">{{ item.label }}</div>
+            <div :style="{ 'color': componentData.leftColor2 }" class="info info1 ellipsis">{{ item.oneLable1 }}</div>
+            <div :style="{ 'color': componentData.leftColor2 }" class="info ellipsis">{{ item.oneLable2 }}</div>
+          </div>
+        </div>
+        <div class="classify2" v-else>
+          <div class="classify-list" v-for="(item, index) in classifyList2" :key="index">
+            <div class="label ellipsis">{{ item.label }}</div>
+            <div class="two-level">
+              <template v-for="(item1, index1) in item.children" :key="index1">
+                <div class="two-hig" v-if="Number(index1) < 2">{{ item1.label }}</div>
+                <div style="margin: 0 4px" v-if="index1 == 0 && item.children.length > 1">/</div>
+              </template>
+            </div>
+          </div>
+        </div>
+        <div class="head-bos">
+          <div
+            class="carousel"
+            :class="{ 'carousel-type': componentData.carouselType == 2 }"
+            :style="{
+              height: componentData.advertNum == 0 ? '540px' : '400px',
+              borderRadius: componentData.carouselRadius + 'px'
+            }"
+          >
+            <el-carousel :autoplay="false" trigger="click" :height="componentData.advertNum == 0 ? '540px' : '400px'" arrow="always">
+              <template v-if="componentData.carouselList && componentData.carouselList.length > 0">
+                <el-carousel-item v-for="item in componentData.carouselList" :key="item">
+                  <el-image
+                    style="width: 100%; height: 100%"
+                    :src="item.imageUrl ? item.imageUrl : figure"
+                    :fit="item.imgType == 1 ? 'fill' : item.imgType == 2 ? 'contain' : 'cover'"
+                  />
+                </el-carousel-item>
+              </template>
+              <template v-else>
+                <el-carousel-item>
+                  <img :src="figure" alt="" />
+                </el-carousel-item>
+              </template>
+            </el-carousel>
+          </div>
+          <div class="head-box" v-if="componentData.advertNum != 0">
+            <template v-for="(item, index) in diyStore.editComponent.advertList" :key="index">
+              <div class="head-item" v-if="item.show">
+                <el-image
+                  style="width: 100%; height: 100%"
+                  :src="item.imageUrl ? item.imageUrl : figure"
+                  :fit="item.imgType == 1 ? 'fill' : item.imgType == 2 ? 'contain' : 'cover'"
+                />
+              </div>
+            </template>
+          </div>
+        </div>
+        <!-- 右边 -->
+        <div
+          class="head-right"
+          :style="{
+            borderRadius: componentData.rightRadius + 'px'
+          }"
+        >
+          <div class="login-bos">
+            <div class="login-box">
+              <img :src="figure" alt="" />
+              <div>
+                <div class="login1">您好,欢迎来到优易达</div>
+                <div class="login2">请先登录</div>
+              </div>
+            </div>
+            <div class="loginBtn-bos flex-row-center">
+              <div class="login-bnt1 flex-row-center">登录</div>
+              <div class="login-bnt2 flex-row-center">注册</div>
+            </div>
+          </div>
+          <div class="real-time">
+            <div class="real-title flex-row-between">
+              <div class="real1">优易资讯</div>
+              <div class="real2 flex-row-start">
+                <div>更多</div>
+                <el-icon :size="13" color="#83899F">
+                  <ArrowRight />
+                </el-icon>
+              </div>
+            </div>
+            <template v-for="(item, index) in realList" :key="index">
+              <div class="real-list ellipsis" v-if="Number(index) < 7">{{ item.announcementTitle }}</div>
+            </template>
+          </div>
+          <div class="interests">
+            <div class="interests-title">企业会员权益</div>
+            <div class="interests-bos">
+              <div v-for="(item, index) in componentData.navlList" :key="index" class="interests-item flex-column-center">
+                <el-image
+                  class="img"
+                  :src="item.imageUrl ? item.imageUrl : figure"
+                  :fit="item.imgType == 1 ? 'fill' : item.imgType == 2 ? 'contain' : 'cover'"
+                />
+                <div style="height: 18px">{{ item.title }}</div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup name="Index" lang="ts">
+import figure from '@/assets/images/figure.png';
+import usePcdiyStore from '@/store/modules/pcdiy';
+const diyStore = usePcdiyStore();
+const props = defineProps<{
+  index: number; // 确保声明 index 为可选属性
+}>();
+const componentData = diyStore.componentList[props.index];
+//左侧分类
+const classifyList = ref<any>([
+  {
+    id: 12792,
+    label: '电脑/外设产品',
+    oneLable1: '联想',
+    oneLable2: '华硕'
+  },
+  {
+    id: 12792,
+    label: '通讯/数码智能',
+    oneLable1: '华为',
+    oneLable2: '罗技'
+  },
+  {
+    id: 12854,
+    label: '办公设备',
+    oneLable1: '惠普',
+    oneLable2: 'MAXHUB'
+  },
+  {
+    id: 12962,
+    label: '办公耗品',
+    oneLable1: '奔图',
+    oneLable2: '莱盛'
+  },
+  {
+    id: 14184,
+    label: '文具用品',
+    oneLable1: '齐心',
+    oneLable2: '得力'
+  },
+  {
+    id: 13162,
+    label: '个护清洁',
+    oneLable1: '洁柔',
+    oneLable2: '宝洁'
+  },
+  {
+    id: 13164,
+    label: '清洁纸品',
+    oneLable1: '洁柔',
+    oneLable2: '宝洁'
+  },
+  {
+    id: 12716,
+    label: '食品饮料',
+    oneLable1: '金龙鱼',
+    oneLable2: '贝蒂斯'
+  },
+  {
+    id: 13320,
+    label: '家居日用',
+    oneLable1: '膳魔师',
+    oneLable2: '博洋'
+  },
+  {
+    id: 13420,
+    label: '运动户外',
+    oneLable1: '红双喜',
+    oneLable2: '捷瑞特'
+  },
+  {
+    id: 13054,
+    label: '家用电器',
+    oneLable1: '摩飞',
+    oneLable2: '小熊'
+  },
+  {
+    id: 13478,
+    label: '家具用品',
+    oneLable1: '科飞亚',
+    oneLable2: '全能'
+  },
+  {
+    id: 13530,
+    label: '工业用品',
+    oneLable1: '代尔塔',
+    oneLable2: '宝工'
+  }
+]);
+//左侧分类
+const classifyList2 = ref<any>([
+  {
+    id: 12792,
+    label: '电脑/外设产品',
+    children: [
+      {
+        label: '电脑整机'
+      },
+      {
+        label: '电脑外设'
+      }
+    ]
+  },
+  {
+    id: 12792,
+    label: '电气控制及工业自动化',
+    children: [
+      {
+        label: '配电及控制电器'
+      },
+      {
+        label: '电气辅材'
+      }
+    ]
+  },
+  {
+    id: 12854,
+    label: '手工具、动力工具及耗材',
+    children: [
+      {
+        label: '防爆工具'
+      },
+      {
+        label: '钳工工具'
+      }
+    ]
+  },
+  {
+    id: 12962,
+    label: '气动、液压、泵、管阀',
+    children: [
+      {
+        label: '气源处理元件'
+      },
+      {
+        label: '气动控制元件'
+      }
+    ]
+  },
+  {
+    id: 14184,
+    label: '轴承、传动、通用设备',
+    children: [
+      {
+        label: '轴承及其工具'
+      },
+      {
+        label: '皮带'
+      }
+    ]
+  },
+  {
+    id: 13162,
+    label: '刀具、量具、磨具、仪表',
+    children: [
+      {
+        label: '刃具'
+      },
+      {
+        label: '刀具智能系统'
+      }
+    ]
+  },
+  {
+    id: 13164,
+    label: '紧固、密封、建工材料',
+    children: [
+      {
+        label: '螺栓'
+      },
+      {
+        label: '组合件'
+      }
+    ]
+  },
+  {
+    id: 12716,
+    label: '搬运、存储、包材',
+    children: [
+      {
+        label: '智能仓储设备'
+      },
+      {
+        label: '搬运堆高设备'
+      }
+    ]
+  }
+]);
+const realList = ref<any>([]);
+
+const warpCss = computed(() => {
+  let style = '';
+  style += 'position:relative;';
+  //边距
+  if (diyStore.componentList[props.index].padding) {
+    if (diyStore.componentList[props.index].padding.top > 0) {
+      style += 'padding-top:' + diyStore.componentList[props.index].padding.top + 'px' + ';';
+    }
+    if (diyStore.componentList[props.index].padding.bottom > 0) {
+      style += 'padding-bottom:' + diyStore.componentList[props.index].padding.bottom + 'px' + ';';
+    }
+    style += 'padding-right:' + diyStore.componentList[props.index].padding.both + 'px' + ';';
+    style += 'padding-left:' + diyStore.componentList[props.index].padding.both + 'px' + ';';
+  }
+
+  return style;
+});
+</script>
+
+<style lang="scss" scoped>
+.head-bos {
+  width: 1300px;
+
+  // 搜索栏
+  .search-bos {
+    margin: 0 auto;
+    width: 1300px;
+    display: flex;
+    background-color: #ffffff;
+    padding: 0 50px;
+
+    .logo {
+      width: 185px;
+      height: 90px;
+      border-radius: 4px;
+      margin-top: 15px;
+      margin-right: 30px;
+    }
+
+    .search-box {
+      flex: 1;
+      padding-top: 57px;
+
+      .search-div {
+        .search-input {
+          flex: 1;
+          height: 48px;
+          border-radius: 10px;
+          border: 2px solid #fb2c36;
+          padding-right: 4px;
+
+          .el-input {
+            height: 40px;
+            width: 100%;
+            font-size: 16px;
+
+            :deep(.el-input__wrapper) {
+              border: none;
+              /* 可选:去除聚焦时的高亮 */
+              box-shadow: none;
+              outline: none;
+            }
+          }
+
+          .bnt {
+            width: 68px;
+            height: 40px;
+            background: #e7000b;
+            border-radius: 8px;
+            font-weight: bold;
+            cursor: pointer;
+          }
+        }
+
+        .cat-bos {
+          width: 143px;
+          height: 48px;
+          background: #ffffff;
+          border-radius: 10px;
+          border: 1px solid #e5e7eb;
+          margin-left: 24px;
+          font-size: 16px;
+          color: #e7000b;
+          cursor: pointer;
+
+          img {
+            width: 16px;
+            height: 16px;
+            margin-right: 8px;
+            margin-top: 2px;
+          }
+        }
+      }
+
+      .search-text {
+        font-size: 14px;
+        color: #e7000b;
+        display: flex;
+        margin-top: 6px;
+
+        div {
+          margin-left: 10px;
+        }
+      }
+    }
+
+    .code {
+      height: 90px;
+      width: 90px;
+      margin-top: 30px;
+      margin-left: 70px;
+      border-radius: 4px;
+    }
+  }
+
+  //分类
+  .nav-pages {
+    width: 1300px;
+    background-color: #ffffff;
+    .nav-bos {
+      margin: 0 auto;
+      width: 1200px;
+      display: flex;
+      position: relative;
+      padding-top: 15px;
+
+      .nav-all {
+        width: 234px;
+        height: 48px;
+        background: #e7000b;
+        padding: 0 10px;
+        font-size: 16px;
+        color: #ffffff;
+        cursor: pointer;
+
+        img {
+          height: 16px;
+          width: 16px;
+          margin-right: 6px;
+        }
+      }
+
+      .nav-list {
+        line-height: 48px;
+        font-size: 16px;
+        color: #364153;
+        margin: 0 16px;
+        text-align: center;
+        cursor: pointer;
+
+        &.hig {
+          color: #e7000b;
+          position: relative;
+          &::before {
+            content: '';
+            position: absolute;
+            bottom: 0;
+            left: 0px;
+            display: inline-block;
+            width: 100%;
+            height: 3px;
+            background: #e7000b;
+            margin-right: 8px;
+          }
+        }
+
+        &:hover {
+          color: #e7000b;
+        }
+      }
+    }
+  }
+
+  // 头部
+  .head-pages {
+    width: 1300px;
+
+    background-size: 100% 100%;
+    background-repeat: no-repeat;
+    position: relative;
+    backdrop-filter: blur(95px);
+    filter: blur(30rpx);
+
+    .bg-img {
+      position: absolute;
+      width: 100%;
+      height: 100%;
+      top: 0;
+      z-index: -1;
+      filter: blur(30rpx);
+      overflow: hidden;
+
+      img {
+        width: 100%;
+        height: 100%;
+        transform: scale(1.2);
+        filter: blur(30px);
+      }
+    }
+
+    .home-head {
+      width: 1200px;
+      position: relative;
+      display: flex;
+      gap: 0px 10px;
+      margin: 10px auto 0 auto;
+
+      .classify {
+        width: 234px;
+        height: 540px;
+        background: #ffffff;
+        border-radius: 5px;
+
+        .classify-list {
+          width: 100%;
+          height: 40px;
+          cursor: pointer;
+          display: flex;
+          align-items: center;
+          padding-left: 15px;
+          position: relative;
+
+          .label {
+            max-width: 100px;
+            font-weight: 600;
+            font-size: 14px;
+            color: #101828;
+            white-space: nowrap;
+            margin-right: 10px;
+
+            &:hover {
+              color: #e7000b;
+            }
+          }
+
+          .info {
+            max-width: 60px;
+            font-size: 12px;
+            color: #364153;
+            white-space: nowrap;
+
+            &.info1 {
+              margin-right: 6px;
+            }
+
+            &:hover {
+              color: #e7000b;
+            }
+          }
+
+          .classify-border {
+            position: absolute;
+            right: -1px;
+            top: 0px;
+            width: 1px;
+            height: 38px;
+            background-color: #ffffff;
+            z-index: 2;
+          }
+        }
+      }
+
+      .classify2 {
+        width: 234px;
+        height: 540px;
+        background: #ffffff;
+        padding: 10px 0px;
+
+        .classify-list {
+          width: 100%;
+          height: 60px;
+          cursor: pointer;
+          position: relative;
+          display: flex;
+          flex-direction: column;
+          justify-content: center;
+          padding-left: 25px;
+
+          &.classify-hig {
+            border: 1px solid var(--el-color-primary);
+            border-right: 0px solid var(--el-color-primary);
+          }
+
+          .label {
+            width: 100%;
+            font-size: 14px;
+            color: #101828;
+            white-space: nowrap;
+            margin-right: 10px;
+            font-weight: 600;
+
+            &:hover {
+              color: var(--el-color-primary);
+            }
+          }
+
+          .two-level {
+            display: flex;
+            align-items: center;
+            font-size: 12px;
+            color: #364153;
+            margin-top: 6px;
+
+            .two-hig {
+              &:hover {
+                color: var(--el-color-primary);
+              }
+            }
+          }
+        }
+      }
+
+      // 头部中间
+      .head-bos {
+        width: 756px;
+
+        .carousel {
+          overflow: hidden;
+          width: 756px;
+          background: #ffffff;
+
+          &.carousel-type {
+            :deep(.el-carousel__arrow) {
+              border-radius: 0;
+            }
+          }
+        }
+
+        .head-box {
+          width: 756px;
+          height: 130px;
+          margin-top: 10px;
+          display: flex;
+          gap: 0 10px;
+
+          .head-item {
+            flex: 1;
+            background-color: #ffffff;
+            height: 130px;
+            display: flex;
+            align-items: center;
+            cursor: pointer;
+            border-radius: 5px;
+            overflow: hidden;
+
+            img {
+              width: 100%;
+              height: 100%;
+            }
+          }
+        }
+      }
+
+      //右边
+      .head-right {
+        flex: 1;
+        width: 0;
+        height: 540px;
+        background: #ffffff;
+        display: flex;
+        flex-direction: column;
+
+        .login-bos {
+          width: calc(100% - 20px);
+          height: 110px;
+          border-bottom: 1px solid #e5e7eb;
+          margin: 0 10px;
+          display: flex;
+          flex-direction: column;
+          justify-content: space-between;
+          padding: 16px 0;
+
+          .login-box {
+            display: flex;
+            align-items: center;
+
+            img {
+              width: 40px;
+              height: 40px;
+              margin-right: 8px;
+              border-radius: 40px;
+            }
+
+            .login1 {
+              font-size: 13px;
+              color: #444444;
+            }
+
+            .login2 {
+              margin-top: 2px;
+              font-size: 12px;
+              color: #6a7282;
+            }
+          }
+
+          .loginBtn-bos {
+            width: 100%;
+            .login-bnt1 {
+              width: 64px;
+              height: 24px;
+              background-color: #e7000b;
+              color: #ffffff;
+              border-radius: 24px;
+              font-size: 12px;
+            }
+            .login-bnt2 {
+              width: 64px;
+              height: 24px;
+              border: 1px solid #e7000b;
+              color: #e7000b;
+              border-radius: 24px;
+              font-size: 12px;
+              margin-left: 10px;
+            }
+          }
+        }
+
+        .real-time {
+          width: calc(100% - 20px);
+          // height: 230px;
+          border-bottom: 1px solid #e5e7eb;
+          margin: 0 10px;
+          padding-top: 15px;
+
+          .real-title {
+            position: relative;
+            margin-bottom: 12px;
+
+            &::after {
+              content: '';
+              top: 3px;
+              left: 0;
+              position: absolute;
+              width: 4px;
+              height: 14px;
+              background: #e7000b;
+            }
+
+            .real1 {
+              font-weight: 600;
+              font-size: 14px;
+              color: #1d2129;
+              padding-left: 15px;
+            }
+
+            .real2 {
+              font-size: 13px;
+              color: #83899f;
+              cursor: pointer;
+            }
+          }
+
+          .real-list {
+            width: 100%;
+            font-size: 14px;
+            color: #1d2129;
+            margin-bottom: 12px;
+            cursor: pointer;
+
+            &:hover {
+              color: #e7000b;
+            }
+          }
+        }
+
+        .interests {
+          flex: 1;
+          width: calc(100% - 20px);
+          margin: 0 10px;
+          padding-top: 15px;
+
+          .interests-title {
+            position: relative;
+            font-weight: 600;
+            font-size: 14px;
+            color: #1d2129;
+            padding-left: 15px;
+
+            &::after {
+              content: '';
+              top: 3px;
+              left: 0;
+              position: absolute;
+              width: 4px;
+              height: 14px;
+              background: #e7000b;
+            }
+          }
+
+          .interests-bos {
+            display: flex;
+            flex-wrap: wrap;
+
+            .interests-item {
+              width: 33.333%;
+              font-size: 12px;
+              color: #101828;
+              margin-top: 15px;
+              cursor: pointer;
+
+              &:hover {
+                color: #e7000b;
+              }
+
+              .img {
+                width: 34px;
+                height: 34px;
+                border-radius: 6px;
+                margin-bottom: 7px;
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
+</style>

+ 93 - 0
src/views/diy/pcPages/imageCube.vue

@@ -0,0 +1,93 @@
+<template>
+  <div class="pcPages" :style="warpCss">
+    <div class="imageCube-bos" :style="{ gap: componentData.gap + 'px' }">
+      <template v-for="(item, index) in componentData.imagelList" :key="index">
+        <div v-if="index < componentData.number" class="imageCube-list" :style="boxCss">
+          <el-image
+            class="img"
+            :src="item.imageUrl ? item.imageUrl : figure"
+            :fit="item.imgType == 1 ? 'fill' : item.imgType == 2 ? 'contain' : 'cover'"
+            :style="componentData.imageRadius ? { borderRadius: componentData.imageRadius + 'px' } : {}"
+          />
+        </div>
+      </template>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import usePcdiyStore from '@/store/modules/pcdiy';
+import figure from '@/assets/images/figure.png';
+const diyStore = usePcdiyStore();
+const props = defineProps<{
+  index: number; // 确保声明 index 为可选属性
+}>();
+const componentData = diyStore.componentList[props.index];
+
+const warpCss = computed(() => {
+  let style = '';
+  style += 'position:relative;';
+  //背景颜色
+  if (componentData.pageStartBgColor) {
+    if (componentData.pageStartBgColor && componentData.pageEndBgColor)
+      style += `background:linear-gradient(${componentData.pageGradientAngle},${componentData.pageStartBgColor},${componentData.pageEndBgColor});`;
+    else if (componentData.pageStartBgColor) style += `background: ${componentData.pageStartBgColor};`;
+    else if (componentData.pageEndBgColor) style += `background: ${componentData.pageEndBgColor};`;
+  }
+  //边距
+  if (componentData.padding) {
+    if (componentData.padding.top > 0) {
+      style += 'padding-top:' + componentData.padding.top + 'px' + ';';
+    }
+    if (componentData.padding.bottom > 0) {
+      style += 'padding-bottom:' + componentData.padding.bottom + 'px' + ';';
+    }
+    style += 'padding-right:' + componentData.padding.both + 'px' + ';';
+    style += 'padding-left:' + componentData.padding.both + 'px' + ';';
+  }
+  //圆角
+  if (componentData.topRounded) style += 'border-top-left-radius:' + componentData.topRounded + 'px;';
+  if (componentData.topRounded) style += 'border-top-right-radius:' + componentData.topRounded + 'px;';
+  if (componentData.bottomRounded) style += 'border-bottom-left-radius:' + componentData.bottomRounded + 'px;';
+  if (componentData.bottomRounded) style += 'border-bottom-right-radius:' + componentData.bottomRounded + 'px;';
+
+  return style;
+});
+
+//组件样式
+const boxCss = computed(() => {
+  let style = '';
+  if (componentData.componentStartBgColor && componentData.componentEndBgColor)
+    style += `background:linear-gradient(${componentData.componentGradientAngle},${componentData.componentStartBgColor},${componentData.componentEndBgColor});`;
+  else if (componentData.componentStartBgColor) style += 'background-color:' + componentData.componentStartBgColor + ';';
+  else if (componentData.componentEndBgColor) style += 'background-color:' + componentData.componentEndBgColor + ';';
+  // 宽度
+  if (componentData.number)
+    style += 'flex:' + `0 0 calc((100% - ${(componentData.number - 1) * componentData.gap}px) / ${componentData.number})` + ';';
+  //圆角
+  if (componentData.imageTopRounded) style += 'border-top-left-radius:' + componentData.imageTopRounded + 'px;';
+  if (componentData.imageTopRounded) style += 'border-top-right-radius:' + componentData.imageTopRounded + 'px;';
+  if (componentData.imageBottomRoundedRounded) style += 'border-bottom-left-radius:' + componentData.imageBottomRoundedRounded + 'px;';
+  if (componentData.imageBottomRoundedRounded) style += 'border-bottom-right-radius:' + componentData.imageBottomRoundedRounded + 'px;';
+  //高度
+  if (componentData.imageHeight) style += 'height:' + componentData.imageHeight + 'px;';
+  return style;
+});
+</script>
+
+<style lang="scss" scoped>
+.pcPages {
+  width: 1200px;
+  margin: 0 auto;
+  .imageCube-bos {
+    display: flex;
+    .imageCube-list {
+      overflow: hidden;
+      .img {
+        width: 100%;
+        height: 100%;
+      }
+    }
+  }
+}
+</style>

+ 151 - 0
src/views/diy/pcPages/navigation.vue

@@ -0,0 +1,151 @@
+<template>
+  <div class="pcPages">
+    <div class="carousel-bos" :style="warpCss" v-if="componentData.styleType == 3">
+      <el-carousel :height="170 * componentData.count + (componentData.count == 2 ? 10 : 0) + 'px'" :autoplay="false" arrow="always">
+        <el-carousel-item v-for="(item1, index1) in dataList" :key="index1" class="w100% h100%">
+          <div class="carousel-list">
+            <div v-for="(item, index) in item1" :key="index" class="data-list flex-column-center" :style="boxCss">
+              <div :style="titleCss">{{ item.title || '' }}</div>
+              <div :style="subtitleCss" class="mt-[2px] mb-[12px]">{{ item.subtitle || '' }}</div>
+              <el-image
+                class="img"
+                :src="item.imageUrl ? item.imageUrl : figure"
+                :fit="item.imgType == 1 ? 'fill' : item.imgType == 2 ? 'contain' : 'cover'"
+                :style="componentData.imageRadius ? { borderRadius: componentData.imageRadius + 'px' } : {}"
+              />
+            </div>
+          </div>
+        </el-carousel-item>
+      </el-carousel>
+    </div>
+    <div v-else :style="warpCss" class="data-bos">
+      <div v-for="(item, index) in componentData.navlList" :key="index" class="data-list flex-column-center" :style="boxCss">
+        <div :style="titleCss">{{ item.title || '' }}</div>
+        <div :style="subtitleCss" class="mt-[2px] mb-[12px]">{{ item.subtitle || '' }}</div>
+        <el-image
+          class="img"
+          :src="item.imageUrl ? item.imageUrl : figure"
+          :fit="item.imgType == 1 ? 'fill' : item.imgType == 2 ? 'contain' : 'cover'"
+          :style="componentData.imageRadius ? { borderRadius: componentData.imageRadius + 'px' } : {}"
+        />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import usePcdiyStore from '@/store/modules/pcdiy';
+import figure from '@/assets/images/figure.png';
+const diyStore = usePcdiyStore();
+const props = defineProps<{
+  index: number; // 确保声明 index 为可选属性
+}>();
+const componentData = diyStore.componentList[props.index];
+
+const dataList = computed(() => {
+  const chunkSize = componentData.number * componentData.count;
+  const result = [];
+  for (let i = 0; i < componentData.navlList.length; i += chunkSize) {
+    const chunk = componentData.navlList.slice(i, i + chunkSize);
+    result.push(chunk);
+  }
+  return result;
+});
+
+const warpCss = computed(() => {
+  let style = '';
+  style += 'position:relative;';
+  //背景颜色
+  if (componentData.pageStartBgColor) {
+    if (componentData.pageStartBgColor && componentData.pageEndBgColor)
+      style += `background:linear-gradient(${componentData.pageGradientAngle},${componentData.pageStartBgColor},${componentData.pageEndBgColor});`;
+    else if (componentData.pageStartBgColor) style += `background: ${componentData.pageStartBgColor};`;
+    else if (componentData.pageEndBgColor) style += `background: ${componentData.pageEndBgColor};`;
+  }
+  //边距
+  if (componentData.padding) {
+    if (componentData.padding.top > 0) {
+      style += 'padding-top:' + componentData.padding.top + 'px' + ';';
+    }
+    if (componentData.padding.bottom > 0) {
+      style += 'padding-bottom:' + componentData.padding.bottom + 'px' + ';';
+    }
+    style += 'padding-right:' + componentData.padding.both + 'px' + ';';
+    style += 'padding-left:' + componentData.padding.both + 'px' + ';';
+    if (componentData.styleType == 1) style += 'flex-wrap:wrap' + ';';
+  }
+  return style;
+});
+
+//组件样式
+const boxCss = computed(() => {
+  let style = '';
+  if (componentData.componentStartBgColor && componentData.componentEndBgColor)
+    style += `background:linear-gradient(${componentData.componentGradientAngle},${componentData.componentStartBgColor},${componentData.componentEndBgColor});`;
+  else if (componentData.componentStartBgColor) style += 'background-color:' + componentData.componentStartBgColor + ';';
+  else if (componentData.componentEndBgColor) style += 'background-color:' + componentData.componentEndBgColor + ';';
+  if (componentData.number) style += 'flex:' + `0 0 calc((100% - ${(componentData.number - 1) * 10}px) / ${componentData.number})` + ';';
+  //圆角
+  if (componentData.topRounded) style += 'border-top-left-radius:' + componentData.topRounded + 'px;';
+  if (componentData.topRounded) style += 'border-top-right-radius:' + componentData.topRounded + 'px;';
+  if (componentData.bottomRounded) style += 'border-bottom-left-radius:' + componentData.bottomRounded + 'px;';
+  if (componentData.bottomRounded) style += 'border-bottom-right-radius:' + componentData.bottomRounded + 'px;';
+  return style;
+});
+
+// 标题样式
+const titleCss = computed(() => {
+  let style = '';
+  if (componentData.titleColor) style += 'color:' + componentData.titleColor + ';';
+  if (componentData.titleSize) style += 'font-size:' + componentData.titleSize + 'px;';
+  if (componentData.titleWeight) style += 'font-weight:' + componentData.titleWeight + ';';
+  return style;
+});
+
+// 副标题样式
+const subtitleCss = computed(() => {
+  let style = '';
+  if (componentData.subtitleColor) style += 'color:' + componentData.subtitleColor + ';';
+  if (componentData.subtitleSize) style += 'font-size:' + componentData.subtitleSize + 'px;';
+  return style;
+});
+</script>
+
+<style lang="scss" scoped>
+.pcPages {
+  width: 1200px;
+  margin: 0 auto;
+  .data-bos {
+    display: flex;
+    gap: 10px;
+    width: 100%;
+    overflow-x: auto;
+    .data-list {
+      min-height: 170px;
+
+      .img {
+        height: 80px;
+        width: 80px;
+      }
+    }
+  }
+
+  .carousel-bos {
+    .carousel-list {
+      width: 100%;
+      height: 100%;
+      display: flex;
+      gap: 10px;
+      flex-wrap: wrap;
+      .data-list {
+        height: 170px;
+
+        .img {
+          height: 80px;
+          width: 80px;
+        }
+      }
+    }
+  }
+}
+</style>

+ 457 - 0
src/views/diy/pcPages/textTitle.vue

@@ -0,0 +1,457 @@
+<template>
+  <div class="pcPages">
+    <div :style="warpCss">
+      <!-- 风格1 -->
+      <div :style="boxCss" v-if="componentData.styleType == 1">
+        <div :style="titleCss">{{ componentData.title }}</div>
+      </div>
+      <!-- 风格2 -->
+      <div :style="boxCss" class="style2 flex-row-center" v-else-if="componentData.styleType == 2">
+        <div class="style2-border" :style="{ backgroundColor: componentData.titleColor }"></div>
+        <div class="style2-text" :style="titleCss">{{ componentData.title }}</div>
+        <div class="style2-border" :style="{ backgroundColor: componentData.titleColor }"></div>
+      </div>
+      <!-- 风格3 -->
+      <div :style="boxCss" class="style3 flex-column-center" v-else-if="componentData.styleType == 3">
+        <div :style="titleCss" class="mb-[4px]">{{ componentData.title }}</div>
+        <div class="style3-border" :style="{ backgroundColor: componentData.titleColor }"></div>
+        <div class="style3-jiao" :style="{ borderColor: componentData.titleColor }"></div>
+      </div>
+      <!-- 风格4 -->
+      <div :style="boxCss" class="style4 flex-column-center" v-else-if="componentData.styleType == 4">
+        <div :style="titleCss" class="mb-[4px]">{{ componentData.title }}</div>
+        <div class="style4-border1" :style="{ backgroundColor: componentData.titleColor }"></div>
+        <div class="style4-border2" :style="{ backgroundColor: componentData.titleColor }"></div>
+      </div>
+      <!-- 风格5 -->
+      <div :style="boxCss" class="style5 flex-column-center" v-else-if="componentData.styleType == 5">
+        <div :style="titleCss" class="mb-[4px]">{{ componentData.title }}</div>
+        <div class="flex-row-center">
+          <div class="style5-border" :style="{ backgroundColor: componentData.titleColor }"></div>
+          <div class="style5-box" :style="{ borderColor: componentData.titleColor }"></div>
+          <div class="style5-border" :style="{ backgroundColor: componentData.titleColor }"></div>
+        </div>
+      </div>
+      <!-- 风格6 -->
+      <div :style="boxCss" class="style6 flex-column-center" v-else-if="componentData.styleType == 6">
+        <div class="style6-bos flex-row-center" :style="{ borderColor: componentData.titleColor }">
+          <div class="style6-border style6-border1" :style="{ backgroundColor: componentData.titleColor }"></div>
+          <div :style="titleCss" class="style6-box">{{ componentData.title }}</div>
+          <div class="style6-border style6-border2" :style="{ backgroundColor: componentData.titleColor }"></div>
+        </div>
+      </div>
+      <!-- 风格7 -->
+      <div :style="boxCss" class="style7 flex-column-center" v-else-if="componentData.styleType == 7">
+        <div class="style7-bos">
+          <div class="style7-text" :style="titleCss">{{ componentData.title }}</div>
+          <div class="style7-line" :style="{ borderColor: componentData.titleColor }"></div>
+        </div>
+      </div>
+      <!-- 风格8 -->
+      <div :style="boxCss" class="style8 flex-column-center" v-else-if="componentData.styleType == 8">
+        <div class="style8-bos">
+          <div class="style8-text" :style="titleCss">{{ componentData.title }}</div>
+          <div class="style8-line" :style="{ borderColor: componentData.titleColor }"></div>
+        </div>
+      </div>
+      <!-- 风格9 -->
+      <div :style="boxCss" class="style9 flex-row-start" v-else-if="componentData.styleType == 9">
+        <div class="style9-border" :style="{ backgroundColor: componentData.titleColor }"></div>
+        <div class="pl-[10px]" :style="titleCss">{{ componentData.title }}</div>
+      </div>
+      <!-- 风格10 -->
+      <div :style="boxCss" class="style10 flex-column-center" v-else-if="componentData.styleType == 10">
+        <div class="style10-bos flex-row-start">
+          <img class="style10-img1" src="@/assets/images/pcdiy/style10-1.png" alt="" />
+          <div :style="titleCss">{{ componentData.title }}</div>
+          <img class="style10-img2" src="@/assets/images/pcdiy/style10-2.png" alt="" />
+        </div>
+        <div class="mt-[2px]" :style="subtitleCss">
+          {{ componentData.subtitle }}
+        </div>
+      </div>
+      <!-- 风格11 -->
+      <div :style="boxCss" class="style11 flex-column-center" v-else-if="componentData.styleType == 11">
+        <div class="style11-bos flex-row-start">
+          <img class="style11-img style11-img1" src="@/assets/images/pcdiy/style11-1.png" alt="" />
+          <div :style="titleCss" class="style11-text">{{ componentData.title }}</div>
+          <img class="style11-img style11-img2" src="@/assets/images/pcdiy/style11-2.png" alt="" />
+          <img class="style11-img3" src="@/assets/images/pcdiy/style11-3.png" alt="" />
+        </div>
+        <div class="mt-[2px]" :style="subtitleCss">
+          {{ componentData.subtitle }}
+        </div>
+      </div>
+      <!-- 风格12 -->
+      <div :style="boxCss" class="style12 flex-row-between" v-else-if="componentData.styleType == 12">
+        <div class="flex-row-start">
+          <div :style="titleCss">{{ componentData.title }}</div>
+          <div :style="subtitleCss" class="ml-[5px]">{{ componentData.subtitle }}</div>
+        </div>
+        <div v-if="componentData.moreShow" class="flex-row-start" :style="moreCss">
+          <div>{{ componentData.more }}</div>
+          <el-icon><ArrowRight /></el-icon>
+        </div>
+      </div>
+      <!-- 风格13 -->
+      <div :style="boxCss" class="style13 flex-column-center" v-else-if="componentData.styleType == 13">
+        <div class="style13-bos flex-row-start">
+          <img class="style13-img1" src="@/assets/images/pcdiy/style13-1.png" alt="" />
+          <div :style="titleCss">{{ componentData.title }}</div>
+          <img class="style13-img2" src="@/assets/images/pcdiy/style13-2.png" alt="" />
+        </div>
+      </div>
+      <!-- 风格14 -->
+      <div :style="boxCss" class="style14 flex-row-between" v-else-if="componentData.styleType == 14">
+        <div>
+          <div :style="titleCss">{{ componentData.title }}</div>
+          <div :style="subtitleCss">{{ componentData.subtitle }}</div>
+        </div>
+        <div v-if="componentData.moreShow" class="flex-row-start" :style="moreCss">
+          <div>{{ componentData.more }}</div>
+          <el-icon><ArrowRight /></el-icon>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup name="Index" lang="ts">
+import usePcdiyStore from '@/store/modules/pcdiy';
+const diyStore = usePcdiyStore();
+
+const props = defineProps<{
+  index: number; // 确保声明 index 为可选属性
+}>();
+const componentData = diyStore.componentList[props.index];
+
+const warpCss = computed(() => {
+  let style = '';
+  style += 'position:relative;';
+  //背景颜色
+  if (componentData.pageStartBgColor) {
+    if (componentData.pageStartBgColor && componentData.pageEndBgColor)
+      style += `background:linear-gradient(${componentData.pageGradientAngle},${componentData.pageStartBgColor},${componentData.pageEndBgColor});`;
+    else if (componentData.pageStartBgColor) style += `background: ${componentData.pageStartBgColor};`;
+    else if (componentData.pageEndBgColor) style += `background: ${componentData.pageEndBgColor};`;
+  }
+  //背景图片
+  if (componentData.componentBgUrl) {
+    style += `background-image:url('${componentData.componentBgUrl}');`;
+    style += 'background-size: cover;background-repeat: no-repeat;';
+  }
+  //边距
+  if (componentData.padding) {
+    if (componentData.padding.top > 0) {
+      style += 'padding-top:' + componentData.padding.top + 'px' + ';';
+    }
+    if (componentData.padding.bottom > 0) {
+      style += 'padding-bottom:' + componentData.padding.bottom + 'px' + ';';
+    }
+    style += 'padding-right:' + componentData.padding.both + 'px' + ';';
+    style += 'padding-left:' + componentData.padding.both + 'px' + ';';
+  }
+  //圆角
+  if (componentData.topRounded) style += 'border-top-left-radius:' + componentData.topRounded + 'px;';
+  if (componentData.topRounded) style += 'border-top-right-radius:' + componentData.topRounded + 'px;';
+  if (componentData.bottomRounded) style += 'border-bottom-left-radius:' + componentData.bottomRounded + 'px;';
+  if (componentData.bottomRounded) style += 'border-bottom-right-radius:' + componentData.bottomRounded + 'px;';
+
+  return style;
+});
+
+//组件样式
+const boxCss = computed(() => {
+  let style = '';
+  if (componentData.componentStartBgColor && componentData.componentEndBgColor)
+    style += `background:linear-gradient(${componentData.componentGradientAngle},${componentData.componentStartBgColor},${componentData.componentEndBgColor});`;
+  else if (componentData.componentStartBgColor) style += 'background-color:' + componentData.componentStartBgColor + ';';
+  else if (componentData.componentEndBgColor) style += 'background-color:' + componentData.componentEndBgColor + ';';
+  return style;
+});
+
+// 标题样式
+const titleCss = computed(() => {
+  let style = '';
+  if (componentData.titleColor) style += 'color:' + componentData.titleColor + ';';
+  if (componentData.titleSize) style += 'font-size:' + componentData.titleSize + 'px;';
+  if (componentData.titleWeight) style += 'font-weight:' + componentData.titleWeight + ';';
+  if (componentData.titleAlign && componentData.styleType == 1) style += 'text-align:' + componentData.titleAlign;
+  if (componentData.titleColor && (componentData.styleType == 6 || componentData.styleType == 7)) {
+    style += 'border-color:' + componentData.titleColor;
+  }
+  if (componentData.titleColor && componentData.styleType == 8) {
+    style += 'background-color:' + componentData.titleColor;
+  }
+  return style;
+});
+
+// 副标题样式
+const subtitleCss = computed(() => {
+  let style = '';
+  if (componentData.subtitleColor) style += 'color:' + componentData.subtitleColor + ';';
+  if (componentData.subtitleSize) style += 'font-size:' + componentData.subtitleSize + 'px;';
+  return style;
+});
+
+// 更多样式
+const moreCss = computed(() => {
+  let style = '';
+  if (componentData.moreColor) style += 'color:' + componentData.moreColor + ';';
+  if (componentData.moreSize) style += 'font-size:' + componentData.moreSize + 'px;';
+  return style;
+});
+
+// 背景图加遮罩层
+const maskLayer = computed(() => {
+  let style = '';
+  //背景颜色
+  if (componentData.componentStartBgColor) {
+    if (componentData.componentStartBgColor && componentData.componentEndBgColor)
+      style += `background:linear-gradient(${componentData.pageGradientAngle},${componentData.componentStartBgColor},${componentData.pageEndBgColor});`;
+    else if (componentData.componentStartBgColor) style += `background: ${componentData.componentStartBgColor};`;
+    else if (componentData.componentEndBgColor) style += `background: ${componentData.componentEndBgColor};`;
+  }
+
+  return style;
+});
+</script>
+
+<style lang="scss" scoped>
+.pcPages {
+  width: 1200px;
+  margin: 0 auto;
+
+  .style2 {
+    .style2-text {
+      margin: 0 30px;
+    }
+
+    .style2-border {
+      width: 160px;
+      height: 1px;
+    }
+  }
+
+  .style3 {
+    position: relative;
+
+    .style3-border {
+      width: 260px;
+      height: 1px;
+    }
+
+    .style3-jiao {
+      background: transparent !important;
+      display: inline-block;
+      border: 6px solid #cccccc;
+      border-top-color: transparent !important;
+      border-left-color: transparent !important;
+      -webkit-transform: rotate(45deg);
+      transform: rotate(45deg);
+      margin-top: -6px;
+    }
+  }
+
+  .style4 {
+    .style4-border1 {
+      width: 160px;
+      height: 2px;
+    }
+
+    .style4-border2 {
+      width: 260px;
+      height: 1px;
+    }
+  }
+
+  .style5 {
+    .style5-box {
+      width: 10px;
+      height: 10px;
+      border: 1px solid #000;
+      display: inline-block;
+      -webkit-transform: rotate(45deg);
+      transform: rotate(45deg);
+      margin: 0 10px;
+    }
+
+    .style5-border {
+      width: 160px;
+      height: 1px;
+    }
+  }
+
+  .style6 {
+    .style6-bos {
+      border: 1px solid #ccc;
+      padding: 4px 4px;
+      position: relative;
+
+      .style6-box {
+        border: 1px solid #ccc;
+        padding: 2px 14px;
+      }
+
+      .style6-border {
+        width: 50px;
+        height: 4px;
+        position: absolute;
+        top: 50%;
+
+        &.style6-border1 {
+          left: -40px;
+        }
+
+        &.style6-border2 {
+          right: -40px;
+        }
+      }
+    }
+  }
+
+  .style7 {
+    .style7-bos {
+      position: relative;
+      margin-bottom: 6px;
+
+      .style7-text {
+        padding: 2px 14px;
+        position: relative;
+        z-index: 2;
+        background-color: #ffffff;
+        border: 1px solid #ccc;
+      }
+
+      .style7-line {
+        position: absolute;
+        width: 100%;
+        height: 100%;
+        border: 1px solid #ccc;
+        top: 6px;
+        left: 6px;
+        z-index: 0;
+      }
+    }
+  }
+
+  .style8 {
+    .style8-bos {
+      position: relative;
+      margin-bottom: 6px;
+
+      .style8-text {
+        padding: 2px 14px;
+        position: relative;
+        z-index: 2;
+        color: #ffffff !important;
+      }
+
+      .style8-line {
+        position: absolute;
+        width: 100%;
+        height: 100%;
+        border: 1px solid #ccc;
+        top: 6px;
+        left: 6px;
+        z-index: 0;
+      }
+    }
+  }
+
+  .style9 {
+    position: relative;
+
+    .style9-border {
+      height: 80%;
+      width: 3px;
+      position: absolute;
+    }
+  }
+
+  .style10 {
+    .style10-bos {
+      height: 100%;
+      position: relative;
+
+      img {
+        height: 100%;
+        position: absolute;
+
+        &.style10-img1 {
+          left: -70%;
+        }
+
+        &.style10-img2 {
+          right: -70%;
+        }
+      }
+    }
+  }
+
+  .style11 {
+    .style11-bos {
+      height: 100%;
+      position: relative;
+
+      .style11-img {
+        height: 100%;
+        position: absolute;
+
+        &.style11-img1 {
+          left: -60%;
+        }
+
+        &.style11-img2 {
+          right: -60%;
+        }
+      }
+
+      .style11-text {
+        position: relative;
+        z-index: 2;
+      }
+
+      .style11-img3 {
+        position: absolute;
+        width: 120%;
+        bottom: 0px;
+        left: -10%;
+      }
+    }
+  }
+
+  .style13 {
+    .style13-bos {
+      height: 100%;
+      position: relative;
+
+      img {
+        height: 100%;
+        position: absolute;
+
+        &.style13-img1 {
+          left: -80%;
+        }
+
+        &.style13-img2 {
+          right: -80%;
+        }
+      }
+    }
+  }
+}
+
+.textTitle-pages {
+  width: 100%;
+
+  .textTitle-bos {
+    background-color: #ffffff;
+    line-height: 45px;
+    padding-left: 20px;
+    font-size: 18px;
+    color: #333333;
+    font-weight: bold;
+  }
+}
+</style>