Explorar o código

feat: 新增动态数据获取样式配置

liaojiaxing hai 10 meses
pai
achega
8a3464e50e

+ 11 - 8
src/components/Charts/Bar/BasicBar/src/Config.vue

@@ -1,24 +1,27 @@
 <template>
-  <div class="config-tab">
-    <ElRadioGroup v-model="activeTab">
-      <ElRadioButton value="1">数据</ElRadioButton>
-      <ElRadioButton value="2">样式</ElRadioButton>
-    </ElRadioGroup>
-  </div>
+  <div class="chart-config">
+    <div class="config-tab">
+      <ElRadioGroup v-model="activeTab" size="small">
+        <ElRadioButton value="1">数据</ElRadioButton>
+        <ElRadioButton value="2">样式</ElRadioButton>
+      </ElRadioGroup>
+    </div>
 
-  <DataConfig v-if="activeTab === '1'" :dataSource="dataSource" @change="handleChange"/>
+    <DataConfig v-if="activeTab === '1'" :dataSource="dataSource" @change="handleChange"/>
+    <StyleConfig v-if="activeTab === '2'" @change="handleChange"/>
+  </div>
 </template>
 
 <script setup lang="ts">
 import { ref, defineProps, defineEmits } from 'vue';
 import { ElRadioGroup, ElRadioButton } from 'element-plus';
 import DataConfig from '@/components/Charts/DataConfig.vue';
+import StyleConfig from '@/components/Charts/StyleConfig.vue';
 import { basicBarProps } from './props';
 
 const props = defineProps(basicBarProps);
 const activeTab = ref('1');
 const emit = defineEmits(['change']);
-console.log(props);
 
 const handleChange = (data: any) => {
   emit('change', {

+ 2 - 2
src/components/Charts/Bar/BasicBar/src/index.vue

@@ -1,5 +1,5 @@
 <template>
-  <Echarts :width="width" :height="height" :echarts-options="options"></Echarts>
+  <Echarts :width="width" :height="height" :echarts-options="options" :loading="loading"></Echarts>
 </template>
 
 <script setup lang="ts">
@@ -10,7 +10,7 @@ import { useChartOptions } from "@/components/Charts/hooks/useChartOptions";
 
 const props = defineProps(basicBarProps);
 
-const { options } = useChartOptions(props);
+const { options, loading } = useChartOptions(props);
 
 </script>
 

+ 21 - 0
src/components/Charts/Bar/BasicBar/src/props.ts

@@ -93,6 +93,27 @@ export const defaultPropsValue: EChartsOption = {
           },
         ]
       },
+      url: location.origin + "/mock/api/get/example/bar",
+      method: "POST",
+      params: {},
+      headers: {},
+      refreshTime: 0,
+      dataProcess: `
+        (res) => {
+          // 取出列表
+          const data = res.data;
+          // x轴数据
+          const xData = data.map((item) => item.name); 
+          // 系列数据
+          const series = [
+            { type: 'bar', name: '价格', data: data.map(item => item.price) },
+            { type: 'bar', name: '总量', data: data.map(item => item.count) },
+          ];
+
+          // 返回图表数据
+          return { xData, series };
+        }
+      `
     },
     ...chartOptions
   },

+ 10 - 6
src/components/Charts/DataConfig.vue

@@ -3,7 +3,8 @@
     size="small"
     layout="horizontal"
     :model="formModel"
-    :label-col="{ span: 6 }"
+    :label-col="{ span: 8 }"
+    :wrapper-col="{ span: 16 }"
   >
     <Form.Item label="类型" name="sourceType">
       <Select v-model:value="formModel.sourceType">
@@ -37,10 +38,9 @@
       <Form.Item label="刷新时间" name="refreshTime">
         <InputNumber
           v-model:value="formModel.refreshTime"
-          :min="0"
           :step="1"
-          :max="60"
           style="width: 100%"
+          @change="handleRefreshTimeChange"
         >
           <template #addonAfter>
             <span class="text-gray-500">秒</span>
