Browse Source

feat: 新增堆积、百分比、弧形柱状图

liaojiaxing 10 months ago
parent
commit
b4b9a35df6
34 changed files with 1874 additions and 41 deletions
  1. BIN
      apps/shalu-bigscreen-designer/src/assets/comp-icon/chart/bar/stack.png
  2. 9 4
      apps/shalu-bigscreen-designer/src/config/compSetting.ts
  3. 1 1
      packages/shalu-dashboard-ui/components/charts/Bar/BasicBar/index.ts
  4. 2 2
      packages/shalu-dashboard-ui/components/charts/Bar/BasicBar/src/BasicBar.vue
  5. 2 2
      packages/shalu-dashboard-ui/components/charts/Bar/BasicBar/src/Config.vue
  6. 2 2
      packages/shalu-dashboard-ui/components/charts/Bar/BasicBar/src/props.ts
  7. 12 0
      packages/shalu-dashboard-ui/components/charts/Bar/PercentBar/index.ts
  8. 182 0
      packages/shalu-dashboard-ui/components/charts/Bar/PercentBar/src/Config.vue
  9. 26 0
      packages/shalu-dashboard-ui/components/charts/Bar/PercentBar/src/PercentBar.vue
  10. 152 0
      packages/shalu-dashboard-ui/components/charts/Bar/PercentBar/src/props.ts
  11. 12 0
      packages/shalu-dashboard-ui/components/charts/Bar/PolarBar/index.ts
  12. 365 0
      packages/shalu-dashboard-ui/components/charts/Bar/PolarBar/src/Config.vue
  13. 26 0
      packages/shalu-dashboard-ui/components/charts/Bar/PolarBar/src/PolarBar.vue
  14. 178 0
      packages/shalu-dashboard-ui/components/charts/Bar/PolarBar/src/props.ts
  15. 12 0
      packages/shalu-dashboard-ui/components/charts/Bar/StackBar/index.ts
  16. 182 0
      packages/shalu-dashboard-ui/components/charts/Bar/StackBar/src/Config.vue
  17. 26 0
      packages/shalu-dashboard-ui/components/charts/Bar/StackBar/src/StackBar.vue
  18. 139 0
      packages/shalu-dashboard-ui/components/charts/Bar/StackBar/src/props.ts
  19. 1 1
      packages/shalu-dashboard-ui/components/charts/Line/BasicLine/index.ts
  20. 2 2
      packages/shalu-dashboard-ui/components/charts/Line/BasicLine/src/BasicLine.vue
  21. 2 2
      packages/shalu-dashboard-ui/components/charts/Line/BasicLine/src/Config.vue
  22. 2 2
      packages/shalu-dashboard-ui/components/charts/Line/BasicLine/src/props.ts
  23. 1 1
      packages/shalu-dashboard-ui/components/charts/Pie/BasicPie/index.ts
  24. 2 2
      packages/shalu-dashboard-ui/components/charts/Pie/BasicPie/src/BasicPie.vue
  25. 2 2
      packages/shalu-dashboard-ui/components/charts/Pie/BasicPie/src/Config.vue
  26. 2 2
      packages/shalu-dashboard-ui/components/charts/Pie/BasicPie/src/props.ts
  27. 12 0
      packages/shalu-dashboard-ui/components/charts/Strip/BasicStrip/index.ts
  28. 26 0
      packages/shalu-dashboard-ui/components/charts/Strip/BasicStrip/src/BasicStrip.vue
  29. 182 0
      packages/shalu-dashboard-ui/components/charts/Strip/BasicStrip/src/Config.vue
  30. 218 0
      packages/shalu-dashboard-ui/components/charts/Strip/BasicStrip/src/props.ts
  31. 21 0
      packages/shalu-dashboard-ui/components/charts/config/chartFormItemsMap.ts
  32. 12 2
      packages/shalu-dashboard-ui/components/charts/hooks/useChartOptions.ts
  33. 1 1
      packages/shalu-dashboard-ui/components/codeEditor/src/Editor.vue
  34. 60 13
      packages/shalu-dashboard-ui/components/components.ts

BIN
apps/shalu-bigscreen-designer/src/assets/comp-icon/chart/bar/stack.png


+ 9 - 4
apps/shalu-bigscreen-designer/src/config/compSetting.ts

