低代码扫盲: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

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

最终实现的效果如下

渲染数据如下

相关推荐
有梦想的刺儿19 分钟前
webWorker基本用法
前端·javascript·vue.js
cy玩具40 分钟前
点击评论详情,跳到评论页面,携带对象参数写法:
前端
customer081 小时前
【开源免费】基于SpringBoot+Vue.JS周边产品销售网站(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·java-ee·开源
qq_390161771 小时前
防抖函数--应用场景及示例
前端·javascript
John.liu_Test2 小时前
js下载excel示例demo
前端·javascript·excel
Yaml42 小时前
智能化健身房管理:Spring Boot与Vue的创新解决方案
前端·spring boot·后端·mysql·vue·健身房管理
PleaSure乐事2 小时前
【React.js】AntDesignPro左侧菜单栏栏目名称不显示的解决方案
前端·javascript·react.js·前端框架·webstorm·antdesignpro
哟哟耶耶2 小时前
js-将JavaScript对象或值转换为JSON字符串 JSON.stringify(this.SelectDataListCourse)
前端·javascript·json
getaxiosluo2 小时前
react jsx基本语法,脚手架,父子传参,refs等详解
前端·vue.js·react.js·前端框架·hook·jsx
理想不理想v2 小时前
vue种ref跟reactive的区别?
前端·javascript·vue.js·webpack·前端框架·node.js·ecmascript