@@ -117,15 +117,19 @@ const formModel = ref({
   // 静态数据相关
   data: "",
   // 接口相关
-  url: location.origin + "/api/get/example/bar",
-  method: "GET",
+  url: '',
+  method: "",
   params: {},
   headers: {},
   refreshTime: 0,
   // 数据处理
-  dataProcess: "() => {}",
+  dataProcess: "",
 });
 
+const handleRefreshTimeChange = (val: unknown) => {
+  formModel.value.refreshTime = val === 0 || val as number >= 60 ? val as number : 60;
+};
+
 /* =====================编辑代码======================= */
 let pathKey: "data" | "params" | "headers" | "dataProcess";
 const codeEditorRef = ref<Ref<CodeEditorModalInstance> | null>(null);

+ 17 - 9
src/components/Charts/Echarts.vue

@@ -1,10 +1,14 @@
 <!-- echarts基础组件 -->
 <template>
-  <div ref="chartRef" style="width: 100%; height: 100%;"></div>
+  <Spin :spinning="loading" :indicator="indicator">
+    <div ref="chartRef" :style="{width: width + 'px', height: height + 'px'}"></div>
+  </Spin>
 </template>
 
 <script setup lang="ts">
-import { ref, Ref, defineProps, watch, nextTick } from "vue";
+import { ref, Ref, defineProps, watch, nextTick, h } from "vue";
+import { Spin } from "ant-design-vue";
+import { LoadingOutlined } from "@ant-design/icons-vue";
 import { useEcharts } from "@/hooks/useEcharts";
 import type { EChartsOption } from "echarts";
 import { throttle } from "lodash";
@@ -13,19 +17,23 @@ const props = defineProps<{
   echartsOptions: EChartsOption;
   width: number;
   height: number;
+  loading?: boolean;
 }>();
 const chartRef: Ref<null | HTMLDivElement> = ref(null);
 const { setOptions, resize } = useEcharts(chartRef as Ref<HTMLDivElement>);
 
