低代码扫盲:Vite+vue3实现C端活动配置

最近,接手公司之前的低代码配置项目,项目是用来配置C端活动页的。使用中,发现了很多弊端,问题一大堆,惨不忍睹😂😂😂,正好组内也是有项目迁移的打算,然后就自己动手实现了一个。话不多说,开整😏😏

一、初始化项目

活动配置类的低代码项目,除了配置平台,还有一个承载页展示。我们用lerna来初始化项目,方便管理。

js 复制代码
mkdir lowCode 
cd lowCode
lerna init --independent
mkdir packages
cd packages

新建两个项目low-code-page(C端承载页)、low-code-set(配置平台)。

生成后的目录。

将项目中package.json中的"private": true去掉,使用lerna list,便能看到lerna下的两个项目了。

1、添加依赖

low-code-set, vant用于中间中间展示配置,vuedraggable拖拽组件核心

js 复制代码
npm i vant element-plus @element-plus/icons-vue vue-router@4 vuedraggable less -D

low-code-page

js 复制代码
npm i vant less -D
2、项目配置

low-code-set 添加配置页面

js 复制代码
// packages/low-code-set/src/views/pageSet/index.vue
<template>
  <div class="content">
    <div class="set">
      <div class="left">
      </div>
      <div class="center">
       
      </div>
      <div class="right">
      </div>
    </div>

    <div class="bottom">
      <button @click="handleSave">保存</button>
      <button @click="handlePreview">预览</button>
    </div>
  </div>
</template>
<script setup lang="ts">

const handleSave = () => {
};
const handlePreview = () => {
};
</script>
<style scoped lang="less">
.content {
  display: flex;
  flex-direction: column;
  height: 100%;
  overflow: auto;
  background: #efefef;
  .left,
  .right {
    flex: 1;
    height: 100%;
    overflow: auto;
    background: #fff;
    padding: 20px;
  }
  .center {
    width:375px;  // 设置宽度为普通屏幕标准750/2
    height: 100%;
    overflow: auto;
    background: #fff;
    margin: 0 20px;
    position: relative;
    .render-draggable {
      height: 100%;
      overflow: auto;
    }
  }
}
.item {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: 80px;
  height: 80px;
  border: 1px solid #efefef;
}
.left-draggable {
  display: flex;
}
.set {
  display: flex;
  flex: 1;
  overflow: auto;
}
.bottom {
  text-align: center;
  padding: 15px;
  button {
    padding: 5px 20px;
    border-radius: 5px;
    cursor: pointer;
    &:first-child {
      background: #0097ff;
      color: #fff;
      border: none;
    }
    &:not(:first-child) {
      margin-left: 10px;
      border: 1px solid #efefef;
    }
  }
}
:deep(.select-comp) {
  border: 1px dashed #efefef;
}
</style>

其余项目配置不是重点,此处就不阐述了。启动项目后页面如下, 左边是组件摆放区,中间是渲染区,右侧是组件配置区。

low-code-page就很简单了,等会和渲染一起讲。

实现

组件

我们先在packages/low-code-set/src/components目录下实现1个基础组件image。

配置组件信息

js 复制代码
// packages/low-code-set/src/components/configs.ts
const configs = [
  {
    groupName: '其他',
    components: [
      {
        //渲染组件名
        render: 'Image',
        name: '图片',
        //组件区图片
        icon: 'Picture',
        //配置数据
        configData: {
          style: 'width:100%',
          url: 'https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg'
        }
      }
    ]
  },
]

export default configs

创建组件文件,config是右侧配置信息,render是中间渲染,index负责注册组件

js 复制代码
// config.vue
<template>
  <el-form :model="configData" label-width="80px">
    <el-form-item label="样式">
      <el-input v-model="configData.style" />
    </el-form-item>
    <el-form-item label="图片链接">
      <el-input v-model="configData.url" />
    </el-form-item>
  </el-form>
</template>
<script setup lang="ts">
import { computed } from "vue";
const props = defineProps<{
  data: any;
}>();

const configData = computed(() => props.data);
</script>
js 复制代码
// render.vue
<template>
  <img :src="configData.url" :style="configData.style" />
</template>
<script setup lang="ts">
import { computed } from "vue";

const props = defineProps<{
  data: any;
}>();

