Browse Source

同步公共组件

lm 1 tháng trước cách đây
mục cha
commit
1c434067dd
3 tập tin đã thay đổi với 282 bổ sung169 xóa
  1. BIN
      src/assets/icon/file.png
  2. 258 142
      src/baseComponents/DoughnutCharts.vue
  3. 24 27
      src/baseComponents/FormDialog.vue

BIN
src/assets/icon/file.png


+ 258 - 142
src/baseComponents/DoughnutCharts.vue

@@ -9,7 +9,7 @@
             显示全部
             </el-checkbox>
         </div>
-        <div class="render_doughnut_charts" ref="chartsRef">
+        <div class="render_doughnut_charts" :style="{height:chartHeight}" ref="chartsRef">
         </div>
     </div>
 
@@ -19,44 +19,149 @@
 <script setup lang="ts">
 import {ref,onMounted, watch,computed} from 'vue'
 import * as echarts from 'echarts';
+import type { PropType } from 'vue'
 
+interface PieItm {
+    name:String,
+    value:String
+}
+interface SeiresLabelFormatter {
+    formatter:Function | String,
+    rich:Object
 
+}
 const props = defineProps({
+    chartHeight:{
+        type:String,
+        default:'270px'
+    },
+    //饼图数据
     chartData:{
         required:true,
-        type:{
-            series:Array
-        }
+        type:Array<PieItm>
     },
+    // 饼图扇形弧度
     rotateAngle:{
         type:[Number,null],
+        default:360
     },
-    randomActiveIndex:{
-        type:Number,
-        required:true
-    },
+    // 是否显示图例
     showLegend:{
         type:Boolean,
-        default:false
+        default:true
+    },
+    // 图例的位置
+    legendTop:{
+        type:[Number,String],
+        default:'auto'
+    },
+    legendBottom:{
+        type:[Number,String],
+        default:'auto'
+    },
+    legendLeft:{
+        type:[Number,String],
+        default:'auto'
     },
+    legendRight:{
+        type:[Number,String],
+        default:'auto'
+    },
+    // 图例布局朝向
+    legendOrient:{
+        type:String,
+        default:'vertical'
+    },
+    // 是否一键隐藏图例或展示图例
     showLegendOperationAll:{
         type:Boolean,
         default:false
     },
+    // 图例展示文本
+    legendFormatter:{
+        type:Function
+    },  
+
+    // 扇形图位置
+    pieTop:{
+        type:[Number,String],
+        default:0
+    },
+    pieLeft:{
+        type:[Number,String],
+        default:0
+    },
+    pieBottom:{
+        type:[Number,String],
+        default:0
+    },
+    pieRight:{
+        type:[Number,String],
+        default:0
+    },
+
+    pieRadius:{
+        type:Array,
+        required:true
+    },
+
+    
+
+    //展示扇区信息
     showSeriesLabel:{
         type:Boolean,
-        default:false
-    }          
+        default:true
+    },
+    // 扇形的信息展示位置
+    seriesLabelPosition:{
+        type:String,
+    },
+    // 扇区信息的格式
+    seiresLabelFormatter: {
+        type:Object as PropType<SeiresLabelFormatter>
+    },
+   
+    // 展示鼠标悬浮提示
+    showTooltip:{
+        type:Boolean,
+        default:true,
+    },
+    // 鼠标悬浮提示 展示的位置
+    tooltipPosition:{
+        type:[Array,Function],
+        default:()=>{
+            return function (point, params, dom, rect, size) {
+                // 固定在鼠标右侧
+                return [point[0], point[1]];
+            }
+        }
+    },
+    // 图表鼠标悬浮提示信息
+    tooltipFormatter:{
+        type:[Function,String] 
+    },
+
+
+    //页面动态变化的标识
+    randomIndex:{
+        type:Number,
+        required:true
+    },
+    
 })
 
 
+
+
 const chartsRef = ref(null)
 const myChart = ref()
 
+/**
+ * 图例全选/取消全选
+ */
 const showAll = ref(true)
 const indeterminate = ref(false)
 const selectedLegendList = ref([])
-
 const HandleCheckAllOrNot = (val)=>{
     console.log('当前的变更值',val)
     showAll.value = val
@@ -83,42 +188,123 @@ const HandleCheckAllOrNot = (val)=>{
     }
 }
 