+const indicator = h(LoadingOutlined, {
+  style: {
+    fontSize: "24px",
+  },
+});
+
 watch(
-  () => [
-    props.width,
-    props.height,
-  ],
+  () => [props.width, props.height],
   throttle(async () => {
-   resize();
-  }, 200),
-)
+    resize();
+  }, 200)
+);
 
 watch(
   () => props,

+ 161 - 0
src/components/Charts/StyleConfig.vue

@@ -0,0 +1,161 @@
+<template>
+  <Form size="small" :label-col="{span: 8}" :colon="false">
+    <Collapse :bordered="false">
+      <CollapsePanel header="标题">
+        <Form.Item name="title" label=" ">
+          <Checkbox v-model:checked="formModel.title.visible">标题可见</Checkbox>
+        </Form.Item>
+        <Form.Item label="内容" name="subTitle">
+          <Input v-model:value="formModel.title.content"/>
+        </Form.Item>
+        <Form.Item label="位置" name="subTitle">
+          <RadioGroup v-model:value="formModel.title.position">
+            <RadioButton value="left"><AlignLeftOutlined/></RadioButton>
+            <RadioButton value="center"><AlignCenterOutlined/></RadioButton>
+            <RadioButton value="right"><AlignRightOutlined/></RadioButton>
+          </RadioGroup>
+        </Form.Item>
+        <Form.Item label="颜色" name="subTitle">
+          <ColorSelect v-model:value="formModel.title.color" />
+        </Form.Item>
+        <Form.Item label="样式" name="subTitle">
+          <FontStyle
+            v-model:value="formModel.title.fontSize"
+            v-model:bold="formModel.title.bold"
+            v-model:italic="formModel.title.italic"
+            v-model:underline="formModel.title.underline"
+          />
+        </Form.Item>
+      </CollapsePanel>
+
+      <CollapsePanel header="图例">
+        <Form.Item name="title" label=" ">
+          <Checkbox v-model:checked="formModel.legend.visible">图例可见</Checkbox>
+        </Form.Item>
+        <Form.Item label="位置" name="subTitle">
+          <RadioGroup v-model:value="formModel.legend.position">
+            <RadioButton value="left"><AlignLeftOutlined/></RadioButton>
+            <RadioButton value="center"><AlignCenterOutlined/></RadioButton>
+            <RadioButton value="right"><AlignRightOutlined/></RadioButton>
+          </RadioGroup>
+        </Form.Item>
+        <Form.Item label="颜色" name="subTitle">
+          <ColorSelect v-model:value="formModel.legend.color" />
+        </Form.Item>
+        <Form.Item label="样式" name="subTitle">
+          <FontStyle
+            v-model:value="formModel.legend.fontSize"
+            v-model:bold="formModel.legend.bold"
+            v-model:italic="formModel.legend.italic"
+            v-model:underline="formModel.legend.underline"
+          />
+        </Form.Item>
+      </CollapsePanel>
+
+      <CollapsePanel header="标签">
+        
+      </CollapsePanel>
+
+      <CollapsePanel header="系列">
+        <Form.Item label="配色" name="subTitle">
+          <ColorScheme v-model:value="formModel.series.colors" />
+        </Form.Item>
+        <Form.Item label="固定柱宽" name="subTitle">
+          <RadioGroup>
+            <RadioButton value="left">是</RadioButton>
+            <RadioButton value="center">否</RadioButton>
+          </RadioGroup>
+          <InputNumber/>
+        </Form.Item>
+      </CollapsePanel>
+
+      <CollapsePanel header="坐标轴">
+      
+      </CollapsePanel>
+
+      <CollapsePanel header="数据表">
+
+      </CollapsePanel>
+
+      <CollapsePanel header="背景">
+
+      </CollapsePanel>
+
+      <CollapsePanel header="提示">
+      
+      </CollapsePanel>
+    </Collapse>
+  </Form>
+</template>
+
+<script setup lang="ts">
+import { ref } from "vue";
+import {
+  Collapse,
+  CollapsePanel,
+  Form,
+  Checkbox,
+  Input,
+  InputNumber,
+  Select,
+  RadioGroup,
+  RadioButton,
+} from "ant-design-vue";
+import { AlignCenterOutlined, AlignRightOutlined, AlignLeftOutlined } from "@ant-design/icons-vue";
+import ColorSelect from "@/components/CusForm/src/ColorSelect.vue";
+import FontStyle from "@/components/CusForm/src/FontStyle.vue";
+import ColorScheme from "@/components/CusForm/src/ColorScheme.vue";
+
+const formModel = ref({
+  // 标题
+  title: {
+    visible: true,
+    content: "",
+    position: "center",
+    color: "#FFFFFFFF",
+    fontSize: 18,
+    bold: false,
+    italic: false,
+    underline: false,
+  },
+  // 图例
+  legend: {
+    visible: true,
+    position: "center",
+    color: "#FFFFFFFF",
+    fontSize: 12,
+    bold: false,
+    italic: false,
+    underline: false,
+  },
+  // 标签
+  label: {
+    visible: true,
+    position: "center",
+    color: "#FFFFFFFF",
+    fontSize: 12,
+    bold: false,
+    italic: false,
+    underline: false,
+  },
+  // 系列
+  series: {
+    barWidth: 20,
+    // 配色方案
+    colors: [],
+    fontSize: 12,
+    bold: false,
+    italic: false,
+    underline: false,
+  },
+});
+</script>
+
+<style lang="less" scoped>
+:deep(.ant-collapse-content-box) {
+  background: #fff;
+  .ant-form-item:last-child {
+    margin-bottom: 0;
+  }
+}
+</style>

+ 65 - 35
src/components/Charts/hooks/useChartOptions.ts

@@ -1,72 +1,102 @@
 import type { EChartsOption } from "echarts";
 import { computed, watch, ref } from "vue";
-import { omit, defaultsDeep } from 'lodash';
+import { omit, defaultsDeep } from "lodash";
 import { useRequest } from "vue-hooks-plus";
-import { DataSourceType } from '@/enum';
+import { DataSourceType } from "@/enum";
+import { cllJsCode } from "@/utils/calljs";
+import { message } from "ant-design-vue";
 
 export const useChartOptions = (chartProps: Record<string, any>) => {
-  const dataSource = chartProps.dataSource || {}
-  const xAxis = ref<EChartsOption["xAxis"]>({data: dataSource?.data?.xData});
-  const yAxis = ref<EChartsOption["yAxis"]>({data: dataSource?.data?.yData});
+  const dataSource = chartProps.dataSource || {};
+  const xAxis = ref<EChartsOption["xAxis"]>({ data: dataSource?.data?.xData });
+  const yAxis = ref<EChartsOption["yAxis"]>({ data: dataSource?.data?.yData });
   const series = ref<EChartsOption["series"]>(dataSource?.data?.series);
 
+  const server = computed(() => {
+    return async () =>
+      await fetch(chartProps.dataSource.url, {
+        method: chartProps.dataSource.method,
+      })
+      .then((res) => res.json());
+  });
+
   // 请求数据
-  const { run, refresh, cancel, data, loading} = useRequest(chartProps.dataSource.url, {
+  const { run, refresh, cancel, data, loading } = useRequest(server.value, {
     defaultParams: chartProps.dataSource.params,
     manual: true,
-    pollingInterval: (chartProps.dataSource?.refreshTime || 0) * 1000,
+    pollingInterval: (chartProps.dataSource?.refreshTime || 0) * 1000, // 刷新时间
+    onError: (error) => {
+      console.error(error);
+      message.error(chartProps.dataSource.url + "请求失败");
+    }
   });
 
   /* 初始请求 */
-  if(chartProps.dataSource.sourceType === DataSourceType.API) {
+  if (chartProps.dataSource.sourceType === DataSourceType.API) {
     run();
   }
 
   watch(
     () => data.value,
-    (val) => {
-      if(val && chartProps.dataSource.sourceType === DataSourceType.API) {
-        const { xData, yData, series } = data.value as any;
-        // TODO 数据处理成通用格式
-        xAxis.value = {data: xData};
-        yAxis.value = {data: yData};
-        series.value = series;
+    async (val) => {
+      if (val && chartProps.dataSource.sourceType === DataSourceType.API) {
+        let res = val;
+        if(chartProps.dataSource.dataProcess) {
+          res = await cllJsCode(chartProps.dataSource.dataProcess, JSON.stringify(val));
+        }
+    
+        xAxis.value = res.xAxis || { data: res.xData };
+        yAxis.value = res.yAxis || { data: res.yData };
+        series.value = res.series;
       }
-    },{
-      deep: true
+    },
+    {
+      deep: true,
     }
-  )
+  );
 
   watch(
-    () => chartProps.dataSource,
-    (val) => {
-      if(val === DataSourceType.API) {
+    () => [
+      chartProps.dataSource.sourceType,
+      chartProps.dataSource.method
+    ],
+    () => {
+      if (chartProps.dataSource.sourceType === DataSourceType.API) {
         refresh();
       } else {
         cancel();
         const dataSource = chartProps.dataSource || {};
-        xAxis.value = {data: dataSource?.data?.xData};
-        yAxis.value = {data: dataSource?.data?.yData};
+        xAxis.value = { data: dataSource?.data?.xData };
+        yAxis.value = { data: dataSource?.data?.yData };
         series.value = dataSource?.data?.series;
       }
     },
     {
-      deep: true
+      deep: true,
     }
-  )
+  );
 
   const options = computed((): EChartsOption => {
-    const opt = omit(chartProps, ['width', 'height', 'dataSource']) as EChartsOption;
-    
-    return defaultsDeep({
-      xAxis: xAxis.value,
-      yAxis: yAxis.value,
-      series: series.value,
-    }, opt);
+    const opt = omit(chartProps, [
+      "width",
+      "height",
+      "dataSource",
+    ]) as EChartsOption;
+
+    const result = defaultsDeep(
+      {
+        xAxis: xAxis.value,
+        yAxis: yAxis.value,
+        series: series.value,
+      },
+      opt
+    );
+
+    return result;
   });
 
   return {
     options,
-    loading
-  }
-}
+    loading,
+  };
+};

+ 0 - 1
src/components/CodeEditor/src/Editor.vue

@@ -44,7 +44,6 @@ watch(
 
 const handleCodeChange = (val: string) => {
   try {
-    JSON.stringify(JSON.parse(val), null, 2);
     emit('update:code', val);
     emit('change', val);
   } catch (error) {

+ 2 - 3
src/components/CodeEditor/src/index.vue

@@ -11,17 +11,15 @@
 
 <script setup lang="ts">
 import { Modal  } from 'ant-design-vue';
-import { ref, defineProps, withDefaults, watch, defineExpose } from 'vue';
+import { ref, defineProps, withDefaults, defineExpose } from 'vue';
 import Editor from './Editor.vue';
 
 interface IProp {
   title?: string;
-  code?: string;
   width?: number;
 }
 withDefaults(defineProps<IProp>(), {
   title: '编辑',
-  code: '',
   width: 800,
 });
 const emit = defineEmits(['ok']);
@@ -29,6 +27,7 @@ const open = ref(false);
 const code = ref('');
 
 const handleOk = () => {
+  // TODO: 检验code
   emit('ok', code.value);
   open.value = false;
 };

+ 1 - 1
src/components/CusForm/src/BackgroundSelect.vue

@@ -74,7 +74,7 @@ const handleChangeColor = (type: any) => {
   display: flex;
   align-items: center;
   margin: 12px 0;
-  :deep .el-color-picker {
+  :deep(.el-color-picker) {
     margin-right: 8px;
   }
 }

+ 74 - 0
src/components/CusForm/src/ColorScheme.vue

@@ -0,0 +1,74 @@
+<template>
+  <div>
+    <Select v-model:value="colorType" size="small" style="width: 100%" @change="changeColorType">
+      <SelectOption
+        v-for="item in colorPreset"
+        :key="item.name"
+        :value="item.color.join(',')"
+      >
+        <span
+          class="color-block"
+          v-for="color in item.color.slice(0, 5)"
+          :key="color"
+          :style="{background: color}"
+        ></span>
+        {{ item.name }}</SelectOption
+      >
+
+      <SelectOption value="custom">自定义组合</SelectOption>
+      <!-- <SelectOption value="gradient">自定义渐变色</SelectOption> -->
+    </Select>
+    <div class="color-list">
+      <span
+        class="color-block"
+        v-for="(color, index) in value"
+        :key="index"
+        :style="{background: color}"
+      ></span>
+      <span class="color-block cus-btn"><PlusOutlined /></span>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref } from "vue";
+import { Select, SelectOption } from "ant-design-vue";
+import { PlusOutlined } from "@ant-design/icons-vue";
+import { colorPreset } from "@/config/colorDefaultConfig";
+
+const value = ref<string[]>(colorPreset[0].color);
+const colorType = ref<string>(colorPreset[0].color.join(","));
+
+const changeColorType = (val: string) => {
+  if (val === "custom") {
+    value.value = [];
+  } else {
+    value.value = val.split(",");
+  }
+};
+</script>
+
+<style lang="less" scoped>
+.color-block {
+  display: inline-block;
+  width: 10px;
+  height: 10px;
+  margin-right: 4px;
+}
+.color-list {
+  margin-top: 12px;
+  display: flex;
+  flex-wrap: wrap;
+  .color-block {
+    margin-right: 0;
+    width: 16px;
+    height: 16px;
+  }
+  .cus-btn {
+    cursor: pointer;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+}
+</style>

+ 2 - 2
src/components/CusForm/src/ColorSelect.vue

@@ -27,7 +27,7 @@ const props = defineProps<{
 }>();
 const colorType = ref(props.value?.length <= 7 || !props.value ? 'pure' : 'gradient');
 const color = ref(props.value);
-const gradientColor = ref(props.value.length >= 7 ? props.value?.split(',') : ['#4ba9ff', '#fff']);
+const gradientColor = ref(props.value?.length >= 7 ? props.value?.split(',') : ['#4ba9ff', '#fff']);
 
 watch(
   () => [colorType.value, color.value, gradientColor.value],
@@ -50,7 +50,7 @@ watch(
   display: flex;
   align-items: center;
   margin: 12px 0;
-  :deep .el-color-picker {
+  :deep(.el-color-picker) {
     margin-right: 8px;
   }
 }

+ 55 - 0
src/components/CusForm/src/FontStyle.vue

@@ -0,0 +1,55 @@
+<template>
+  <div class="font-style">
+    <span class="cus-btn" :class="{'active-btn': bold}"><BoldOutlined @click="handleBold"/></span>
+    <span class="cus-btn" :class="{'active-btn': italic}"><ItalicOutlined @click="handleItalic"/></span>
+    <span class="cus-btn" :class="{'active-btn': underline}"><UnderlineOutlined @click="handleUnderline"/></span>
+    <InputNumber size="small" min={1} max={100} step={1} precision={0} style="width: 80px" @change="handleChange"/>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { defineProps, defineEmits, ref } from 'vue';
+import { BoldOutlined, ItalicOutlined, UnderlineOutlined, } from '@ant-design/icons-vue';
+import { InputNumber } from 'ant-design-vue';
+
+const props = defineProps<{
+  value: number;
+  bold: boolean;
+  italic: boolean;
+  underline: boolean;
+}>();
+
+const emit = defineEmits(["update:value", "update:bold", "update:italic", "update:underline"]);
+
+const bold = ref(props.bold);
+const italic = ref(props.italic);
+const underline = ref(props.underline);
+const value = ref(props.value);
+
+const handleBold = () => {
+  bold.value = !bold.value;
+  emit('update:bold', bold.value);
+};
+const handleItalic = () => {
+  italic.value = !italic.value;
+  emit('update:italic', italic.value);
+};
+const handleUnderline = () => {
+  underline.value = !underline.value;
+  emit('update:underline', underline.value);
+};
+const handleChange = (e: any) => {
+  value.value = e.target.value;
+  emit('update:value', value.value);
+};
+</script>
+
+<style lang="less" scoped>
+.font-style {
+  display: flex;
+  justify-content: space-between;
+}
+.active-btn {
+  color: @primary-color;
+}
+</style>

+ 1 - 0
src/main.ts

@@ -4,6 +4,7 @@ import router from "./router";
 import { setupStore } from "./store";
 import "ant-design-vue/dist/reset.css";
 import "./style/index.css";
+import "@/mock";
 
 const app = createApp(App);
 

+ 8 - 10
src/mock/index.ts

@@ -3,7 +3,7 @@
 import { MockMethod } from 'vite-plugin-mock'
 export default [
   {
-    url: '/api/get/resource/image-list',
+    url: '/mock/api/get/resource/image-list',
     method: 'get',
     response: ({ query }) => {
       return {
@@ -15,19 +15,17 @@ export default [
     },
   },
   {
-    url: '/api/get/example/bar',
+    url: '/mock/api/get/example/bar',
     method: 'post',
     timeout: 2000,
     response: {
       code: 0,
-      data: {
-        data: [
-          { name: '苹果', price: 10, count: 100 },
-          { name: '香蕉', price: 150, count: 100 },
-          { name: '西瓜', price: 4, count: 100 },
-          { name: '葡萄', price: 45, count: 100 },
-        ],
-      },
+      data: [
+        { name: '苹果', price: 20, count: 10 },
+        { name: '香蕉', price: 10, count: 20},
+        { name: '西瓜', price: 25, count: 30 },
+        { name: '葡萄', price: 12, count: 40},
+      ],
     },
   },
 ] as MockMethod[]

+ 29 - 0
src/utils/calljs.ts

@@ -0,0 +1,29 @@
+/**
+ * 执行动态js
+ * @param code 代码
+ * @param param 参数
+ * @returns promise
+ */
+export const cllJsCode = (code: string, param: string): Promise<any> => {
+  
+  return new Promise((resove, reject) => {
+    // 生成一份new webwork
+    const blob = new Blob([`
+      self.onmessage = function(e) {
+        self.postMessage((${code}).call(null, e.data));
+      }
+    `], { type: 'application/javascript' });
+
+    const worker = new Worker(URL.createObjectURL(blob));
+    // 向webwork发送消息
+    worker.postMessage(JSON.parse(param));
+    worker.onmessage = (e) => {
+      worker.terminate();
+      resove(e.data);
+    }
+    worker.onerror = (e) => {
+      worker.terminate();
+      reject(e);
+    }
+  });
+}

+ 71 - 46
src/views/designer/component/ComponentLibary.vue

@@ -1,9 +1,17 @@
 <template>
   <div id="compBox">
     <!-- 组件弹窗 -->
-    <div class="comp-list-drawer" :style="getDrawerStyle" ondragover="return false">
+    <div
+      class="comp-list-drawer"
+      :style="getDrawerStyle"
+      ondragover="return false"
+    >
       <div class="drawer-title">{{ selectedMenu?.name }}</div>
-      <Collapse :bordered="false" style="border-radius: 0" v-if="selectedMenu?.isGroup">
+      <Collapse
+        :bordered="false"
+        style="border-radius: 0"
+        v-if="selectedMenu?.isGroup"
+      >
         <CollapsePanel
           class="custom-collapse-panel"
           v-for="group in getList"
@@ -11,12 +19,18 @@
           :header="group.name"
         >
           <Space :size="4" wrap>
-            <div class="comp-item"
+            <div
+              class="comp-item"
               v-for="item in group.children"
               :key="item.name"
               @click="handleAddComp(item)"
             >
-              <img :src="item.icon" draggable="true" @dragstart="handleDragStart(item)" @dragend="handleDragEnd">
+              <img
+                :src="item.icon"
+                draggable="true"
+                @dragstart="handleDragStart(item)"
+                @dragend="handleDragEnd"
+              />
               <div class="comp-name">{{ item.name }}</div>
             </div>
           </Space>
@@ -24,17 +38,22 @@
       </Collapse>
       <div class="comp-box" v-else>
         <Space :size="4" wrap>
-          <div class="comp-item"
+          <div
+            class="comp-item"
             v-for="item in getList"
             :key="item.name"
             @click="handleAddComp(item)"
           >
-            <img :src="item.icon" draggable="true" @dragstart="handleDragStart(item)" @dragend="handleDragEnd"/>
+            <img
+              :src="item.icon"
+              draggable="true"
+              @dragstart="handleDragStart(item)"
+              @dragend="handleDragEnd"
+            />
             <div class="comp-name">{{ item.name }}</div>
           </div>
         </Space>
       </div>
-      
     </div>
 
     <Layout style="height: 100%">
@@ -45,7 +64,12 @@
         style="background: #f7fafc"
         width="100"
       >
-        <Menu theme="light" @click="handleMenuClick" v-model:selected-keys="selectedKeys" class="comp-menu">
+        <Menu
+          theme="light"
+          @click="handleMenuClick"
+          v-model:selected-keys="selectedKeys"
+          class="comp-menu"
+        >
           <MenuItem v-for="item in compSetting.compList" :key="item.type">
             <component :is="item.icon" />
             <span>{{ item.name }}</span>
@@ -70,7 +94,7 @@ import {
 } from "ant-design-vue";
 import { compSetting } from "@/config/compSetting";
 import type { CompCategory, CompItem } from "@/config/compSetting";
-import { useEventListener } from "@vueuse/core"
+import { useEventListener } from "@vueuse/core";
 import { useProjectStore } from "@/store/modules/project";
 import { useStageStore } from "@/store/modules/stage";
 
@@ -108,7 +132,11 @@ watch(
 );
 
 useEventListener(document, "click", (e) => {
-  if (openDrawer.value && !e?.target?.closest(".comp-list-drawer") && !e?.target?.closest(".comp-menu")) {
+  if (
+    openDrawer.value &&
+    !e?.target?.closest(".comp-list-drawer") &&
+    !e?.target?.closest(".comp-menu")
+  ) {
     openDrawer.value = false;
     selectedKeys.value = [];
   }
@@ -137,8 +165,8 @@ const handleAddComp = (item: CompItem) => {
       props: {
         x: stageStore.width / 2,
         y: stageStore.height / 2,
-      }
-    }
+      },
+    },
   };
 
   projectStore.addElement(compData);
@@ -150,7 +178,7 @@ const handleAddComp = (item: CompItem) => {
 const handleDragStart = (item: CompItem) => {
   openDrawer.value = false;
   selectedKeys.value = [];
-  
+
   projectStore.setAddCompData({
     key: Date.now(),
     name: item.name,
@@ -159,9 +187,9 @@ const handleDragStart = (item: CompItem) => {
       props: {
         x: 0,
         y: 0,
-      }
-    }
-  })
+      },
+    },
+  });
 };
 // 拖拽结束 清空临时数据
 const handleDragEnd = () => {
@@ -194,13 +222,13 @@ const handleDragEnd = () => {
   .custom-collapse-panel {
     border-radius: 0;
     background: #fff;
-    :deep .ant-collapse-header {
+    :deep(.ant-collapse-header) {
       padding: 4px 16px;
       border-bottom: solid 1px #f0f0f0;
       background: @bg-color;
       font-size: 13px;
     }
-    :deep .ant-collapse-content-box {
+    :deep(.ant-collapse-content-box) {
       padding: 16px;
       padding-bottom: 24px;
     }
@@ -226,37 +254,34 @@ const handleDragEnd = () => {
   }
 }
 
-.ant-menu-light {
+:deep(.ant-menu-light) {
   background: none;
-  :deep {
-    .ant-menu-item:not(.ant-menu-item-selected):hover {
-      background: #eff8ff;
-    }
-    .ant-menu-item {
-      width: 100%;
-      border-radius: 0;
-      margin-inline: 0;
-      &:not(.ant-menu-item-selected) {
-        color: #666;
-      }
-    }
-    .ant-menu-item-selected {
-      position: relative;
-    }
-    .ant-menu-item-selected::before {
-      content: "";
-      position: absolute;
-      left: 0;
-      top: 0;
-      height: 100%;
-      border-left: 3px solid #1890ff;
+
+  .ant-menu-item:not(.ant-menu-item-selected):hover {
+    background: #eff8ff;
+  }
+  .ant-menu-item {
+    width: 100%;
+    border-radius: 0;
+    margin-inline: 0;
+    &:not(.ant-menu-item-selected) {
+      color: #666;
     }
   }
-}
-:deep {
-  .ant-layout-sider-trigger {
-    background: none;
-    color: #666;
+  .ant-menu-item-selected {
+    position: relative;
   }
+  .ant-menu-item-selected::before {
+    content: "";
+    position: absolute;
+    left: 0;
+    top: 0;
+    height: 100%;
+    border-left: 3px solid #1890ff;
+  }
+}
+:deep(.ant-layout-sider-trigger) {
+  background: none;
+  color: #666;
 }
 </style>

+ 11 - 2
src/views/designer/component/Configurator.vue

@@ -16,7 +16,7 @@
           <component
             :is="configComponent"
             @change="handleConfigChange"
-            v-bind="projectStore.currentSelectedElements[0].props"
+            v-bind="currentElementProps"
           />
         </div>
       </TabPane>
@@ -42,6 +42,7 @@ import componentAll from "@/components";
 
 const projectStore = useProjectStore();
 const configComponent = shallowRef<null | string>(null);
+const currentElementProps = shallowRef<any>({});
 
 watch(
   () => projectStore.currentSelectedElements,
@@ -51,8 +52,10 @@ watch(
         val[0].componentType as keyof typeof componentAll
       ]?.();
       configComponent.value = Config;
+      currentElementProps.value = val[0].props;
     } else {
       configComponent.value = null;
+      currentElementProps.value = {};
     }
   },
   { immediate: true, deep: true }
@@ -67,8 +70,14 @@ const handleConfigChange = (config: any) => {
 <style lang="less" scoped>
 .configurator {
   width: 300px;
-  overflow-y: auto;
   padding: 0 12px;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  :deep(.ant-tabs) {
+    flex: 1;
+    overflow-y: scroll;
+  }
   .config-content {
     padding: 12px;
     padding-top: 0;

+ 2 - 2
vite.config.ts

@@ -8,9 +8,9 @@ import { viteMockServe } from "vite-plugin-mock";
 export default defineConfig({
   plugins: [
     vue(),
-    ElementPlus(),
+    ElementPlus({}),
     viteMockServe({
-      mockPath: "src/mock",
+      mockPath: "./src/mock",
       enable: true,
       watchFiles: true
     }),