@@ -66,13 +66,18 @@ export const compSetting: CompSetting = {
               icon: compIcon['chart/bar/basic']
             },
             {
-              name: '百分比柱状图',
-              componetName: 'BasicBar',
+              name: '堆积柱状图',
+              componetName: 'StackBar',
+              icon: compIcon['chart/bar/stack']
+            },
+            {
+              name: '百分比堆积柱状图',
+              componetName: 'PercentBar',
               icon: compIcon['chart/bar/percent']
             },
             {
               name: '弧形柱状图',
-              componetName: 'BasicBar',
+              componetName: 'PolarBar',
               icon: compIcon['chart/bar/camber']
             },
             {
@@ -172,7 +177,7 @@ export const compSetting: CompSetting = {
           children: [
             {
               name: '条形图',
-              componetName: 'BasicPie',
+              componetName: 'BasicStrip',
               icon: compIcon['chart/strip/basic']
             },
             {

+ 1 - 1
packages/shalu-dashboard-ui/components/charts/Bar/BasicBar/index.ts

@@ -9,4 +9,4 @@ BasicBar.install = (app: any) => {
 
 export default BasicBar;
 export { Config };
-export { defaultPropsValue, basicBarProps } from './src/props';
+export { defaultPropsValue, componentProps } from './src/props';

+ 2 - 2
packages/shalu-dashboard-ui/components/charts/Bar/BasicBar/src/BasicBar.vue

@@ -5,13 +5,13 @@
 <script lang="ts">
 import { defineComponent } from 'vue';
 import Charts from '../../../Charts.vue';
-import { basicBarProps } from "./props";
+import { componentProps } from "./props";
 import { useChartOptions } from "../../../hooks/useChartOptions";
 
 export default defineComponent({
   name: "DBasicBar",
   components: { Charts },
-  props: basicBarProps,
+  props: componentProps,
   setup( props ) {
     const { options, loading } = useChartOptions(props);
 

+ 2 - 2
packages/shalu-dashboard-ui/components/charts/Bar/BasicBar/src/Config.vue

@@ -37,11 +37,11 @@ import { Tabs, TabPane } from "ant-design-vue";
 import { DatabaseOutlined, SkinOutlined } from "@ant-design/icons-vue";
 import DataConfig from "../../../DataConfig.vue";
 import { CusForm, IFormItem } from "../../../../cusForm";
-import { basicBarProps } from "./props";
+import { componentProps } from "./props";
 import { chartFormItemsMap } from "../../../config/chartFormItemsMap";
 import { set, cloneDeep } from "lodash-es";
 
-const props = defineProps(basicBarProps);
+const props = defineProps(componentProps);
 const activeTab = ref("1");
 const emit = defineEmits(["change"]);
 

+ 2 - 2
packages/shalu-dashboard-ui/components/charts/Bar/BasicBar/src/props.ts

@@ -3,7 +3,7 @@ import { EChartsOption } from "echarts";
 import { getNormalizedChart, dataSource } from "../../../utils";
 import { DataSourceType } from "../../../chartEnum";
 
-export const basicBarProps = {
+export const componentProps = {
   width: {
     type: Number as PropType<number>,
     default: 400,
@@ -135,4 +135,4 @@ export const defaultPropsValue: EChartsOption = {
   },
 };
 
-export type BasicBarProps = ExtractPropTypes<typeof basicBarProps>;
+export type BasicBarProps = ExtractPropTypes<typeof componentProps>;

+ 12 - 0
packages/shalu-dashboard-ui/components/charts/Bar/PercentBar/index.ts

@@ -0,0 +1,12 @@
+import PercentBar from './src/PercentBar.vue';
+import Config from './src/Config.vue';
+
+PercentBar.Config = Config;
+PercentBar.install = (app: any) => {
+  app.component(PercentBar.name, PercentBar);
+  return app;
+};
+
+export default PercentBar;
+export { Config };
+export { defaultPropsValue, componentProps } from './src/props';

+ 182 - 0
packages/shalu-dashboard-ui/components/charts/Bar/PercentBar/src/Config.vue

@@ -0,0 +1,182 @@
+<template>
+  <div class="chart-config">
+    <div class="config-tab">
+      <Tabs v-model:activeKey="activeTab" size="small" centered>
+        <TabPane key="1">
+          <template #tab>
+            <DatabaseOutlined />
+            <span>数据设置</span>
+          </template>
+        </TabPane>
+        <TabPane key="2">
+          <template #tab>
+            <SkinOutlined />
+            <span>样式设置</span>
+          </template>
+        </TabPane>
+      </Tabs>
+    </div>
+
+    <DataConfig
+      v-if="activeTab === '1'"
+      :dataSource="dataSource"
+      @change="handleDataSourceChange"
+    />
+    <CusForm
+      v-if="activeTab === '2'"
+      :columns="formItems"
+      :formModel="props"
+      @change="handleFormChange"
+    />
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, defineProps, defineEmits } from "vue";
+import { Tabs, TabPane } from "ant-design-vue";
+import { DatabaseOutlined, SkinOutlined } from "@ant-design/icons-vue";
+import DataConfig from "../../../DataConfig.vue";
+import { CusForm, IFormItem } from "../../../../cusForm";
+import { componentProps } from "./props";
+import { chartFormItemsMap } from "../../../config/chartFormItemsMap";
+import { set, cloneDeep } from "lodash-es";
+
+const props = defineProps(componentProps);
+const activeTab = ref("1");
+const emit = defineEmits(["change"]);
+
+const baseSeries: IFormItem[] = [
+  {
+    label: "样式",
+    prop: "",
+    type: "divider",
+  },
+  {
+    label: "固定柱宽",
+    prop: "seriesExtend.fixedBarWidth",
+    type: "radioGroup",
+    fieldProps: {
+      options: [
+        { label: "是", value: true },
+        { label: "否", value: false },
+      ],
+    },
+    defaultValue: false,
+    format: (formatModel, value) => {
+      formatModel.value["seriesExtend.barWidth"] =
+        value && formatModel.value?.["seriesExtend.barWidth"] !== "auto"
+          ? formatModel.value?.["seriesExtend.barWidth"] || 20
+          : "auto";
+    },
+  },
+  {
+    label: "",
+    prop: "",
+    type: "dependency",
+    name: ["seriesExtend.fixedBarWidth"],
+    children: (model) => {
+      return model["seriesExtend.fixedBarWidth"]
+        ? [
+            {
+              label: "柱宽",
+              prop: "seriesExtend.barWidth",
+              type: "inputNumber",
+              fieldProps: {
+                addonAfter: "px",
+              },
+              defaultValue: 20,
+            },
+          ]
+        : [];
+    },
+  },
+  {
+    label: "系列间隔",
+    prop: "seriesExtend.barGap",
+    type: "slider",
+    defaultValue: 30,
+    format: (formatFormatModel, value) => {
+      formatFormatModel.value['seriesExtend.barGap'] = value + "%";
+    },
+    valueToForm: (value) => {
+      return +(value?.replace("%", "") || 0);
+    },
+  },
+  {
+    label: "分类间隔",
+    prop: "seriesExtend.barCategoryGap",
+    type: "slider",
+    defaultValue: 20,
+    format: (formatFormatModel, value) => {
+      formatFormatModel.value['seriesExtend.barCategoryGap'] = value + "%";
+    },
+    valueToForm: (value) => {
+      return +(value?.replace("%", "") || 0);
+    },
+  },
+  {
+    label: "边框",
+    prop: "",
+    type: "divider",
+  },
+  {
+    label: "线宽",
+    prop: "seriesExtend.itemStyle.borderWidth",
+    type: "inputNumber",
+    fieldProps: {
+      addonAfter: "px",
+    },
+    defaultValue: 0,
+  },
+  {
+    label: "颜色",
+    prop: "seriesExtend.itemStyle.borderColor",
+    type: "colorSelect",
+    defaultValue: "#ccc",
+  },
+  {
+    label: "圆角",
+    prop: "seriesExtend.itemStyle.borderRadius",
+    type: "inputNumber",
+    fieldProps: {
+      addonAfter: "px",
+    },
+    defaultValue: 0,
+  },
+];
+const formItems: IFormItem[] = [
+  chartFormItemsMap.title,
+  chartFormItemsMap.legend,
+  chartFormItemsMap.label,
+  {
+    ...chartFormItemsMap.series,
+    children: (chartFormItemsMap.series.children as IFormItem[]).concat(
+      baseSeries
+    ),
+  },
+  chartFormItemsMap.xAxis,
+  chartFormItemsMap.yAxis,
+  chartFormItemsMap.tooltip,
+];
+
+const handleDataSourceChange = (data: any) => {
+  emit("change", {
+    ...props,
+    dataSource: data,
+  });
+};
+const handleFormChange = (formatData: any) => {
+  const obj = cloneDeep(props);
+  Object.keys(formatData).forEach((key) => {
+    set(obj, key, formatData[key]);
+  });
+  emit("change", obj);
+};
+</script>
+
+<style lang="less" scoped>
+.config-tab {
+  text-align: center;
+  margin-bottom: 12px;
+}
+</style>

+ 26 - 0
packages/shalu-dashboard-ui/components/charts/Bar/PercentBar/src/PercentBar.vue

@@ -0,0 +1,26 @@
+<template>
+  <Charts :width="width" :height="height" :echarts-options="options" :loading="loading"></Charts>
+</template>
+
+<script lang="ts">
+import { defineComponent } from 'vue';
+import Charts from '../../../Charts.vue';
+import { componentProps, chartOptions } from "./props";
+import { useChartOptions } from "../../../hooks/useChartOptions";
+
+export default defineComponent({
+  name: "DPercentBar",
+  components: { Charts },
+  props: componentProps,
+  setup( props ) {
+    const { options, loading } = useChartOptions(props, { grid: chartOptions.grid });
+
+    return {
+      options,
+      loading
+    }
+  }
+})
+</script>
+
+<style scoped></style>

+ 152 - 0
packages/shalu-dashboard-ui/components/charts/Bar/PercentBar/src/props.ts

@@ -0,0 +1,152 @@
+import type { PropType, ExtractPropTypes } from "vue";
+import { EChartsOption } from "echarts";
+import { getNormalizedChart, dataSource } from "../../../utils";
+import { DataSourceType } from "../../../chartEnum";
+
+export const componentProps = {
+  width: {
+    type: Number as PropType<number>,
+    default: 400,
+  },
+  height: {
+    type: Number as PropType<number>,
+    default: 260,
+  },
+  dataSource,
+  // 标题
+  title: {
+    type: Object as PropType<EChartsOption["title"]>,
+  },
+  // 图例
+  legend: {
+    type: Object as PropType<EChartsOption["legend"]>,
+  },
+  // 背景
+  backgroundColor: {
+    type: String as PropType<string>,
+  },
+  // 边框
+  grid: {
+    type: Object as PropType<EChartsOption["grid"]>,
+  },
+  // 提示框
+  tooltip: {
+    type: Object as PropType<EChartsOption["tooltip"]>,
+  },
+  // x轴数据
+  xAxis: {
+    type: Object as PropType<EChartsOption["xAxis"]>,
+  },
+  // y轴数据
+  yAxis: {
+    type: Object as PropType<EChartsOption["yAxis"]>,
+  },
+  // 折线
+  series: {
+    type: Array as PropType<EChartsOption["series"]>,
+  },
+  // color
+  color: {
+    type: Object as PropType<EChartsOption["color"]>
+  },
+  // 系列设置
+  seriesExtend: {
+    type: Object as PropType<EChartsOption["series"]>,
+    default: () => seriesExtend
+  }
+};
+
+/* 系列相关 */
+const series: EChartsOption['series'] = [];
+const seriesExtend = {
+  // @ts-ignore
+  fixedBarWidth: false,
+  barWidth: 'auto',
+  barGap: '10%',
+  barCategoryGap: '20%',
+  itemStyle: {
+    borderColor: '#ccc',
+    borderRadius: 0,
+    borderWidth: 0,
+  },
+  stack: 'x'
+};
+
+export const chartOptions = getNormalizedChart({
+  title: {
+    text: "百分比堆积柱状图标题",
+  },
+  xAxis: {
+    data: ['轴标签A', '轴标签B', '轴标签C', '轴标签D']
+  },
+  yAxis: {
+    type: 'value',
+    axisLabel: {
+      formatter: '{value}%'
+    }
+  },
+  // 布局
+  grid: {
+    bottom: 34,
+    right: 20,
+    left: 40,
+    top: 20,
+  },
+  series,
+  seriesExtend
+})
+
+export const defaultPropsValue: EChartsOption = {
+  // 组件容器默认属性
+  container: {
+    props: {
+      width: 400,
+      height: 260,
+    },
+  },
+  // 图表默认属性
+  props: {
+    // 数据源
+    dataSource: {
+      sourceType: DataSourceType.STATIC,
+      data: {
+        series: [
+          {
+            type: 'bar',
+            name: '系列1',
+            data: [10, 30, 20, 40]
+          },
+          {
+            type: 'bar',
+            name: '系列2',
+            data: [15, 35, 25, 45]
+          },
+        ]
+      },
+      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
+  },
+};
+
+export type PercentBarProps = ExtractPropTypes<typeof componentProps>;

+ 12 - 0
packages/shalu-dashboard-ui/components/charts/Bar/PolarBar/index.ts

@@ -0,0 +1,12 @@
+import PolarBar from './src/PolarBar.vue';
+import Config from './src/Config.vue';
+
+PolarBar.Config = Config;
+PolarBar.install = (app: any) => {
+  app.component(PolarBar.name, PolarBar);
+  return app;
+};
+
+export default PolarBar;
+export { Config };
+export { defaultPropsValue, componentProps } from './src/props';

+ 365 - 0
packages/shalu-dashboard-ui/components/charts/Bar/PolarBar/src/Config.vue

@@ -0,0 +1,365 @@
+<template>
+  <div class="chart-config">
+    <div class="config-tab">
+      <Tabs v-model:activeKey="activeTab" size="small" centered>
+        <TabPane key="1">
+          <template #tab>
+            <DatabaseOutlined />
+            <span>数据设置</span>
+          </template>
+        </TabPane>
+        <TabPane key="2">
+          <template #tab>
+            <SkinOutlined />
+            <span>样式设置</span>
+          </template>
+        </TabPane>
+      </Tabs>
+    </div>
+
+    <DataConfig
+      v-if="activeTab === '1'"
+      :dataSource="dataSource"
+      @change="handleDataSourceChange"
+    />
+    <CusForm
+      v-if="activeTab === '2'"
+      :columns="formItems"
+      :formModel="props"
+      @change="handleFormChange"
+    />
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, defineProps, defineEmits } from "vue";
+import { Tabs, TabPane } from "ant-design-vue";
+import { DatabaseOutlined, SkinOutlined } from "@ant-design/icons-vue";
+import DataConfig from "../../../DataConfig.vue";
+import { CusForm, IFormItem } from "../../../../cusForm";
+import { componentProps } from "./props";
+import { chartFormItemsMap } from "../../../config/chartFormItemsMap";
+import { set, get, cloneDeep } from "lodash-es";
+
+const props = defineProps(componentProps);
+const activeTab = ref("1");
+const emit = defineEmits(["change"]);
+
+const formItems: IFormItem[] = [
+  chartFormItemsMap.title,
+  {
+    label: "标签",
+    prop: "label",
+    type: "group",
+    children: [
+      {
+        label: " ",
+        prop: "radiusAxis.axisLabel.show",
+        type: "checkboxGroup",
+        fieldProps: {
+          options: [{ label: "标签可见", value: true }],
+        },
+        defaultValue: [true],
+        format: (formatModel, value) => {
+          formatModel.value["radiusAxis.axisLabel.show"] = value?.length ? true : false;
+        },
+        valueToForm: (value) => {
+          return value ? [true] : [];
+        }
+      },
+      {
+        label: "",
+        prop: "",
+        type: "dependency",
+        name: ["radiusAxis.axisLabel.show"],
+        children: (model) => {
+          return model["radiusAxis.axisLabel.show"].length
+            ? [
+                {
+                  label: "文本",
+                  prop: "radiusAxis.axisLabel.formatter",
+                  type: "checkboxGroup",
+                  fieldProps: {
+                    options: [
+                      { label: "分类名", value: "value" },
+                      { label: "数值", value: "index" },
+                    ],
+                  },
+                  defaultValue: ["b"],
+                  format: (formatModel, list) => {
+                    formatModel.value["radiusAxis.axisLabel.formatter"] = list.map((item: any) => `{${item}}`).join(" ");
+                  },
+                  valueToForm: (_, model) => {
+                    return get(model, 'radiusAxis.axisLabel.formatter')?.replace(/\{|\}/g, "")?.split(" ");
+                  }
+                },
+                {
+                  label: "样式",
+                  prop: "radiusAxis.axisLabel",
+                  type: "fontStyle",
+                  defaultValue: {
+                    color: "#000000ff",
+                    size: 12,
+                    bold: false,
+                    italic: false,
+                  },
+                  format: (formatModel, value) => {
+                    formatModel.value["radiusAxis.axisLabel.color"] = value.color;
+                    formatModel.value["radiusAxis.axisLabel.fontSize"] = value.fontSize;
+                    formatModel.value["radiusAxis.axisLabel.fontWeight"] = value.fontWeight;
+                    formatModel.value["radiusAxis.axisLabel.fontStyle"] = value.fontStyle;
+                  },
+                  valueToForm: (_, model) => {
+                    return {
+                      color: get(model, 'radiusAxis.axisLabel.color', '#000000ff'),
+                      size: get(model, 'radiusAxis.axisLabel.fontSize', 12),
+                      bold: get(model, 'radiusAxis.axisLabel.fontWeight') === 'bold',
+                      italic: get(model, 'radiusAxis.axisLabel.fontStyle') === 'italic',
+                    }
+                  }
+                },
+              ]
+            : []
+          }
+      }
+    ]
+  },
+  {
+    label: "图形",
+    prop: "",
+    type: "group",
+    children: [
+      {
+        label: "颜色",
+        prop: "seriesExtend.itemStyle.color",
+        type: "colorSelect",
+        defaultValue: "#fff",
+        valueToForm: (value) => {
+          if(typeof value === "object") {
+            return `linear-gradient(90deg, ${value?.colorStops?.[0]?.color},${value?.colorStops?.[1]?.color})`
+          } else {
+            return value
+          }
+        },
+        format(formatModel, value) {
+          if(value?.includes("linear-gradient")) {
+            console.log(value)
+            formatModel.value["seriesExtend.itemStyle.color"] = {
+              type: "linear",
+              x: 0,
+              y: 0,
+              x2: 1,
+              y2: 1,
+              colorStops: [
+                {
+                  offset: 0,
+                  color: value.replace('linear-gradient(90deg,', '').split(',')[0]
+                },
+                {
+                  offset: 1,
+                  color: value.split(',')[2].replace(')', '')
+                }
+              ]
+            }
+          } else {
+            formatModel.value["seriesExtend.itemStyle.color"] = value;
+          }
+        },
+      },
+      {
+        label: "内径占比",
+        prop: "polar.radius",
+        type: "slider",
+        format: (formatModel, value) => {
+          const inner = value * 0.75;
+          formatModel.value["polar.radius"] = [inner + "%", "80%"];
+          return value * 100;
+        },
+        valueToForm: (value) => {
+          const inner = (value || ["0%", "80%"])[0].replace("%", "");
+          return inner / 0.8;
+        },
+      },
+    ]
+  },
+  {
+    label: "提示",
+    prop: "tooltip",
+    type: "group",
+    children: [
+      {
+        label: " ",
+        prop: "tooltip.show",
+        type: "checkboxGroup",
+        fieldProps: {
+          options: [{ label: "提示可见", value: true }],
+        },
+        defaultValue: [true],
+        format: (formatModel, value) => {
+          formatModel.value["tooltip.show"] = value?.length ? true : false;
+        },
+        valueToForm: (value) => {
+          return value ? [true] : [];
+        }
+      },
+      {
+        label: "",
+        prop: "",
+        type: "dependency",
+        name: ["tooltip.show"],
+        children: (model) => {
+          return model["tooltip.show"].length
+            ? [
+                {
+                  label: "文本",
+                  prop: "tooltip.formatter",
+                  type: "checkboxGroup",
+                  fieldProps: {
+                    options: [
+                      { label: "分类名", value: "b" },
+                      { label: "数值", value: "c" },
+                    ],
+                  },
+                  defaultValue: ["b"],
+                  format: (formatModel, list) => {
+                    formatModel.value["tooltip.formatter"] = list.map((item: any) => `{${item}}`).join(" ");
+                  },
+                  valueToForm: (_, model) => {
+                    return get(model, 'tooltip.formatter')?.replace(/\{|\}/g, "")?.split(" ");
+                  }
+                },
+                {
+                  label: "样式",
+                  prop: "tooltip.textStyle",
+                  type: "fontStyle",
+                  defaultValue: {
+                    color: "#000000ff",
+                    size: 12,
+                    bold: false,
+                    italic: false,
+                  },
+                  format: (formatModel, value) => {
+                    formatModel.value["tooltip.textStyle"] = {
+                      color: value.color,
+                      fontSize: value.size,
+                      fontWeight: value.bold ? "bold" : "normal",
+                      fontStyle: value.italic ? "italic" : "normal",
+                    };
+                  },
+                  valueToForm: (_, model) => {
+                    return {
+                      color: get(model, 'tooltip.textStyle.color', '#000000ff'),
+                      size: get(model, 'tooltip.textStyle.fontSize', 12),
+                      bold: get(model, 'tooltip.textStyle.fontWeight') === 'bold',
+                      italic: get(model, 'tooltip.textStyle.fontStyle') === 'italic',
+                    }
+                  }
+                },
+                {
+                  label: "边框",
+                  prop: "",
+                  type: "divider",
+                },
+                {
+                  label: "线宽",
+                  prop: "tooltip.borderWidth",
+                  type: "inputNumber",
+                  fieldProps: {
+                    addonAfter: "px",
+                  },
+                  defaultValue: 1,
+                },
+                {
+                  label: "颜色",
+                  prop: "tooltip.borderColor",
+                  type: "colorSelect",
+                  defaultValue: "#ccc",
+                },
+                {
+                  label: "圆角",
+                  prop: "tooltip.borderRadius",
+                  type: "inputNumber",
+                  fieldProps: {
+                    addonAfter: "px",
+                  },
+                  defaultValue: 4,
+                },
+                {
+                  label: "背景",
+                  prop: "",
+                  type: "divider",
+                },
+                {
+                  label: "填充",
+                  prop: "tooltip.backgroundColor",
+                  type: "backgroundSelect",
+                  fieldProps: {
+                    filterOptions: ["image"],
+                  },
+                  defaultValue: {
+                    type: "color",
+                    color: "#fff",
+                  },
+                  format: (formatModel, value) => {
+                    formatModel.value["tooltip.backgroundColor"] = value?.type === 'color' ? value.color : 'none';
+                  },
+                  valueToForm: (value) => {
+                    return !value || value === 'none' 
+                    ? {
+                      type: 'none'
+                    } 
+                    : {
+                      type: "color",
+                      color: value,
+                    };
+                  },
+                },
+                {
+                  label: "阴影",
+                  prop: "tooltip.extraCssText",
+                  type: "radioGroup",
+                  fieldProps: {
+                    options: [
+                      { label: "开启", value: true },
+                      { label: "关闭", value: false },
+                    ],
+                  },
+                  defaultValue: false,
+                  format: (formatModel, value) => {
+                    formatModel.value["tooltip.extraCssText"] = value
+                      ? "box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);"
+                      : "";
+                  },
+                  valueToForm: (_, model) => {
+                    return get(model, 'tooltip.extraCssText') ? true : false;
+                  }
+                },
+              ]
+            : [];
+        },
+      },
+    ],
+  },
+];
+
+const handleDataSourceChange = (data: any) => {
+  emit("change", {
+    ...props,
+    dataSource: data,
+  });
+};
+const handleFormChange = (formatData: any) => {
+  const obj = cloneDeep(props);
+  Object.keys(formatData).forEach((key) => {
+    set(obj, key, formatData[key]);
+  });
+  emit("change", obj);
+};
+</script>
+
+<style lang="less" scoped>
+.config-tab {
+  text-align: center;
+  margin-bottom: 12px;
+}
+</style>

+ 26 - 0
packages/shalu-dashboard-ui/components/charts/Bar/PolarBar/src/PolarBar.vue

@@ -0,0 +1,26 @@
+<template>
+  <Charts :width="width" :height="height" :echarts-options="options" :loading="loading"></Charts>
+</template>
+
+<script lang="ts">
+import { defineComponent } from 'vue';
+import Charts from '../../../Charts.vue';
+import { componentProps } from "./props";
+import { useChartOptions } from "../../../hooks/useChartOptions";
+
+export default defineComponent({
+  name: "DPolarBar",
+  components: { Charts },
+  props: componentProps,
+  setup( props ) {
+    const { options, loading } = useChartOptions(props);
+
+    return {
+      options,
+      loading
+    }
+  }
+})
+</script>
+
+<style scoped></style>

+ 178 - 0
packages/shalu-dashboard-ui/components/charts/Bar/PolarBar/src/props.ts

@@ -0,0 +1,178 @@
+import type { PropType, ExtractPropTypes } from "vue";
+import { EChartsOption } from "echarts";
+import { getNormalizedChart, dataSource } from "../../../utils";
+import { DataSourceType } from "../../../chartEnum";
+import { colorPreset } from "../../../config";
+
+export const componentProps = {
+  width: {
+    type: Number as PropType<number>,
+    default: 400,
+  },
+  height: {
+    type: Number as PropType<number>,
+    default: 260,
+  },
+  dataSource,
+  // 标题
+  title: {
+    type: Object as PropType<EChartsOption["title"]>,
+  },
+  // 图例
+  legend: {
+    type: Object as PropType<EChartsOption["legend"]>,
+  },
+  // 背景
+  backgroundColor: {
+    type: String as PropType<string>,
+  },
+  // 边框
+  grid: {
+    type: Object as PropType<EChartsOption["grid"]>,
+  },
+  // 提示框
+  tooltip: {
+    type: Object as PropType<EChartsOption["tooltip"]>,
+  },
+  // x轴数据
+  xAxis: {
+    type: Object as PropType<EChartsOption["xAxis"]>,
+  },
+  // y轴数据
+  yAxis: {
+    type: Object as PropType<EChartsOption["yAxis"]>,
+  },
+  // 折线
+  series: {
+    type: Array as PropType<EChartsOption["series"]>,
+  },
+  // color
+  color: {
+    type: Object as PropType<EChartsOption["color"]>
+  },
+  polar: {
+    type: Object as PropType<EChartsOption["polar"]>
+  },
+  angleAxis: {
+    type: Object as PropType<EChartsOption["angleAxis"]>
+  },
+  radiusAxis: {
+    type: Object as PropType<EChartsOption["radiusAxis"]>
+  },
+  // 系列设置
+  seriesExtend: {
+    type: Object as PropType<EChartsOption["series"]>,
+    default: () => seriesExtend
+  }
+};
+
+/* 系列相关 */
+const series: EChartsOption['series'] = [];
+const seriesExtend = {
+  // @ts-ignore
+  fixedBarWidth: false,
+  barWidth: 'auto',
+  barGap: '10%',
+  barCategoryGap: '20%',
+  itemStyle: {
+    borderColor: '#ccc',
+    borderRadius: 0,
+    borderWidth: 0,
+    color: colorPreset[0].color[0]
+  },
+  coordinateSystem: 'polar',
+};
+
+const chartOptions = getNormalizedChart({
+  title: {
+    text: "弧形柱状图",
+  },
+  xAxis: {
+    show: false
+  },
+  yAxis: {
+    show: false
+  },
+  polar: {
+    radius: ['20%', '80%']
+  },
+  // 极坐标系的角度轴
+  angleAxis: {
+    startAngle: 90,
+    splitLine: { show: false }, // 关闭角度轴上的分割线
+    axisLine: { show: false },  // 关闭角度轴上的轴线
+    axisTick: { show: false },  // 关闭角度轴上的刻度线
+    axisLabel: { show: false }, // 关闭角度轴上的标签
+  },
+  // 极坐标系的半径轴
+  radiusAxis: {
+    type: 'category',
+    data: ['分类A', '分类B', '分类C', '分类D'],
+    splitLine: { show: false }, // 关闭角度轴上的分割线
+    axisLine: { show: false },  // 关闭角度轴上的轴线
+    axisTick: { show: false },  // 关闭角度轴上的刻度线
+    axisLabel: { 
+      show: true,
+      formatter: '{value}',
+      color: '#ffffff',
+      fontSize: 12,
+      fontWeight: 'normal',
+      fontStyle: 'normal'
+    }
+  },
+  legend: {
+    show: false
+  },
+  series,
+  seriesExtend
+})
+
+export const defaultPropsValue: EChartsOption = {
+  // 组件容器默认属性
+  container: {
+    props: {
+      width: 400,
+      height: 260,
+    },
+  },
+  // 图表默认属性
+  props: {
+    // 数据源
+    dataSource: {
+      sourceType: DataSourceType.STATIC,
+      data: {
+        series: [
+          {
+            type: 'bar',
+            name: '系列1',
+            data: [10, 30, 20, 40]
+          },
+        ]
+      },
+      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
+  },
+};
+
+export type PolarBarProps = ExtractPropTypes<typeof componentProps>;

+ 12 - 0
packages/shalu-dashboard-ui/components/charts/Bar/StackBar/index.ts

@@ -0,0 +1,12 @@
+import StackBar from './src/StackBar.vue';
+import Config from './src/Config.vue';
+
+StackBar.Config = Config;
+StackBar.install = (app: any) => {
+  app.component(StackBar.name, StackBar);
+  return app;
+};
+
+export default StackBar;
+export { Config };
+export { defaultPropsValue, componentProps } from './src/props';

+ 182 - 0
packages/shalu-dashboard-ui/components/charts/Bar/StackBar/src/Config.vue

@@ -0,0 +1,182 @@
+<template>
+  <div class="chart-config">
+    <div class="config-tab">
+      <Tabs v-model:activeKey="activeTab" size="small" centered>
+        <TabPane key="1">
+          <template #tab>
+            <DatabaseOutlined />
+            <span>数据设置</span>
+          </template>
+        </TabPane>
+        <TabPane key="2">
+          <template #tab>
+            <SkinOutlined />
+            <span>样式设置</span>
+          </template>
+        </TabPane>
+      </Tabs>
+    </div>
+
+    <DataConfig
+      v-if="activeTab === '1'"
+      :dataSource="dataSource"
+      @change="handleDataSourceChange"
+    />
+    <CusForm
+      v-if="activeTab === '2'"
+      :columns="formItems"
+      :formModel="props"
+      @change="handleFormChange"
+    />
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, defineProps, defineEmits } from "vue";
+import { Tabs, TabPane } from "ant-design-vue";
+import { DatabaseOutlined, SkinOutlined } from "@ant-design/icons-vue";
+import DataConfig from "../../../DataConfig.vue";
+import { CusForm, IFormItem } from "../../../../cusForm";
+import { componentProps } from "./props";
+import { chartFormItemsMap } from "../../../config/chartFormItemsMap";
+import { set, cloneDeep } from "lodash-es";
+
+const props = defineProps(componentProps);
+const activeTab = ref("1");
+const emit = defineEmits(["change"]);
+
+const baseSeries: IFormItem[] = [
+  {
+    label: "样式",
+    prop: "",
+    type: "divider",
+  },
+  {
+    label: "固定柱宽",
+    prop: "seriesExtend.fixedBarWidth",
+    type: "radioGroup",
+    fieldProps: {
+      options: [
+        { label: "是", value: true },
+        { label: "否", value: false },
+      ],
+    },
+    defaultValue: false,
+    format: (formatModel, value) => {
+      formatModel.value["seriesExtend.barWidth"] =
+        value && formatModel.value?.["seriesExtend.barWidth"] !== "auto"
+          ? formatModel.value?.["seriesExtend.barWidth"] || 20
+          : "auto";
+    },
+  },
+  {
+    label: "",
+    prop: "",
+    type: "dependency",
+    name: ["seriesExtend.fixedBarWidth"],
+    children: (model) => {
+      return model["seriesExtend.fixedBarWidth"]
+        ? [
+            {
+              label: "柱宽",
+              prop: "seriesExtend.barWidth",
+              type: "inputNumber",
+              fieldProps: {
+                addonAfter: "px",
+              },
+              defaultValue: 20,
+            },
+          ]
+        : [];
+    },
+  },
+  {
+    label: "系列间隔",
+    prop: "seriesExtend.barGap",
+    type: "slider",
+    defaultValue: 30,
+    format: (formatFormatModel, value) => {
+      formatFormatModel.value['seriesExtend.barGap'] = value + "%";
+    },
+    valueToForm: (value) => {
+      return +(value?.replace("%", "") || 0);
+    },
+  },
+  {
+    label: "分类间隔",
+    prop: "seriesExtend.barCategoryGap",
+    type: "slider",
+    defaultValue: 20,
+    format: (formatFormatModel, value) => {
+      formatFormatModel.value['seriesExtend.barCategoryGap'] = value + "%";
+    },
+    valueToForm: (value) => {
+      return +(value?.replace("%", "") || 0);
+    },
+  },
+  {
+    label: "边框",
+    prop: "",
+    type: "divider",
+  },
+  {
+    label: "线宽",
+    prop: "seriesExtend.itemStyle.borderWidth",
+    type: "inputNumber",
+    fieldProps: {
+      addonAfter: "px",
+    },
+    defaultValue: 0,
+  },
+  {
+    label: "颜色",
+    prop: "seriesExtend.itemStyle.borderColor",
+    type: "colorSelect",
+    defaultValue: "#ccc",
+  },
+  {
+    label: "圆角",
+    prop: "seriesExtend.itemStyle.borderRadius",
+    type: "inputNumber",
+    fieldProps: {
+      addonAfter: "px",
+    },
+    defaultValue: 0,
+  },
+];
+const formItems: IFormItem[] = [
+  chartFormItemsMap.title,
+  chartFormItemsMap.legend,
+  chartFormItemsMap.label,
+  {
+    ...chartFormItemsMap.series,
+    children: (chartFormItemsMap.series.children as IFormItem[]).concat(
+      baseSeries
+    ),
+  },
+  chartFormItemsMap.xAxis,
+  chartFormItemsMap.yAxis,
+  chartFormItemsMap.tooltip,
+];
+
+const handleDataSourceChange = (data: any) => {
+  emit("change", {
+    ...props,
+    dataSource: data,
+  });
+};
+const handleFormChange = (formatData: any) => {
+  const obj = cloneDeep(props);
+  Object.keys(formatData).forEach((key) => {
+    set(obj, key, formatData[key]);
+  });
+  emit("change", obj);
+};
+</script>
+
+<style lang="less" scoped>
+.config-tab {
+  text-align: center;
+  margin-bottom: 12px;
+}
+</style>

+ 26 - 0
packages/shalu-dashboard-ui/components/charts/Bar/StackBar/src/StackBar.vue

@@ -0,0 +1,26 @@
+<template>
+  <Charts :width="width" :height="height" :echarts-options="options" :loading="loading"></Charts>
+</template>
+
+<script lang="ts">
+import { defineComponent } from 'vue';
+import Charts from '../../../Charts.vue';
+import { componentProps } from "./props";
+import { useChartOptions } from "../../../hooks/useChartOptions";
+
+export default defineComponent({
+  name: "DStackBar",
+  components: { Charts },
+  props: componentProps,
+  setup( props ) {
+    const { options, loading } = useChartOptions(props);
+
+    return {
+      options,
+      loading
+    }
+  }
+})
+</script>
+
+<style scoped></style>

+ 139 - 0
packages/shalu-dashboard-ui/components/charts/Bar/StackBar/src/props.ts

@@ -0,0 +1,139 @@
+import type { PropType, ExtractPropTypes } from "vue";
+import { EChartsOption } from "echarts";
+import { getNormalizedChart, dataSource } from "../../../utils";
+import { DataSourceType } from "../../../chartEnum";
+
+export const componentProps = {
+  width: {
+    type: Number as PropType<number>,
+    default: 400,
+  },
+  height: {
+    type: Number as PropType<number>,
+    default: 260,
+  },
+  dataSource,
+  // 标题
+  title: {
+    type: Object as PropType<EChartsOption["title"]>,
+  },
+  // 图例
+  legend: {
+    type: Object as PropType<EChartsOption["legend"]>,
+  },
+  // 背景
+  backgroundColor: {
+    type: String as PropType<string>,
+  },
+  // 边框
+  grid: {
+    type: Object as PropType<EChartsOption["grid"]>,
+  },
+  // 提示框
+  tooltip: {
+    type: Object as PropType<EChartsOption["tooltip"]>,
+  },
+  // x轴数据
+  xAxis: {
+    type: Object as PropType<EChartsOption["xAxis"]>,
+  },
+  // y轴数据
+  yAxis: {
+    type: Object as PropType<EChartsOption["yAxis"]>,
+  },
+  // 折线
+  series: {
+    type: Array as PropType<EChartsOption["series"]>,
+  },
+  // color
+  color: {
+    type: Object as PropType<EChartsOption["color"]>
+  },
+  // 系列设置
+  seriesExtend: {
+    type: Object as PropType<EChartsOption["series"]>,
+    default: () => seriesExtend
+  }
+};
+
+/* 系列相关 */
+const series: EChartsOption['series'] = [];
+const seriesExtend = {
+  // @ts-ignore
+  fixedBarWidth: false,
+  barWidth: 'auto',
+  barGap: '10%',
+  barCategoryGap: '20%',
+  itemStyle: {
+    borderColor: '#ccc',
+    borderRadius: 0,
+    borderWidth: 0,
+  },
+  stack: 'x'
+};
+
+const chartOptions = getNormalizedChart({
+  title: {
+    text: "堆积柱状图标题",
+  },
+  xAxis: {
+    data: ['轴标签A', '轴标签B', '轴标签C', '轴标签D']
+  },
+  series,
+  seriesExtend
+})
+
+export const defaultPropsValue: EChartsOption = {
+  // 组件容器默认属性
+  container: {
+    props: {
+      width: 400,
+      height: 260,
+    },
+  },
+  // 图表默认属性
+  props: {
+    // 数据源
+    dataSource: {
+      sourceType: DataSourceType.STATIC,
+      data: {
+        series: [
+          {
+            type: 'bar',
+            name: '系列1',
+            data: [10, 30, 20, 40]
+          },
+          {
+            type: 'bar',
+            name: '系列2',
+            data: [15, 35, 25, 45]
+          },
+        ]
+      },
+      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
+  },
+};
+
+export type StackBarProps = ExtractPropTypes<typeof componentProps>;

+ 1 - 1
packages/shalu-dashboard-ui/components/charts/Line/BasicLine/index.ts

@@ -8,4 +8,4 @@ BasicLine.install = (app: any) => {
 };
 export default BasicLine;
 export { Config };
-export { defaultPropsValue, basicLineProps } from './src/props';
+export { defaultPropsValue, componentProps } from './src/props';

+ 2 - 2
packages/shalu-dashboard-ui/components/charts/Line/BasicLine/src/BasicLine.vue

@@ -5,13 +5,13 @@
 <script lang="ts">
 import { defineComponent } from 'vue';
 import Charts from '../../../Charts.vue';
-import { basicLineProps } from "./props";
+import { componentProps } from "./props";
 import { useChartOptions } from "../../../hooks/useChartOptions";
 
 export default defineComponent({
   name: "DBasicLine",
   components: { Charts },
-  props: basicLineProps,
+  props: componentProps,
   setup( props ) {
     const { options, loading } = useChartOptions(props);
 

+ 2 - 2
packages/shalu-dashboard-ui/components/charts/Line/BasicLine/src/Config.vue

@@ -38,11 +38,11 @@ import { Tabs, TabPane } from "ant-design-vue";
 import { DatabaseOutlined, SkinOutlined } from "@ant-design/icons-vue";
 import DataConfig from "../../../DataConfig.vue";
 import { CusForm } from "../../../../cusForm";
-import { basicLineProps } from "./props";
+import { componentProps } from "./props";
 import { chartFormItemsMap } from "../../../config/chartFormItemsMap";
 import { set, cloneDeep, get } from "lodash-es";
 
-const props = defineProps(basicLineProps);
+const props = defineProps(componentProps);
 const activeTab = ref("1");
 const emit = defineEmits(["change"]);
 

+ 2 - 2
packages/shalu-dashboard-ui/components/charts/Line/BasicLine/src/props.ts

@@ -4,7 +4,7 @@ import { getNormalizedChart } from "../../../utils";
 import { dataSource } from "../../../utils";
 import { DataSourceType } from "../../../chartEnum";
 
-export const basicLineProps = {
+export const componentProps = {
   width: {
     type: Number as PropType<number>,
     default: 400,
@@ -131,4 +131,4 @@ export const defaultPropsValue: EChartsOption = {
   },
 };
 
-export type BasicLineProps = ExtractPropTypes<typeof basicLineProps>;
+export type BasicLineProps = ExtractPropTypes<typeof componentProps>;

+ 1 - 1
packages/shalu-dashboard-ui/components/charts/Pie/BasicPie/index.ts

@@ -9,4 +9,4 @@ BasicPie.install = (app: any) => {
 
 export default BasicPie;
 export { Config };
-export { defaultPropsValue, basicPieProps } from './src/props';
+export { defaultPropsValue, componentProps } from './src/props';

+ 2 - 2
packages/shalu-dashboard-ui/components/charts/Pie/BasicPie/src/BasicPie.vue

@@ -5,13 +5,13 @@
 <script lang="ts">
 import { defineComponent } from 'vue';
 import Charts from '../../../Charts.vue';
-import { basicPieProps } from "./props";
+import { componentProps } from "./props";
 import { useChartOptions } from "../../../hooks/useChartOptions";
 
 export default defineComponent({
   name: "DBasicPie",
   components: { Charts },
-  props: basicPieProps,
+  props: componentProps,
   setup( props ) {
     const { options, loading } = useChartOptions(props);
     return {

+ 2 - 2
packages/shalu-dashboard-ui/components/charts/Pie/BasicPie/src/Config.vue

@@ -37,11 +37,11 @@ import { Tabs, TabPane } from "ant-design-vue";
 import { DatabaseOutlined, SkinOutlined } from "@ant-design/icons-vue";
 import DataConfig from "../../../DataConfig.vue";
 import { CusForm, IFormItem } from "../../../../cusForm";
-import { basicPieProps } from "./props";
+import { componentProps } from "./props";
 import { chartFormItemsMap } from "../../../config/chartFormItemsMap";
 import { set, cloneDeep } from "lodash-es";
 
-const props = defineProps(basicPieProps);
+const props = defineProps(componentProps);
 const activeTab = ref("1");
 const emit = defineEmits(["change"]);
 

+ 2 - 2
packages/shalu-dashboard-ui/components/charts/Pie/BasicPie/src/props.ts

@@ -3,7 +3,7 @@ import { EChartsOption } from "echarts";
 import { getNormalizedChart, dataSource } from "../../../utils";
 import { DataSourceType } from "../../../chartEnum";
 
-export const basicPieProps = {
+export const componentProps = {
   width: {
     type: Number as PropType<number>,
     default: 400,
@@ -128,4 +128,4 @@ export const defaultPropsValue: EChartsOption = {
   },
 };
 
-export type BasicPieProps = ExtractPropTypes<typeof basicPieProps>;
+export type BasicPieProps = ExtractPropTypes<typeof componentProps>;

+ 12 - 0
packages/shalu-dashboard-ui/components/charts/Strip/BasicStrip/index.ts

@@ -0,0 +1,12 @@
+import BasicStrip from './src/BasicStrip.vue';
+import Config from './src/Config.vue';
+
+BasicStrip.Config = Config;
+BasicStrip.install = (app: any) => {
+  app.component(BasicStrip.name, BasicStrip);
+  return app;
+};
+
+export default BasicStrip;
+export { Config };
+export { defaultPropsValue, componentProps } from './src/props';

+ 26 - 0
packages/shalu-dashboard-ui/components/charts/Strip/BasicStrip/src/BasicStrip.vue

@@ -0,0 +1,26 @@
+<template>
+  <Charts :width="width" :height="height" :echarts-options="options" :loading="loading"></Charts>
+</template>
+
+<script lang="ts">
+import { defineComponent } from 'vue';
+import Charts from '../../../Charts.vue';
+import { componentProps } from "./props";
+import { useChartOptions } from "../../../hooks/useChartOptions";
+
+export default defineComponent({
+  name: "DBasicStrip",
+  components: { Charts },
+  props: componentProps,
+  setup( props ) {
+    const { options, loading } = useChartOptions(props);
+
+    return {
+      options,
+      loading
+    }
+  }
+})
+</script>
+
+<style scoped></style>

+ 182 - 0
packages/shalu-dashboard-ui/components/charts/Strip/BasicStrip/src/Config.vue

@@ -0,0 +1,182 @@
+<template>
+  <div class="chart-config">
+    <div class="config-tab">
+      <Tabs v-model:activeKey="activeTab" size="small" centered>
+        <TabPane key="1">
+          <template #tab>
+            <DatabaseOutlined />
+            <span>数据设置</span>
+          </template>
+        </TabPane>
+        <TabPane key="2">
+          <template #tab>
+            <SkinOutlined />
+            <span>样式设置</span>
+          </template>
+        </TabPane>
+      </Tabs>
+    </div>
+
+    <DataConfig
+      v-if="activeTab === '1'"
+      :dataSource="dataSource"
+      @change="handleDataSourceChange"
+    />
+    <CusForm
+      v-if="activeTab === '2'"
+      :columns="formItems"
+      :formModel="props"
+      @change="handleFormChange"
+    />
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, defineProps, defineEmits } from "vue";
+import { Tabs, TabPane } from "ant-design-vue";
+import { DatabaseOutlined, SkinOutlined } from "@ant-design/icons-vue";
+import DataConfig from "../../../DataConfig.vue";
+import { CusForm, IFormItem } from "../../../../cusForm";
+import { componentProps } from "./props";
+import { chartFormItemsMap } from "../../../config/chartFormItemsMap";
+import { set, cloneDeep } from "lodash-es";
+
+const props = defineProps(componentProps);
+const activeTab = ref("1");
+const emit = defineEmits(["change"]);
+
+const baseSeries: IFormItem[] = [
+  {
+    label: "样式",
+    prop: "",
+    type: "divider",
+  },
+  {
+    label: "固定柱宽",
+    prop: "seriesExtend.fixedBarWidth",
+    type: "radioGroup",
+    fieldProps: {
+      options: [
+        { label: "是", value: true },
+        { label: "否", value: false },
+      ],
+    },
+    defaultValue: false,
+    format: (formatModel, value) => {
+      formatModel.value["seriesExtend.barWidth"] =
+        value && formatModel.value?.["seriesExtend.barWidth"] !== "auto"
+          ? formatModel.value?.["seriesExtend.barWidth"] || 20
+          : "auto";
+    },
+  },
+  {
+    label: "",
+    prop: "",
+    type: "dependency",
+    name: ["seriesExtend.fixedBarWidth"],
+    children: (model) => {
+      return model["seriesExtend.fixedBarWidth"]
+        ? [
+            {
+              label: "柱宽",
+              prop: "seriesExtend.barWidth",
+              type: "inputNumber",
+              fieldProps: {
+                addonAfter: "px",
+              },
+              defaultValue: 20,
+            },
+          ]
+        : [];
+    },
+  },
+  {
+    label: "系列间隔",
+    prop: "seriesExtend.barGap",
+    type: "slider",
+    defaultValue: 30,
+    format: (formatFormatModel, value) => {
+      formatFormatModel.value['seriesExtend.barGap'] = value + "%";
+    },
+    valueToForm: (value) => {
+      return +(value?.replace("%", "") || 0);
+    },
+  },
+  {
+    label: "分类间隔",
+    prop: "seriesExtend.barCategoryGap",
+    type: "slider",
+    defaultValue: 20,
+    format: (formatFormatModel, value) => {
+      formatFormatModel.value['seriesExtend.barCategoryGap'] = value + "%";
+    },
+    valueToForm: (value) => {
+      return +(value?.replace("%", "") || 0);
+    },
+  },
+  {
+    label: "边框",
+    prop: "",
+    type: "divider",
+  },
+  {
+    label: "线宽",
+    prop: "seriesExtend.itemStyle.borderWidth",
+    type: "inputNumber",
+    fieldProps: {
+      addonAfter: "px",
+    },
+    defaultValue: 0,
+  },
+  {
+    label: "颜色",
+    prop: "seriesExtend.itemStyle.borderColor",
+    type: "colorSelect",
+    defaultValue: "#ccc",
+  },
+  {
+    label: "圆角",
+    prop: "seriesExtend.itemStyle.borderRadius",
+    type: "inputNumber",
+    fieldProps: {
+      addonAfter: "px",
+    },
+    defaultValue: 0,
+  },
+];
+const formItems: IFormItem[] = [
+  chartFormItemsMap.title,
+  chartFormItemsMap.legend,
+  chartFormItemsMap.label,
+  {
+    ...chartFormItemsMap.series,
+    children: (chartFormItemsMap.series.children as IFormItem[]).concat(
+      baseSeries
+    ),
+  },
+  chartFormItemsMap.xAxis,
+  chartFormItemsMap.yAxis,
+  chartFormItemsMap.tooltip,
+];
+
+const handleDataSourceChange = (data: any) => {
+  emit("change", {
+    ...props,
+    dataSource: data,
+  });
+};
+const handleFormChange = (formatData: any) => {
+  const obj = cloneDeep(props);
+  Object.keys(formatData).forEach((key) => {
+    set(obj, key, formatData[key]);
+  });
+  emit("change", obj);
+};
+</script>
+
+<style lang="less" scoped>
+.config-tab {
+  text-align: center;
+  margin-bottom: 12px;
+}
+</style>

+ 218 - 0
packages/shalu-dashboard-ui/components/charts/Strip/BasicStrip/src/props.ts

@@ -0,0 +1,218 @@
+import type { PropType, ExtractPropTypes } from "vue";
+import { EChartsOption } from "echarts";
+import { getNormalizedChart, dataSource } from "../../../utils";
+import { DataSourceType } from "../../../chartEnum";
+
+export const componentProps = {
+  width: {
+    type: Number as PropType<number>,
+    default: 400,
+  },
+  height: {
+    type: Number as PropType<number>,
+    default: 260,
+  },
+  dataSource,
+  // 标题
+  title: {
+    type: Object as PropType<EChartsOption["title"]>,
+  },
+  // 图例
+  legend: {
+    type: Object as PropType<EChartsOption["legend"]>,
+  },
+  // 背景
+  backgroundColor: {
+    type: String as PropType<string>,
+  },
+  // 边框
+  grid: {
+    type: Object as PropType<EChartsOption["grid"]>,
+  },
+  // 提示框
+  tooltip: {
+    type: Object as PropType<EChartsOption["tooltip"]>,
+  },
+  // x轴数据
+  xAxis: {
+    type: Object as PropType<EChartsOption["xAxis"]>,
+  },
+  // y轴数据
+  yAxis: {
+    type: Object as PropType<EChartsOption["yAxis"]>,
+  },
+  // 折线
+  series: {
+    type: Array as PropType<EChartsOption["series"]>,
+  },
+  // color
+  color: {
+    type: Object as PropType<EChartsOption["color"]>
+  },
+  // 系列设置
+  seriesExtend: {
+    type: Object as PropType<EChartsOption["series"]>,
+    default: () => seriesExtend
+  }
+};
+
+/* 系列相关 */
+const series: EChartsOption['series'] = [];
+const seriesExtend = {
+  // @ts-ignore
+  fixedBarWidth: false,
+  barWidth: 'auto',
+  barGap: '10%',
+  barCategoryGap: '20%',
+  itemStyle: {
+    borderColor: '#ccc',
+    borderRadius: 0,
+    borderWidth: 0,
+  }
+};
+
+const chartOptions = getNormalizedChart({
+  title: {
+    text: "条形图标题",
+  },
+  // y轴
+  yAxis: {
+    type: "category",
+    name: '',
+    nameLocation: "middle",
+    nameTruncate: {
+      ellipsis: "...",
+      maxWidth: 80
+    },
+    nameTextStyle: {
+      color: '#FFFFFFFF',
+      fontSize: 12,
+      fontWeight: 'normal',
+      fontStyle: 'normal'
+    },
+    axisLabel: {
+      show: true,
+      color: "#9fadbf",
+      fontSize: 12,
+      fontWeight: "normal",
+      fontStyle: "normal",
+      rotate: 90
+    },
+    axisLine: {
+      show: true,
+      lineStyle: {
+        width: 1
+      }
+    },
+    axisTick: {
+      show: true,
+      lineStyle: {
+        width: 1,
+        color: "#ccc"
+      }
+    },
+    data: ['轴标签A', '轴标签B', '轴标签C', '轴标签D']
+  },
+  // x轴
+  xAxis: {
+    splitLine: {
+      show: true,
+      lineStyle: {
+        type: "dashed",
+        color: "#36485f",
+      },
+    },
+    type: "value",
+    name: '',
+    nameLocation: "middle",
+    nameTruncate: {
+      ellipsis: "...",
+      maxWidth: 80
+    },
+    nameTextStyle: {
+      color: '#FFFFFFFF',
+      fontSize: 12,
+      fontWeight: 'normal',
+      fontStyle: 'normal'
+    },
+    axisLabel: {
+      show: true,
+      color: "#9fadbf",
+      fontSize: 12,
+      fontWeight: "normal",
+      fontStyle: "normal",
+      rotate: 0,
+    },
+    axisLine: {
+      show: false,
+      lineStyle: {
+        width: 1,
+        color: "#ccc"
+      }
+    },
+    axisTick: {
+      show: false,
+      lineStyle: {
+        width: 1,
+        color: "#ccc"
+      }
+    }
+  },
+  series,
+  seriesExtend
+})
+
+export const defaultPropsValue: EChartsOption = {
+  // 组件容器默认属性
+  container: {
+    props: {
+      width: 400,
+      height: 260,
+    },
+  },
+  // 图表默认属性
+  props: {
+    // 数据源
+    dataSource: {
+      sourceType: DataSourceType.STATIC,
+      data: {
+        series: [
+          {
+            type: 'bar',
+            name: '系列1',
+            data: [10, 30, 20, 40]
+          },
+          {
+            type: 'bar',
+            name: '系列2',
+            data: [15, 35, 25, 45]
+          },
+        ]
+      },
+      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
+  },
+};
+
+export type BasicStripProps = ExtractPropTypes<typeof componentProps>;

+ 21 - 0
packages/shalu-dashboard-ui/components/charts/config/chartFormItemsMap.ts

@@ -248,6 +248,9 @@ export const chartFormItemsMap: Record<string, IFormItem> = {
                   prop: "legend.borderColor",
                   type: "colorSelect",
                   defaultValue: "#ccc",
+                  fieldProps: {
+                    gradient: false
+                  }
                 },
                 {
                   label: "圆角",
@@ -476,6 +479,9 @@ export const chartFormItemsMap: Record<string, IFormItem> = {
         prop: "xAxis.axisLine.lineStyle.color",
         type: "colorSelect",
         defaultValue: "#ccc",
+        fieldProps: {
+          gradient: false
+        }
       },
       {
         label: "刻度",
@@ -521,6 +527,9 @@ export const chartFormItemsMap: Record<string, IFormItem> = {
                   prop: "xAxis.axisTick.lineStyle.color",
                   type: "colorSelect",
                   defaultValue: "#ccc",
+                  fieldProps: {
+                    gradient: false
+                  }
                 },
               ]
             : [];
@@ -744,6 +753,9 @@ export const chartFormItemsMap: Record<string, IFormItem> = {
                   prop: "yAxis.axisLine.lineStyle.color",
                   type: "colorSelect",
                   defaultValue: "#ccc",
+                  fieldProps: {
+                    gradient: false
+                  }
                 },
               ]
             : [];
@@ -793,6 +805,9 @@ export const chartFormItemsMap: Record<string, IFormItem> = {
                   prop: "yAxis.axisTick.lineStyle.color",
                   type: "colorSelect",
                   defaultValue: "#ccc",
+                  fieldProps: {
+                    gradient: false
+                  }
                 },
               ]
             : [];
@@ -974,6 +989,9 @@ export const chartFormItemsMap: Record<string, IFormItem> = {
                   prop: "tooltip.borderColor",
                   type: "colorSelect",
                   defaultValue: "#ccc",
+                  fieldProps: {
+                    gradient: false
+                  }
                 },
                 {
                   label: "圆角",
@@ -1180,6 +1198,9 @@ export const chartFormItemsMap: Record<string, IFormItem> = {
                   prop: "label.borderColor",
                   type: "colorSelect",
                   defaultValue: "#ccc",
+                  fieldProps: {
+                    gradient: false
+                  }
                 },
                 {
                   label: "圆角",

+ 12 - 2
packages/shalu-dashboard-ui/components/charts/hooks/useChartOptions.ts

@@ -5,7 +5,11 @@ import { useRequest } from "vue-hooks-plus";
 import { DataSourceType } from "../chartEnum";
 import { message } from "ant-design-vue";
 import { cllJsCode } from "../utils";
-export const useChartOptions = (chartProps: Record<string, any>) => {
+
+type ExtOptions = {
+  grid: EChartsOption["grid"];
+}
+export const useChartOptions = (chartProps: Record<string, any>, extOptions?: ExtOptions) => {
   const dataSource = chartProps.dataSource || {};
   const xAxis = ref<EChartsOption["xAxis"]>();
   const yAxis = ref<EChartsOption["yAxis"]>();
@@ -106,7 +110,13 @@ export const useChartOptions = (chartProps: Record<string, any>) => {
 
   // 获取grid
   const getGrid = (opt: EChartsOption) => {
-    let bottom = 34, right = 20, left = 30, top = 20;
+    // 试着从配置里读取
+    let { bottom = 34, right = 20, left = 30, top = 20} = extOptions?.grid && !Array.isArray(extOptions?.grid) ? extOptions.grid : {};
+    bottom = Number(bottom);
+    top = Number(top);
+    right = Number(right);
+    left = Number(left);
+
     // 有标题
     if(!Array.isArray(opt.title) && opt.title?.show) {
       top += 20;

+ 1 - 1
packages/shalu-dashboard-ui/components/codeEditor/src/Editor.vue

@@ -12,7 +12,7 @@ import {
 } from "vue";
 import jsBeautify from "js-beautify";
 
-import * as monaco from "monaco-editor";
+import * as monaco from "monaco-editor/esm/vs/editor/editor.api";
 //@ts-ignore
 import editorWorker from "monaco-editor/esm/vs/editor/editor.worker?worker";
 //@ts-ignore

+ 60 - 13
packages/shalu-dashboard-ui/components/components.ts

@@ -1,53 +1,96 @@
 export const asyncComponentAll = {
   Title: () => import('./text/Title'),
+
   BasicLine: () => import('./charts/Line/BasicLine'),
+
   BasicBar: () => import('./charts/Bar/BasicBar'),
+  PercentBar: () => import('./charts/Bar/PercentBar'),
+  StackBar: () => import('./charts/Bar/StackBar'),
+  PolarBar: () => import('./charts/Bar/PolarBar'),
+
   BasicPie: () => import('./charts/Pie/BasicPie'),
+
+  BasicStrip: () => import('./charts/Strip/BasicStrip'),
+
   Container: () => import('./container'),
 }
-
+/* 数据源 */
 export { DataSourceType } from './charts/chartEnum';
 export type { DataSource, ChartData } from './charts/types';
-
 /* 容器 */
 import { default as Container } from './container';
-
 /* 标题 */
 import { default as Title } from './text/Title'; 
+/* 自定义表单 */
+export type { IFormItem } from './cusForm';
+export { CusForm } from './cusForm';
 
 /* 基础折线图 */
 import { default as BasicLine } from './charts/Line/BasicLine';
 export { 
   Config as BasicLineConfig,
-  defaultPropsValue as BasicLineDefaultProps,
-  basicLineProps as BasicLineProps
+  defaultPropsValue as basicLineDefaultProps,
+  componentProps as basicLineComponentProps
 } from './charts/Line/BasicLine';
 
 /* 基础柱形图 */
 import { default as BasicBar } from './charts/Bar/BasicBar/src/BasicBar.vue';
 export { 
   Config as BasicBarConfig,
-  defaultPropsValue as BasicBarDefaultProps,
-  basicBarProps as BasicBarProps
+  defaultPropsValue as basicBarDefaultProps,
+  componentProps as basicBarProps
 } from './charts/Bar/BasicBar';
 
 /* 基础饼图 */
 import { default as BasicPie } from './charts/Pie/BasicPie/src/BasicPie.vue';
 export { 
   Config as BasicPieConfig,
-  defaultPropsValue as BasicPieDefaultProps,
-  basicPieProps as BasicPieProps
+  defaultPropsValue as basicPieDefaultProps,
+  componentProps as basicPieProps
 } from './charts/Pie/BasicPie';
 
-export type { IFormItem } from './cusForm';
-export { CusForm } from './cusForm';
+/* 基础条形图 */
+import { default as BasicStrip } from './charts/Strip/BasicStrip/src/BasicStrip.vue';
+export { 
+  Config as BasicStripConfig,
+  defaultPropsValue as basicStripDefaultProps,
+  componentProps as basicStripProps
+} from './charts/Strip/BasicStrip';
+
+/* 堆积柱形图 */
+import { default as StackBar } from './charts/Bar/StackBar/src/StackBar.vue';
+export { 
+  Config as StackBarConfig,
+  defaultPropsValue as stackBarDefaultProps,
+  componentProps as stackBarProps
+} from './charts/Bar/StackBar';
+
+/* 百分比堆积柱形图 */
+import { default as PercentBar } from './charts/Bar/PercentBar/src/PercentBar.vue';
+export { 
+  Config as PercentBarConfig,
+  defaultPropsValue as percentBarDefaultProps,
+  componentProps as percentBarProps
+} from './charts/Bar/PercentBar';
+
+/* 百分比堆积柱形图 */
+import { default as PolarBar } from './charts/Bar/PolarBar/src/PolarBar.vue';
+export { 
+  Config as PolarBarConfig,
+  defaultPropsValue as polarBarDefaultProps,
+  componentProps as polarBarProps
+} from './charts/Bar/PolarBar';
 
 export const components = {
   BasicLine,
   BasicBar,
   Title,
   BasicPie,
-  Container
+  Container,
+  BasicStrip,
+  StackBar,
+  PercentBar,
+  PolarBar
 }
 
 export {
@@ -55,5 +98,9 @@ export {
   BasicBar,
   Title,
   BasicPie,
-  Container
+  Container,
+  BasicStrip,
+  StackBar,
+  PercentBar,
+  PolarBar
 }