const configData = computed(() => props.data.configData);
</script>
js 复制代码
// index.ts
import Config from './config.vue'
import Render from './render.vue'

const install = (app:any) => {
  app.component('ImageConfig',Config)
  app.component('Image',Render)
}

export default install

使用组件

js 复制代码
//packages/low-code-set/src/components/index.ts
import Image from './image'

const install = (app: any) => {
  app.use(Image)
}
export default install

在packages/low-code-set/src/main.ts中引入使用

diff 复制代码
import { createApp } from 'vue'
import router from './router'
import App from './App.vue'
import ElementPlus from 'element-plus'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
+ import Components from './components'
import 'element-plus/dist/index.css'
import './style.css'
import 'vant/lib/index.css';

const app = createApp(App);
-app.use(router).use(ElementPlus).mount('#app')
+app.use(router).use(Components).use(ElementPlus).mount('#app')

for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
  app.component(key, component)
}

有了上面的组件后,就需要实现拖拽了,这也是重点,但不是难点😄😄😄😄。

拖拽

拖拽的实现借助vuedraggable 来实现。

1、根据组件配置渲染左侧组件区。

需要注意的是,draggable一定要配置clone,不然三个数据都是指向同一个地址,会出现改一动全的bug,sort=false,防止组件区的相互拖拽

diff 复制代码
<template>
  <div class="content">
    <div class="set">
      <div class="left">
+        <div v-for="group in componentConfig" :key="group.groupName">
+         <div>
+            {{ group.groupName }}
+          </div>
+         <draggable
+           :group="{ name: group.groupName, pull: 'clone', put: false }"
+           :list="group.components"
+           animation="300"
+           :sort="false"
+           :clone="clone"
+           @end="handleEnd"
+           class="left-draggable"
+         >
+            <template #item="{ element }">
+              <div class="item">
+               <component :is="element.icon" style="width: 20px" />
+               <span>{{ element.name }}</span>
+             </div>
+           </template>
+         </draggable>
+       </div>
     </div>
      <div class="center"></div>
      <div class="right"></div>
    </div>

    <div class="bottom">
      <button @click="handleSave">保存</button>
      <button @click="handlePreview">预览</button>
    </div>
  </div>
</template>
<script setup lang="ts">
+ import draggable from "vuedraggable";
+ import componentConfig from "@/components/configs";
// 拖拽结束回调
+ const handleEnd = (evt: any) => {
+  console.log("handleEnd==>", evt);
+ };
const handleSave = () => {};
const handlePreview = () => {};

+ const clone = (obj: any) => {
+  // 深拷贝一个对象,否则三个数据指向的都是一个地址
+  const newObj = JSON.parse(JSON.stringify(obj));
+  return newObj;
+};
</script>
......

2、中间接收区

diff 复制代码
......
<div class="center">
+        <!-- 用于接收拖拽数据 -->
+        <draggable
+          :list="renderList"
+          :group="{ name: 'renderList', pull: true, put: true }"
+          animation="300"
+          class="render-draggable"
+        >
+          <template #item="{ element }">
+            <component
+              :is="element.render"
+              :data="element"
+              :class="{ 'select-comp': currComp === element }"
+              @click="() => handleSelectComponent(element)"
+            ></component>
+          </template>
+        </draggable>
      </div>
......
<script setup lang="ts">
+ import { ref } from "vue";
  import draggable from "vuedraggable";
  import componentConfig from "@/components/configs";
  
//渲染区数据
+ const renderList = ref([]);
//当前选中设置组件
+ const currComp = ref({});
const handleEnd = (evt: any) => {
  console.log("handleEnd==>", evt);
};

+ const handleSelectComponent = (component: any) => {  
+  currComp.value = component;
+ };
const handleSave = () => {};
const handlePreview = () => {};
const clone = (obj: any) => {
  // 深拷贝一个对象,否则三个数据指向的都是一个地址
  const newObj = JSON.parse(JSON.stringify(obj));
  return newObj;
};
</script>
......

右侧配置区

右侧配置区就很简单了,直接拿到当前选中的组件,将configData传过去,如下

diff 复制代码
......
<div class="right">
+        <div>{{ currComp.name }}</div>
+       <component
+          :is="`${currComp.render}Config`"
+          :data="currComp.configData"
+        ></component>
</div>