-let baseOption = {
-  tooltip: {
-    trigger: 'item',
-    borderWidth:0,
-    showContent:true,
-    textStyle:{
-        color:'#666'
+
+// 基础配置
+const getBaseOption = () => ({
+    tooltip: {
+        trigger: 'item',
+        borderWidth:0,
+        textStyle:{
+            color:'#666'
+        },
     },
-    formatter: function (params) {
-        const {name,marker,percent,value} = params
-        return marker + name.split(':')[0] + `<span style=\"margin-left:10px;\">${value} %</span>` ;
+    legend: {
+        itemGap:15,
+        itemWidth:20,
+        padding:1,
+        itemHeight:10,
+        textStyle:{
+            fontSize:12,
+        },
     },
-    position:function (point, params, dom, rect, size) {
-      // 固定在鼠标右侧
-      return [point[0], point[1]];
-    }
-  },
-  legend: {
-    show:props.showLegend,
-    top: '0%',
-    left:props.showLegendOperationAll ?  92 : 'left',
-    itemGap:15,
-    itemWidth:20,
-    padding:1,
-    itemHeight:10,
-    formatter:function (name) {
-        return name;
+    color:['#5470C6','#3BA272','#FAC858','#995FB3','#EE6666','#72C0DD','#90CB75','#EA7ACB','#FC8451','#65789B',
+        '#178BD3','#26A616','#A6129E','#D3A141','#234098','#047640','#B43535','#848BDC','#D6AF83','#84313D'],
+
+    series: [
+        {
+            name: '',
+            type: 'pie',
+            startAngle:90,
+            avoidLabelOverlap: false,
+            labelLine: {
+                length:15,
+                length2:10,
+            },
+            emphasis: {
+                scale:false,
+            },
+            data: [],
+        },
+        {
+            name: '',
+            type: 'pie',
+            startAngle:90,
+            endAngle:'auto',
+            avoidLabelOverlap: false,
+            label: {
+                show: false,
+            },
+            labelLine: {
+                show: false,
+            },
+            emphasis: {
+                scale:false,
+                label: {
+                    show: false,
+                }
+            },
+            data:[],
+            emptyCircleStyle:{
+                color:'#F4F5F8',
+                opacity:1
+            },
+        }
+    ]
+});
+// 动态配置
+const getDynamicOption = ()=>({
+    tooltip: {
+        showContent:props.showTooltip,
+        formatter: props.tooltipFormatter,
+        position:props.tooltipPosition
     },
-    textStyle:{
-        fontSize:12,
+    legend: {
+        show: props.showLegend,
+        top: props.legendTop,
+        right:props.legendRight,
+        left:props.legendLeft,
+        bottom:props.legendBottom,
+        orient:props.legendOrient,
+        // 自定义图例展示内容:名称:占比xx%
+        formatter: props.legendFormatter,
     },
-  },
-  color:['#5470C6','#3BA272','#FAC858','#995FB3','#EE6666','#72C0DD','#90CB75','#EA7ACB','#FC8451','#65789B',
-          '#178BD3','#26A616','#A6129E','#D3A141','#234098','#047640','#B43535','#848BDC','#D6AF83','#84313D'],
-};
+    series: [
+        {
+            name: '',
+            radius: props.pieRadius,
+            endAngle:endAngle.value,
+            top:props.pieTop,
+            left:props.pieLeft,
+            right:props.pieRight,
+            bottom:props.pieBottom,
+            label: {
+                show: props.showSeriesLabel,
+                position:props.seriesLabelPosition,
+                ...props.seiresLabelFormatter,
+            },
+            labelLine: {
+                show: props.showSeriesLabel,
+            },
+            emphasis: {
+                disabled: !props.showTooltip,
+                label: {
+                    show: props.showSeriesLabel,
+                }
+            },
+            data: props?.chartData || [],
+        },
+        {
+            name: '',
+            radius: props.pieRadius,
+            top:props.pieTop,
+            left:props.pieLeft,
+            right:props.pieRight,
+            bottom:props.pieBottom,
+        }
+    ]
+})
 
+// 饼图的扇区结束位置
 const endAngle = computed(()=>{
     console.log('旋转角度',props.rotateAngle)
     if(props.rotateAngle &&  props.rotateAngle !== 360){
@@ -132,101 +318,24 @@ const endAngle = computed(()=>{
     }
 })
 
-const UpdateChart = (dynamicOption,baseOption) =>{
-    myChart.value.setOption({
-        ...baseOption,
-        ...dynamicOption
-
-    });
+const UpdateChart = () =>{
+    if(!myChart.value) return
+    console.log('labelformater值',props.seiresLabelFormatter)
+    myChart.value.setOption(getBaseOption());
+    myChart.value.setOption(getDynamicOption());
 }
 
 const HandleInitChart = ()=>{
-    // 基于准备好的dom,初始化echarts实例
-    myChart.value = echarts.init(chartsRef.value);
+    console.log('准备初始化了--')
 
-    let dynamicOption = {
-        series: [
-            {
-                name: '',
-                type: 'pie',
-                radius: props.showSeriesLabel ? ['40%', '80%'] : ['50%', '90%'],
-                startAngle:90,
-                endAngle:endAngle.value,
-                avoidLabelOverlap: false,
-                top:35,
-                left:60,
-                right:60,
-                bottom:10,
-                label: {
-                    show: props.showSeriesLabel,
-                    position: 'outside',
-                    color:'#666666',
-                    formatter:(params)=>{
-                        // console.log('获取到的参数',params)
-                        const {name,value,percent} = params
-                        return [
-                            `{a|${name.split(':')[0]}:}`,
-                            `{b|${value}%}`,
-                        ].join('\n')
-                    },
-                    rich:{
-                        b:{
-                            align:'left',
-                            lineHeight: 20,
-                        },
-                        a:{
-                            // lineHeight: 20,
-                        }
-                    },
-                },
-                labelLine: {
-                    show: props.showSeriesLabel,
-                    length:15,
-                    length2:10,
-                },
-                emphasis: {
-                    scale:false,
-                    label: {
-                        show: true,
-                        //   fontSize: 40,
-                        //   fontWeight: 'bold'
-                    }
-                },
-                data:props?.chartData?.series[0]?.data || [],
-            },
-            {
-                name: '',
-                type: 'pie',
-                radius: props.showSeriesLabel ? ['40%', '80%'] : ['50%', '90%'],
-                startAngle:90,
-                endAngle:'auto',
-                avoidLabelOverlap: false,
-                top:35,
-                left:60,
-                right:60,
-                bottom:10,
-                label: {
-                    show: false,
-                },
-                labelLine: {
-                    show: false,
-                },
-                emphasis: {
-                    scale:false,
-                    label: {
-                        show: false,
-                    }
-                },
-                data:[],
-                emptyCircleStyle:{
-                    color:'#F4F5F8',
-                    opacity:1
-                },
-            }
-        ]
+    if(myChart.value){
+        myChart.value?.dispose();
+        myChart.value = null
     }
-    UpdateChart(dynamicOption,baseOption)
 
+    // 基于准备好的dom,初始化echarts实例
+    myChart.value = echarts.init(chartsRef.value);
+    UpdateChart()
 
     myChart.value.on('legendselectchanged', function (params) {
         console.log('图例状态切换了',params);
@@ -251,21 +360,29 @@ const HandleInitChart = ()=>{
 }
 
 
-watch([()=>props.randomActiveIndex,()=>props.rotateAngle],([newVal,newRotate])=>{
-    // 监听到父组件页面的容器尺寸变化后,要重新init
-    if(myChart.value){
-        myChart.value.dispose();
-        myChart.value = null
+watch([()=>props.randomIndex,()=>props.rotateAngle],([newRandom,newRotate],[oldRandom,oldRotate])=>{
+    if(!myChart.value) return
+    // rotateAngle 实际发生变化时需要重建以应用 endAngle 变更
+    if(newRotate !== oldRotate){
+        HandleInitChart()
+        return
     }
-    HandleInitChart()
+    console.log('执行到了--')
+    // 仅容器尺寸/布局变化时调整大小
+    try{ myChart.value.resize() }catch(e){}
 })
 
 watch(()=>props.chartData,(newVal)=>{
-    // 数据变换后要重新渲染
-    UpdateChart(newVal,{})
+    // 数据变换后只更新 series.data
+    if(!myChart.value) return
+    try{
+        // 同步更新 series 数据并刷新 legend 和 label 的展示(使百分比同步)
+        myChart.value.setOption(getDynamicOption())
+    }catch(e){
+        HandleInitChart()
+    }
 },{deep:true})
 
-
 onMounted(()=>{
     HandleInitChart()
 })
@@ -278,7 +395,6 @@ onMounted(()=>{
 .render_doughnut_charts{
     position: relative;
     width: 100%;
-    height: 270px;
 }
 
 .operation{

+ 24 - 27
src/baseComponents/FormDialog.vue

@@ -304,7 +304,6 @@
             <template  v-else-if="0" >
               <div class="img_file_area">
                 <el-upload 
-                  v-model:file-list="fileList"
                   action="#" 
                   :auto-upload="false"
                   @change="(file)=>handleFileChange(file,item)"
@@ -331,7 +330,6 @@
             <template  v-else-if="item.type=='imgFile'" >
               <div class="img_file_area1">
                 <el-upload 
-                  v-model:file-list="fileList"
                   action="#" 
                   :auto-upload="false"
                   @change="(file)=>handleFileChange(file,item)"
@@ -344,7 +342,7 @@
                   </div>
                 </el-upload>
 
-                <span v-if="props.fileLoading" class="cover"  style="gap: 3px;line-height: 1.5;color:#2e64fa">
+                <span v-if="formData[item.key].fileLoading" class="cover"  style="gap: 3px;line-height: 1.5;color:#2e64fa">
                   上传中
                   <el-icon class="is-loading">
                     <Loading />
@@ -352,7 +350,7 @@
                 </span>
 
                 <!-- 单张 -->
-                <template  v-if="item.limit == 1 && !props.fileLoading">
+                <template  v-if="item.limit == 1 && !formData[item.key].fileLoading">
                   <span  class="img_file" v-if="formData[item.key]?.url">
                     <img class="cover"   :src="formData[item.key]?.url" alt="">
                     <img   class="close" @click="HandleRemove(item.key)" :src="closeFillImg">
@@ -360,8 +358,9 @@
                 </template>
 
                   <!-- 多张 -->
+                  <!-- 多张 对象结构 { files:[{name:'',url:''},{}],fileLoading:false}-->
                   <template v-else-if="item.limit > 1">
-                    <span  class="img_file" v-for="fileItm in formData[item.key]" :key="fileItm">
+                    <span  class="img_file" v-for="fileItm in formData[item.key].files" :key="fileItm">
                       <img class="cover"   :src="fileItm?.url" alt="">
                       <div class="operation_btn">
                         <span class="delete" @click="HandleRemoveSingleImg(item.key,fileItm)">删除</span>
@@ -382,7 +381,6 @@
             <template  v-else-if="item.type=='fileShowList'" >
               <div class="file_show_list">
                 <el-upload 
-                  v-model:file-list="fileList"
                   action="#" 
                   :auto-upload="false"
                   @change="(file)=>handleFileChange(file,item)"
@@ -395,16 +393,16 @@
                   </div>
                 </el-upload>
 
-                <span v-if="props.fileLoading" class="cover"  style="gap: 3px;line-height: 1.5;color:#2e64fa">
-                  上传中
-                  <el-icon class="is-loading">
-                    <Loading />
-                  </el-icon>
-                </span>
-
+                
                 <div class="file_list">
+                  <span v-if="formData[item.key].fileLoading" class="cover"  style="gap: 3px;line-height: 1.5;color:#2e64fa">
+                    上传中
+                    <el-icon class="is-loading">
+                      <Loading />
+                    </el-icon>
+                  </span>
                   <!-- 单张 -->
-                  <template  v-if="item.limit == 1 && !props.fileLoading">
+                  <template  v-if="item.limit == 1 && !formData[item.key].fileLoading">
                     <div  class="file_desc" v-if="formData[item.key]?.url">
                       <img class="icon" :src="fileIcon" alt="">
                       <div class="file_msg">
@@ -423,9 +421,9 @@
                     </div>
                   </template>
 
-                  <!-- 多张 -->
+                  <!-- 多张 对象结构 { files:[{name:'',url:''},{}],fileLoading:false}-->
                   <template v-else-if="item.limit > 1">
-                    <div  class="file_desc" v-for="fileItm in formData[item.key]" :key="fileItm">
+                    <div  class="file_desc" v-for="fileItm in formData[item.key].files" :key="fileItm">
                       <img class="icon" :src="fileIcon" alt="">
                       <div class="file_msg">
                         <span class="name" :title="fileItm?.name">{{ fileItm?.name }}</span>
@@ -517,16 +515,10 @@ import closeFillImg from '@/assets/icon/close_fill.svg'
 
 import { ElMessage } from 'element-plus'
 
-// import fileIcon from '@/assets/icon/file.png'
+import fileIcon from '@/assets/icon/file.png'
 
 
 const previewVisible = ref(false)
-const fileList = ref([
-   {
-    name: 'food.jpeg',
-    url: 'https://jtzx001.oss-accelerate.aliyuncs.com/huijiaoyan_test/20260114_50007/14091841_0834e935-5393-4981-ba53-3c37fda73a1f.jpeg',
-  },
-])
 
 const handlePictureCardPreview = ()=>{
   console.log('预览')
@@ -712,10 +704,15 @@ const HandleRemove = (formKey)=>{
 }
 
 const HandleRemoveSingleImg = (formKey,aimFile) =>{
-  formData.value[formKey] = formData.value[formKey].filter(item=>{
-    return item !== aimFile
-  })
-
+  formData.value[formKey] = {
+    ...formData.value[formKey],
+    files:formData.value[formKey].files.filter(item=>{
+      return item !== aimFile
+    })
+  }
+  // formData.value[formKey].filter(item=>{
+  //   return item !== aimFile
+  // })
 }
 
 const imgVisible = ref(false)