至此,配置端的核心已经完成了70%😄😄😄😄。

完善

配置中,少不了容器组件Container、图片热点区域组件HotArea。

Container

container/config.vue

js 复制代码
// container/config.vue
<template>
  <el-form :model="configData" label-width="80px">
    <el-form-item label="样式">
      <el-input v-model="configData.style" />
    </el-form-item>
  </el-form>
</template>
<script setup lang="ts">
import { computed } from "vue";
const props = defineProps<{
  data: any;
}>();

const configData = computed(() => props.data);
</script>

container/render.vue,也是一个draggable组件,可以往里或往外拖组件。通过inject引用根组件当前的选中组件与组件点击事件。

js 复制代码
// container/render.vue
<template>
  <draggable
    :list="list"
    :group="{ name: 'renderList', pull: true, put: true }"
    animation="300"
    :style="configData.style"
  >
    <template #item="{ element }">
      <component
        :is="element.render"
        :data="element"
        :class="{ 'select-comp': currComp === element }"
        @click.stop="() => handleSelectComponent(element)"
      ></component>
    </template>
  </draggable>
</template>
<script setup lang="ts">
import { computed, ref, inject } from "vue";
import draggable from "vuedraggable";

const currComp = inject("currComp");
const handleSelectComponent = inject("handleSelectComponent");
const props = defineProps<{
  data: any;
}>();
const list = computed(() => props.data.children);
const configData = computed(() => props.data.configData);
</script>

在根组件中通过provide提供给子组件引用

js 复制代码
// packages/low-code-set/src/views/pageSet/index.vue
import { ref,provide } from "vue";

provide("currComp", currComp);
provide("handleSelectComponent", handleSelectComponent);

container/index.ts

js 复制代码
//container/index.ts
import Config from './config.vue'
import Render from './render.vue'

const install = (app:any) => {
  app.component('ContainerConfig',Config)
  app.component('Container',Render)
}

export default install

应用组件

diff 复制代码
// packages/low-code-set/src/components/configs.ts
const configs = [
+  {
+    groupName: '基础组件',
+    components: [
+      {
+        render: 'Container',
+        name: '容器',
+        icon: 'House',
+        configData: {
+          style: 'position:relative;width:100%;min-height:100px;',
+        },
+       // 子组件
+        children: []
+      }
+    ]
+  },
  {
    groupName: '其他',
    components: [
      {
        render: 'Image',
        name: '图片',
        icon: 'Picture',
        configData: {
          style: 'width:100%',
          url: 'https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg'
        }
      }
    ]
  },
]

export default configs
diff 复制代码
import Image from './image'
+import Container from './container'

const install = (app: any) => {
  app.use(Image)
+  app.use(Container)
}
export default install

这样,我们就实现了Contanier组件,可以把组件拖到Contanier容器中。

HotArea

图片热点区域实现是一个难点,因为篇幅原因,会再用一章来解释图片热点的实现。

最终实现的效果如下

渲染数据如下

相关推荐
NiNg_1_23417 分钟前
npm、yarn、pnpm之间的区别
前端·npm·node.js
秋殇与星河19 分钟前
CSS总结
前端·css
BigYe程普41 分钟前
我开发了一个出海全栈SaaS工具,还写了一套全栈开发教程
开发语言·前端·chrome·chatgpt·reactjs·个人开发
余生H1 小时前
前端的全栈混合之路Meteor篇:关于前后端分离及与各框架的对比
前端·javascript·node.js·全栈
花花鱼1 小时前
@antv/x6 导出图片下载,或者导出图片为base64由后端去处理。
vue.js
程序员-珍1 小时前
使用openapi生成前端请求文件报错 ‘Token “Integer“ does not exist.‘
java·前端·spring boot·后端·restful·个人开发
axihaihai1 小时前
网站开发的发展(后端路由/前后端分离/前端路由)
前端
流烟默1 小时前
Vue中watch监听属性的一些应用总结
前端·javascript·vue.js·watch
2401_857297911 小时前
招联金融2025校招内推
java·前端·算法·金融·求职招聘
茶卡盐佑星_2 小时前
meta标签作用/SEO优化
前端·javascript·html