24个文件已修改
5个文件已添加
5086 ■■■■■ 已修改文件
H5/App.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
H5/components/voiceInputPopup.vue 104 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
H5/config/index.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
H5/pages/Appeal/Appeal.vue 860 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
H5/pages/add-progress/index.vue 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
H5/pages/index/index.vue 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
H5/pages/statistics/index.vue 2022 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
H5/pages/statistics/service.js 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
H5/pages/supervision/edit-supervision-progress.vue 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
H5/pages/supervision/supervision.vue 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
H5/pages/work-detail/work-detail.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
H5/static/home/Group 2@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
H5/utils/request.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
management/config/env.ts 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
management/config/routes.ts 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
management/package.json 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
management/src/pages/appeal-management/detail.jsx 235 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
management/src/pages/appeal-management/service.js 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
management/src/pages/party/manage/index.jsx 188 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
management/src/pages/setting/role/components/addAndEdit.jsx 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
management/src/pages/setting/role/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
management/src/pages/setting/user/components/addAndEdit.jsx 693 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
management/src/pages/setting/user/index.jsx 181 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
management/src/pages/setting/user/index.less 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
management/src/pages/setting/user/service.js 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
management/src/pages/statistics/components/addAndEdit.jsx 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
management/src/pages/statistics/index.jsx 558 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
management/src/pages/statistics/service.js 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
management/src/requestErrorConfig.ts 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
H5/App.vue
@@ -9,7 +9,6 @@
            trialNode.innerHTML = '试运行版本';
            document.body.appendChild(trialNode);
            // #endif
        },
        onShow: function() {
H5/components/voiceInputPopup.vue
@@ -107,7 +107,7 @@
        console.log('当前录音片段URL:', res.localUrl)
        console.log('当前已存储的片段数:', this.recordSegments.length)
        console.log('当前片段:', this.currentSegment)
        // 只有当当前片段与上一个不同时才添加
        if (!this.currentSegment || this.currentSegment.url !== res.localUrl) {
          console.log('添加新的录音片段')
@@ -183,7 +183,7 @@
      try {
        const audioContext = new (window.AudioContext || window.webkitAudioContext)();
        const audioBuffers = [];
        // 加载所有音频文件
        for (const url of audioUrls) {
          const response = await fetch(url);
@@ -194,7 +194,7 @@
        // 计算总时长
        const totalLength = audioBuffers.reduce((acc, buffer) => acc + buffer.length, 0);
        // 创建新的音频缓冲区
        const mergedBuffer = audioContext.createBuffer(
          audioBuffers[0].numberOfChannels,
@@ -215,7 +215,7 @@
        // 将合并后的音频转换为 Blob
        const wavBlob = await this.audioBufferToWav(mergedBuffer);
        const mergedUrl = URL.createObjectURL(wavBlob);
        return mergedUrl;
      } catch (error) {
        console.error('合并音频失败:', error);
@@ -284,65 +284,67 @@
      if (this.isRecording) {
        this.$refs.mumuRecorder.stop()
      }
      // 停止计时
      this.stopTimer()
      // 重置状态
      this.isRecording = false
      this.isPaused = false
      this.currentSegment = null
      // 处理录音片段
      if (this.recordSegments.length > 0) {
        try {
          uni.showLoading({
            title: '正在处理音频...'
          })
          // 获取所有录音片段的URL
          const audioUrls = this.recordSegments.map(segment => segment.url)
          console.log('准备合并的音频片段列表:', audioUrls)
          console.log('音频片段数量:', audioUrls.length)
          // 合并音频文件
          const mergedUrl = await this.mergeAudioFiles(audioUrls)
          // 创建合并后的音频数据对象
          const mergedAudioData = {
            url: this.recordSegments[this.recordSegments.length - 1].url,
            data: this.recordSegments[this.recordSegments.length - 1].data,
            duration: this.seconds + this.minutes * 60 + this.hours * 3600
      setTimeout(async() => {
        console.log('处理录音片段')
        // 处理录音片段
        if (this.recordSegments.length > 0) {
          try {
            uni.showLoading({
              title: '正在处理音频...'
            })
            // 获取所有录音片段的URL
            const audioUrls = this.recordSegments.map(segment => segment.url)
            console.log('准备合并的音频片段列表:', audioUrls)
            console.log('音频片段数量:', audioUrls.length)
            // 合并音频文件
            const mergedUrl = await this.mergeAudioFiles(audioUrls)
            // 创建合并后的音频数据对象
            const mergedAudioData = {
              url: this.recordSegments[this.recordSegments.length - 1].url,
              data: this.recordSegments[this.recordSegments.length - 1].data,
              duration: this.seconds + this.minutes * 60 + this.hours * 3600
            }
            uni.showToast({
              title: '录制成功',
              icon: 'success',
              duration: 2000
            })
            setTimeout(() => {
              this.$emit('submit', mergedAudioData)
            }, 2000)
            uni.hideLoading()
            this.closePopup()
          } catch (error) {
            console.error('音频合并失败:', error)
            uni.hideLoading()
            uni.showToast({
              title: '音频合并失败,请重试',
              icon: 'none',
              duration: 2000
            })
          }
        } else {
          uni.showToast({
            title: '录制成功',
            icon: 'success',
            duration: 2000
          })
          setTimeout(() => {
            this.$emit('submit', mergedAudioData)
          }, 2000)
          uni.hideLoading()
          this.closePopup()
        } catch (error) {
          console.error('音频合并失败:', error)
          uni.hideLoading()
          uni.showToast({
            title: '音频合并失败,请重试',
            title: '未检测到录音文件',
            icon: 'none',
            duration: 2000
          })
        }
      } else {
        uni.showToast({
          title: '未检测到录音文件',
          icon: 'none',
          duration: 2000
        })
      }
      }, 1000);
    }
  },
  beforeDestroy() {
H5/config/index.js
@@ -1,5 +1,5 @@
export default {
    // BASE_URL: 'https://huacheng.psciio.com',
    BASE_URL: 'http://192.168.110.188:6194',
    BASE_URL: 'https://3z8tsrt8s2uq.ngrok.xiaomiqiu123.top',
    imageUrl: 'https://huacheng.psciio.com/api/huacheng-applets/common/uploadimages',
}
H5/pages/Appeal/Appeal.vue
@@ -146,459 +146,485 @@
</template>
<script>
import dayjs from '@/uni_modules/uview-ui/libs/util/dayjs.js';
import {
    save,
    getproblem
} from './service.js'
import config from '@/config/index.js'
import voiceInputPopup from '@/components/voiceInputPopup.vue'
import {
    mapActions,
    mapState
} from "vuex";
    import dayjs from '@/uni_modules/uview-ui/libs/util/dayjs.js';
    import {
        save,
        getproblem
    } from './service.js'
    import config from '@/config/index.js'
    import voiceInputPopup from '@/components/voiceInputPopup.vue'
    import {
        mapActions,
        mapState
    } from "vuex";
export default {
    components: {
        voiceInputPopup
    },
    data() {
        return {
            showList: false,
            showDate: false,
            value1: Number(new Date()),
            columns: [
                ['医疗', '教育', '就业', '住房', '养老']
            ],
            time: '',
            problemType: '',
            name: '',
            contactNumber: '',
            location: '',
            detailedAddress: '',
            descriptionTitle: '',
            descriptionContent: '',
            videoContent: [],
            latitude: '',
            longitude: '',
            images: [],
            videos: [],
            voiceFile: '',//语音文件多个逗号拼接
            userInfo: uni.getStorageSync('userInfo'), //个人信息
            voiceInputShow: false,
        };
    },
    computed: {
        ...mapState(["playFlag"]),
    },
    onReady() {
        uni.setNavigationBarTitle({
            title: '诉求录入'
        })
    },
    onLoad() {
        this.getproblem()
        this.time = dayjs().format('YYYY-MM-DD')
    },
    methods: {
        ...mapActions(["playRecording", "pausePlaying"]),
        onPlayRecording(index) {
            // 先处理本地播放状态
            this.videoContent.forEach((item, i) => {
                item.playing = i === index;
            });
            // 调用store的播放方法,传url
            this.playRecording(this.videoContent[index].url);
    export default {
        components: {
            voiceInputPopup
        },
        onPausePlaying(index) {
            this.videoContent[index].playing = false;
            this.pausePlaying(this.videoContent[index].url);
        data() {
            return {
                showList: false,
                showDate: false,
                value1: Number(new Date()),
                columns: [
                    ['医疗', '教育', '就业', '住房', '养老']
                ],
                time: '',
                problemType: '',
                name: '',
                contactNumber: '',
                detailedAddress: '',
                descriptionTitle: '',
                descriptionContent: '',
                videoContent: [],
                latitude: '',
                longitude: '',
                location: '',
                images: [],
                videos: [],
                voiceFile: '', //语音文件多个逗号拼接
                userInfo: uni.getStorageSync('userInfo'), //个人信息
                voiceInputShow: false,
            };
        },
        voiceInput() {
            this.voiceInputShow = true;
        computed: {
            ...mapState(["playFlag"]),
        },
        closeVoiceInput() {
            this.voiceInputShow = false;
        },
        submitVoiceInput(e) {
            console.log('eeeeeeeeeeeeeeeeeee', e)
            this.videoContent.push({ url: e.url, data: e.data, playing: false });
            this.voiceInputShow = false;
        },
        previewImage(index) {
            uni.previewImage({
                urls: this.images,
                current: this.images[index],
                longPressActions: {
                    itemList: ['发送给朋友', '保存图片', '收藏'],
                    success: function (data) {
                    },
                    fail: function (err) {
                    }
                }
            });
        },
        deletimg(e) {
            this.images = this.images.filter(item => item != e)
        },
        deletvideo(e) {
            this.videos = this.videos.filter(item => item != e)
        },
        gotoPage(e) {
            uni.navigateTo({
                url: `/pages/work-detail/maxVideo?url=${e}`
        onReady() {
            uni.setNavigationBarTitle({
                title: '诉求录入'
            })
        },
        getproblem() {
            getproblem().then((resp => {
                this.columns = [resp.data.map(item => {
                    return item.name
                })]
            }))
        onLoad() {
            this.getproblem()
            this.time = dayjs().format('YYYY-MM-DD')
        },
        async submit(type) {
            if (this.videoContent.length > 0) {
                uni.showLoading({
                    title: '正在上传语音文件...'
                });
                const uploadPromises = this.videoContent.map(item => {
                    return new Promise((resolve, reject) => {
                        console.log('item.data', item.data)
        methods: {
            ...mapActions(["playRecording", "pausePlaying"]),
            onPlayRecording(index) {
                // 先处理本地播放状态
                this.videoContent.forEach((item, i) => {
                    item.playing = i === index;
                });
                // 调用store的播放方法,传url
                this.playRecording(this.videoContent[index].url);
            },
            onPausePlaying(index) {
                this.videoContent[index].playing = false;
                this.pausePlaying(this.videoContent[index].url);
            },
            voiceInput() {
                this.voiceInputShow = true;
            },
            closeVoiceInput() {
                this.voiceInputShow = false;
            },
            submitVoiceInput(e) {
                console.log('eeeeeeeeeeeeeeeeeee', e)
                this.videoContent.push({
                    url: e.url,
                    data: e.data,
                    playing: false
                });
                this.voiceInputShow = false;
            },
            previewImage(index) {
                uni.previewImage({
                    urls: this.images,
                    current: this.images[index],
                    longPressActions: {
                        itemList: ['发送给朋友', '保存图片', '收藏'],
                        success: function(data) {
                        },
                        fail: function(err) {
                        }
                    }
                });
            },
            deletimg(e) {
                this.images = this.images.filter(item => item != e)
            },
            deletvideo(e) {
                this.videos = this.videos.filter(item => item != e)
            },
            gotoPage(e) {
                uni.navigateTo({
                    url: `/pages/work-detail/maxVideo?url=${e}`
                })
            },
            getproblem() {
                getproblem().then((resp => {
                    this.columns = [resp.data.map(item => {
                        return item.name
                    })]
                }))
            },
            async submit(type) {
                if (this.videoContent.length > 0) {
                    uni.showLoading({
                        title: '正在上传语音文件...'
                    });
                    const uploadPromises = this.videoContent.map(item => {
                        return new Promise((resolve, reject) => {
                            console.log('item.data', item.data)
                            // uni.chooseAudio({
                            //     success:  (res)=> {
                            //         console.log(res.tempFilePaths[0]);
                            //         uni.uploadFile({
                            //             url: config.imageUrl,
                            //             filePath: res.tempFilePaths[0],  // 使用 Blob 数据
                            //             name: 'file',
                            //             header: {
                            //                 // 'Content-Type': 'multipart/form-data',
                            //                 'Authorization': uni.getStorageSync('token')
                            //             },
                            //             success: (uploadFileRes) => {
                            //                 const response = JSON.parse(uploadFileRes.data);
                            //                 if (response.code === 200) {
                            //                     resolve(response.data);
                            //                 } else {
                            //                     reject(new Error('上传失败'));
                            //                 }
                            //             },
                            //             fail: (error) => {
                            //                 reject(error);
                            //             }
                            //         });
                            //     }
                            // });
                            uni.uploadFile({
                                url: config.imageUrl,
                                file: item.data, // 使用 Blob 数据
                                name: 'file',
                                header: {
                                    // 'Content-Type': 'multipart/form-data',
                                'Authorization': uni.getStorageSync('token')
                                },
                                success: (uploadFileRes) => {
                                    const response = JSON.parse(uploadFileRes.data);
                                    if (response.code === 200) {
                                        resolve(response.data);
                                    } else {
                                        reject(new Error('上传失败'));
                                    }
                                },
                                fail: (error) => {
                                    reject(error);
                                }
                            });
                        });
                    });
                    try {
                        const uploadedUrls = await Promise.all(uploadPromises);
                        this.voiceFile = uploadedUrls.join(',');
                        console.log('this.voiceFile', this.voiceFile)
                        uni.hideLoading();
                    } catch (error) {
                        uni.hideLoading();
                        uni.showToast({
                            title: '语音文件上传失败',
                            icon: 'error'
                        });
                        return;
                    }
                }
                const preciseRegex = /^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/;
                if (!this.time) {
                    uni.showToast({
                        title: '请选择时间',
                        icon: 'none'
                    })
                    return
                } else if (!this.problemType) {
                    uni.showToast({
                        title: '请选择问题类型',
                        icon: 'none'
                    })
                    return
                } else if (!this.name) {
                    uni.showToast({
                        title: '请输入群众姓名',
                        icon: 'none'
                    })
                    return
                } else if (!this.contactNumber) {
                    uni.showToast({
                        title: '请输入联系电话',
                        icon: 'none'
                    })
                    return
                } else if (!preciseRegex.test(this.contactNumber)) {
                    uni.showToast({
                        title: '请输入正确的手机号',
                        icon: 'none'
                    })
                    return
                } else if (!this.location) {
                    uni.showToast({
                        title: '请选择地点',
                        icon: 'none'
                    })
                    return
                } else if (!this.detailedAddress) {
                    uni.showToast({
                        title: '请输入详细地址',
                        icon: 'none'
                    })
                    return
                }
                const data = {
                    time: this.time,
                    problemType: this.problemType,
                    name: this.name,
                    contactNumber: this.contactNumber,
                    location: this.location,
                    latitude: this.latitude,
                    longitude: this.longitude,
                    detailedAddress: this.detailedAddress,
                    descriptionContent: this.descriptionContent,
                    images: this.images.join(','),
                    videos: this.videos.join(','),
                    voiceFile: this.voiceFile,
                }
                // 问题上报
                if (type == 1) {
                    uni.navigateTo({
                        url: `/pages/problemReporting/problemReporting?data=${JSON.stringify(data)}`
                    })
                    return
                }
                // 办理结果录入
                if (type == 2) {
                    uni.navigateTo({
                        url: `/pages/result-entry/index?data=${JSON.stringify(data)}`
                    })
                    return
                }
                // 添加
                if (type == 3) {
                    save(data).then(resp => {
                        if (resp.code == 200) {
                            uni.showToast({
                                title: '保存成功',
                                icon: 'none'
                            })
                            setTimeout(() => {
                                uni.navigateBack()
                            }, 1500)
                        }
                    })
                }
            },
            confirmone(e) {
                this.time = dayjs(e.value).format('YYYY-MM-DD')
                this.showDate = false
            },
            confirmtwo(e) {
                this.problemType = e.value[0]
                this.showList = false
            },
            cancel() {
                this.showDate = false
                this.showList = false
            },
            close(e) {
                this.showDate = false
                this.showList = false
            },
            goTopagelocation() {
                uni.navigateTo({
                    url: '/pages/location/location'
                })
            },
            uploadImg() {
                uni.chooseImage({
                    count: 1, //默认9
                    sizeType: ['original', 'compressed'], //可以指定是原图还是压缩图,默认二者都有
                    sourceType: ['album'], //从相册选择
                    success: (res) => {
                        uni.showLoading()
                        console.log('res.tempFilePaths[0]', res.tempFilePaths[0])
                        uni.uploadFile({
                            url: config.imageUrl,
                            file: item.data,
                            filePath: res.tempFilePaths[0],
                            name: 'file',
                            // fileType: 'audio/mpeg',
                            // filePath: item.url,
                            // name: 'file',
                            header: {
                                'Content-Type': 'multipart/form-data',
                                'Authorization': uni.getStorageSync('token')
                            },
                            success: (uploadFileRes) => {
                                const response = JSON.parse(uploadFileRes.data);
                                if (response.code === 200) {
                                    resolve(response.data);
                                } else {
                                    reject(new Error('上传失败'));
                                }
                                this.images = [...this.images, JSON.parse(uploadFileRes.data).data]
                                uni.hideLoading()
                            },
                            fail: (error) => {
                                reject(error);
                            fail: () => {
                                uni.hideLoading()
                                uni.showToast({
                                    title: '上传失败',
                                    icon: 'error'
                                })
                            }
                        });
                    });
                        })
                    }
                });
                try {
                    const uploadedUrls = await Promise.all(uploadPromises);
                    this.voiceFile = uploadedUrls.join(',');
                    console.log('this.voiceFile', this.voiceFile)
                    uni.hideLoading();
                } catch (error) {
                    uni.hideLoading();
                    uni.showToast({
                        title: '语音文件上传失败',
                        icon: 'error'
                    });
                    return;
                }
            }
            const preciseRegex = /^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/;
            if (!this.time) {
                uni.showToast({
                    title: '请选择时间',
                    icon: 'none'
                })
                return
            } else if (!this.problemType) {
                uni.showToast({
                    title: '请选择问题类型',
                    icon: 'none'
                })
                return
            } else if (!this.name) {
                uni.showToast({
                    title: '请输入群众姓名',
                    icon: 'none'
                })
                return
            } else if (!this.contactNumber) {
                uni.showToast({
                    title: '请输入联系电话',
                    icon: 'none'
                })
                return
            } else if (!preciseRegex.test(this.contactNumber)) {
                uni.showToast({
                    title: '请输入正确的手机号',
                    icon: 'none'
                })
                return
            } else if (!this.location) {
                uni.showToast({
                    title: '请选择地点',
                    icon: 'none'
                })
                return
            } else if (!this.detailedAddress) {
                uni.showToast({
                    title: '请输入详细地址',
                    icon: 'none'
                })
                return
            }
            const data = {
                time: this.time,
                problemType: this.problemType,
                name: this.name,
                contactNumber: this.contactNumber,
                location: this.location,
                latitude: this.latitude,
                longitude: this.longitude,
                detailedAddress: this.detailedAddress,
                descriptionContent: this.descriptionContent,
                images: this.images.join(','),
                videos: this.videos.join(','),
                voiceFile: this.voiceFile,
            }
            // 问题上报
            if (type == 1) {
                uni.navigateTo({
                    url: `/pages/problemReporting/problemReporting?data=${JSON.stringify(data)}`
                })
                return
            }
            // 办理结果录入
            if (type == 2) {
                uni.navigateTo({
                    url: `/pages/result-entry/index?data=${JSON.stringify(data)}`
                })
                return
            }
            // 添加
            if (type == 3) {
                save(data).then(resp => {
                    if (resp.code == 200) {
                        uni.showToast({
                            title: '保存成功',
                            icon: 'none'
                        })
                        setTimeout(() => {
                            uni.navigateBack()
                        }, 1500)
                    }
                })
            }
        },
        confirmone(e) {
            this.time = dayjs(e.value).format('YYYY-MM-DD')
            this.showDate = false
        },
        confirmtwo(e) {
            this.problemType = e.value[0]
            this.showList = false
        },
        cancel() {
            this.showDate = false
            this.showList = false
        },
        close(e) {
            this.showDate = false
            this.showList = false
        },
        goTopagelocation() {
            uni.navigateTo({
                url: '/pages/location/location'
            })
        },
        uploadImg() {
            uni.chooseImage({
                count: 1, //默认9
                sizeType: ['original', 'compressed'], //可以指定是原图还是压缩图,默认二者都有
                sourceType: ['album'], //从相册选择
                success: (res) => {
                    uni.showLoading()
                    console.log('res.tempFilePaths[0]', res.tempFilePaths[0])
                    uni.uploadFile({
                        url: config.imageUrl,
                        filePath: res.tempFilePaths[0],
                        name: 'file',
                        header: {
                            'Authorization': uni.getStorageSync('token')
                        },
                        success: (uploadFileRes) => {
                            this.images = [...this.images, JSON.parse(uploadFileRes.data).data]
                            uni.hideLoading()
                        },
                        fail: () => {
                            uni.hideLoading()
            },
            uploadVideo() {
                uni.chooseVideo({
                    count: 1, //默认9
                    sourceType: ['camera', 'album'],
                    success: (res) => {
                        const videoExtensions = /\.(mp4|avi|rmvb)$/i;
                        if (!videoExtensions.test(res.name)) {
                            uni.showToast({
                                title: '上传失败',
                                icon: 'error'
                                title: '请上传mp4, avi, rmvb格式的视频',
                                icon: 'none',
                                duration: 3000
                            })
                            return
                        }
                    })
                }
            });
        },
        uploadVideo() {
            uni.chooseVideo({
                count: 1, //默认9
                sourceType: ['camera', 'album'],
                success: (res) => {
                    const videoExtensions = /\.(mp4|avi|rmvb)$/i;
                    if (!videoExtensions.test(res.name)) {
                        uni.showToast({
                            title: '请上传mp4, avi, rmvb格式的视频',
                            icon: 'none',
                            duration: 3000
                        })
                        return
                    }
                    uni.showLoading()
                    uni.uploadFile({
                        url: config.imageUrl,
                        filePath: res.tempFilePath,
                        name: 'file',
                        header: {
                            'Authorization': uni.getStorageSync('token')
                        },
                        success: (uploadFileRes) => {
                        uni.showLoading()
                        uni.uploadFile({
                            url: config.imageUrl,
                            filePath: res.tempFilePath,
                            name: 'file',
                            header: {
                                'Authorization': uni.getStorageSync('token')
                            },
                            success: (uploadFileRes) => {
                            this.videos = [...this.videos, JSON.parse(uploadFileRes.data).data]
                            uni.hideLoading()
                        },
                        fail: () => {
                            uni.hideLoading()
                            uni.showToast({
                                title: '上传失败',
                                icon: 'error'
                            })
                        }
                    })
                }
            });
        },
                                this.videos = [...this.videos, JSON.parse(uploadFileRes.data).data]
                                uni.hideLoading()
                            },
                            fail: () => {
                                uni.hideLoading()
                                uni.showToast({
                                    title: '上传失败',
                                    icon: 'error'
                                })
                            }
                        })
                    }
                });
            },
        }
    }
}
</script>
<style lang="scss" scoped>
.min-108 {
    min-height: 108rpx;
}
.red {
    color: rgba(255, 73, 72, 1);
}
.pink {
    background: #FFF1F4;
    border-radius: 8rpx;
    margin-top: 19rpx;
}
.card {
    border-bottom: 2rpx solid rgba(0, 10, 26, 0.07);
}
.bg {
    background: #F8F8F8;
}
.content {
    padding: 38rpx 31rpx 162rpx 31rpx;
    .main {
        padding: 0 27rpx 48rpx 27rpx;
        background: #FFFFFF;
        box-shadow: 0rpx 0rpx 27rpx 0rpx rgba(0, 0, 0, 0.1);
        border-radius: 19rpx;
    .min-108 {
        min-height: 108rpx;
    }
}
textarea {
    padding: 27rpx 31rpx 27rpx 31rpx;
    font-weight: 500;
}
.img {
    position: relative;
    .img-icon {
        height: 140rpx;
        line-height: 140rpx;
        position: absolute;
        top: 0rpx;
        left: 35rpx;
    .red {
        color: rgba(255, 73, 72, 1);
    }
}
/deep/.u-popup__content {
    border-radius: 16rpx 16rpx 0rpx 0rpx;
}
    .pink {
        background: #FFF1F4;
        border-radius: 8rpx;
        margin-top: 19rpx;
    }
/deep/ .u-toolbar {
    border-bottom: 2rpx solid RGBA(243, 243, 243, 1);
}
    .card {
        border-bottom: 2rpx solid rgba(0, 10, 26, 0.07);
    }
/deep/ .u-toolbar__wrapper__cancel {
    font-weight: 400;
    font-size: 30rpx;
    color: #FF4948 !important;
}
    .bg {
        background: #F8F8F8;
    }
/deep/ .u-toolbar__wrapper__confirm {
    font-weight: 400;
    font-size: 30rpx;
    color: #FF4948 !important;
}
    .content {
        padding: 38rpx 31rpx 162rpx 31rpx;
.footer {
    position: fixed;
    bottom: 0;
    left: 0;
    box-shadow: 0rpx -4rpx 27rpx 0rpx rgba(0, 0, 0, 0.08);
    width: calc(100% - 20rpx);
    background-color: #fff;
    display: flex;
    gap: 27rpx;
    padding: 33rpx 10rpx;
        .main {
            padding: 0 27rpx 48rpx 27rpx;
            background: #FFFFFF;
            box-shadow: 0rpx 0rpx 27rpx 0rpx rgba(0, 0, 0, 0.1);
            border-radius: 19rpx;
        }
    }
    .cancel {
        width: 331rpx;
        height: 77rpx;
        border: 2rpx solid rgba(252, 141, 85, 1);
        border-radius: 38rpx;
    textarea {
        padding: 27rpx 31rpx 27rpx 31rpx;
        font-weight: 500;
    }
    .img {
        position: relative;
        .img-icon {
            height: 140rpx;
            line-height: 140rpx;
            position: absolute;
            top: 0rpx;
            left: 35rpx;
        }
    }
    /deep/.u-popup__content {
        border-radius: 16rpx 16rpx 0rpx 0rpx;
    }
    /deep/ .u-toolbar {
        border-bottom: 2rpx solid RGBA(243, 243, 243, 1);
    }
    /deep/ .u-toolbar__wrapper__cancel {
        font-weight: 400;
        font-size: 30rpx;
        color: #FF4948 !important;
    }
    /deep/ .u-toolbar__wrapper__confirm {
        font-weight: 400;
        font-size: 30rpx;
        color: #FF4948 !important;
    }
    .footer {
        position: fixed;
        bottom: 0;
        left: 0;
        box-shadow: 0rpx -4rpx 27rpx 0rpx rgba(0, 0, 0, 0.08);
        width: calc(100% - 20rpx);
        background-color: #fff;
        display: flex;
        align-items: center;
        justify-content: center;
        color: #FF4948;
    }
        gap: 27rpx;
        padding: 33rpx 10rpx;
    .ok {
        width: 331rpx;
        height: 77rpx;
        background: linear-gradient(270deg, #FC8D55 0%, #FF4948 100%);
        border-radius: 48rpx;
        display: flex;
        align-items: center;
        justify-content: center;
        color: #FFFFFF;
        .cancel {
            width: 331rpx;
            height: 77rpx;
            border: 2rpx solid rgba(252, 141, 85, 1);
            border-radius: 38rpx;
            display: flex;
            align-items: center;
            justify-content: center;
            color: #FF4948;
        }
        .ok {
            width: 331rpx;
            height: 77rpx;
            background: linear-gradient(270deg, #FC8D55 0%, #FF4948 100%);
            border-radius: 48rpx;
            display: flex;
            align-items: center;
            justify-content: center;
            color: #FFFFFF;
        }
    }
}
</style>
H5/pages/add-progress/index.vue
@@ -20,10 +20,15 @@
                    <view class="fs-27 lh-38 font-bold">群众姓名</view>
                    <view class="fs-27 lh-38 mt-10">{{ info.name }}</view>
                </view>
                <view class="flex-column a-center">
                    <view class="fs-27 lh-38 font-bold">联系电话</view>
                    <view class="fs-27 lh-38 mt-10">{{ info.contactNumber }}</view>
                <view class="flex a-center">
                    <view class="flex-column a-center">
                        <view class="fs-27 lh-38 font-bold">联系电话</view>
                        <view class="fs-27 lh-38 mt-10">{{ info.contactNumber }}</view>
                    </view>
                    <image @click.stop="callPhone(info.contactNumber)" src="../../static/tell.png"
                        class="w-58 h-58 shrink0 " />
                </view>
            </view>
        </view>
        <view class="br-19 mt-38 bs-1 pt-35 pb-33 px-31">
@@ -108,6 +113,11 @@
            })
        },
        methods: {
            callPhone(phoneNumber) {
                uni.makePhoneCall({
                    phoneNumber
                });
            },
            back() {
                uni.navigateBack({
                    delta: 2
@@ -315,7 +325,7 @@
    }
    .bgcolor1 {
        background: linear-gradient( 270deg, rgba(255,241,0,0.5) 0%, rgba(255,249,172,0.25) 48%, rgba(255,255,255,0.2) 100%, #FFFFFF 100%);
        background: linear-gradient(270deg, rgba(255, 241, 0, 0.5) 0%, rgba(255, 249, 172, 0.25) 48%, rgba(255, 255, 255, 0.2) 100%, #FFFFFF 100%);
    }
    .bgcolor2 {
H5/pages/index/index.vue
@@ -71,7 +71,6 @@
                    <image class="w-79 h-77 ml-38" src="/static/home/img1.png" mode=""></image>
                    <text class="ml-37 font-bold">回访评价</text>
                </view> -->
                <view class="pt-38 pb-38 bg1 w-333 br-19 mt-19 flex a-center" @click="toStatistics">
                    <image class="w-81 h-77 ml-38" src="/static/home/img8.png" mode=""></image>
                    <text class="ml-37 font-bold">统计分析</text>
@@ -145,7 +144,7 @@
                <view class="lineBox mb-10"></view>
                <view style="height: 546rpx;overflow-y: auto;">
                    <view @tap.stop="changeRole(item)" v-for="(item,index) in userInfo.permissions" :key="index"
                        :class="item.identity == userInfo.identity && 'bgcolor2'"
                        :class="item.name == userInfo.roleName && 'bgcolor2'"
                        class="mt-38 bgcolor1 br-58 fs-31 ml-54 mr-62 py-37 txt-center">
                        <view>{{ item.name }}</view>
                    </view>
@@ -335,8 +334,10 @@
                    this.changeRolePopup = false
                    this.$refs.dongjiePop.showPopup()
                } else {
                    console.log(e);
                    changeIdentity({
                        identity: e.identity
                        identity: e.identity,
                        levelId: e.identity == 1 ? '' : e.levelId
                    }).then(res => {
                        this.$refs.uToast.show({
                            type: "success",
@@ -415,7 +416,7 @@
                resp.data.isAdmin = index >= 0 ? 1 : 0
                if (resp.data.permissions.length > 0) {
                    let obj = resp.data.permissions.find(item => item.identity == resp.data.identity)
                    let obj = resp.data.permissions.find(item => item.levelId == resp.data.levelId)
                    resp.data.roleName = obj.name
                }
H5/pages/statistics/index.vue
@@ -1,179 +1,166 @@
 <template>
     <view class="content">
         <view v-if="userInfo.isAdmin == 1" class="flex a-center pl-31 pr-31 fs-31 color1 pt-38">
             <text class="mr-15">查看范围:</text>
             <view
                 class="h-77 flex a-center j-between flex1 pl-31 pr-23 border1 br-15 bgColor1" :class="!address && 'color2'">
                <view @click.top="selectPopup=true" class="flex1">
                    {{ address || '全部' }}
                </view>
                <u-icon class="shrink0" v-if="address" @click="clearAddress" name="close-circle"></u-icon>
                 <image v-else src="/static/down@2x.png" mode="aspectFill" class="w-31 h-31 shrink0"></image>
             </view>
         </view>
         <view class="fs-35 font-bold pt-38 ml-27">
             处理满意率
         </view>
         <view class="ml-29 mr-29 border2 br-15 mt-27 shadow1 flex j-between pl-19 pr-19 pb-35">
             <view class="mt-19 flex1">
                 <view class="flex a-center">
                     <view class="w-12 h-12 br-6 border3"></view>
                     <view class="fs-23 ml-15 color3">
                         总体满意率
                     </view>
                 </view>
                 <view class="fs-46 ml-27 mt-12 font-bold">
                     77%
                 </view>
             </view>
             <view class="flex1 flex j-between ml-50">
                 <view class="fs-23 mt-42 txt-center">
                     <view class="color6">
                         本月
                     </view>
                     <view class="fs-27 font-bold color5 mt-2">
                         88%
                     </view>
                 </view>
                 <view class="fs-23 mt-44">
                     <view class="color6">
                         同比上月
                     </view>
                     <view class="txt-aligin-r color4 font-bold mt-4">
                         +12%
                     </view>
                 </view>
             </view>
         </view>
         <view class="fs-35 font-bold mt-38 ml-27">
             诉求单统计
         </view>
         <view class="flex j-between a-center pl-29 pr-29 mt-27">
             <view class="pl-19 pr-21 shadow1 border2 pt-19 pb-42 flex1 br-15">
                 <view class="flex a-center">
                     <view class="w-12 h-12 br-6 border3"></view>
                     <view class="fs-23 ml-15 color3">
                         诉求单量总计
                     </view>
                 </view>
                 <view class="fs-46 mt-12 font-bold ml-27">
                     448451
                 </view>
                 <view class="flex j-between mt-21">
                     <view class="fs-23">
                         <view class="">
                             本月
                         </view>
                         <view class="fs-27 font-bold color3 mt-2">
                             4448
                         </view>
                     </view>
                     <view class="fs-23">
                         <view class="">
                             同比上月
                         </view>
                         <view class="font-bold color8 txt-aligin-r mt-4">
                             -12%
                         </view>
                     </view>
                 </view>
             </view>
             <view class="pl-19 pr-21 shadow1 border2 pt-19 pb-42 flex1 ml-31 br-15">
                 <view class="flex a-center">
                     <view class="w-12 h-12 br-6 border3"></view>
                     <view class="fs-23 ml-15 color3">
                         平均处理时间(天)
                     </view>
                 </view>
                 <view class="fs-46 mt-12 font-bold ml-27">
                     3.2
                 </view>
                 <view class="flex j-between mt-21">
                     <view class="fs-23">
                         <view class="">
                             本月
                         </view>
                         <view class="fs-27 font-bold color3 mt-2">
                             2.2
                         </view>
                     </view>
                     <view class="fs-23">
                         <view class="">
                             同比上月
                         </view>
                         <view class="font-bold color4 txt-aligin-r mt-4">
                             +12%
                         </view>
                     </view>
                 </view>
             </view>
         </view>
         <view class="flex mt-27 gap25 pl-29 pr-29">
             <view class="flex1 h-154 bgColor2 border4 shadow2 br-19 txt-center">
                 <view class="mt-37 fs-35 color4 font-bold">
                     4521
                 </view>
                 <view class="fs-23 mt-8 color3">
                     正在办理
                 </view>
             </view>
             <view class="flex1 h-154 bgColor3 border4 shadow2 br-19 txt-center">
                 <view class="mt-37 fs-35 color9 font-bold">
                     321
                 </view>
                 <view class="fs-23 mt-8 color3">
                     审核中
                 </view>
             </view>
             <view class="flex1 h-154 bgColor4 border4 shadow2 br-19 txt-center">
                 <view class="mt-37 fs-35 color10 font-bold">
                     6850
                 </view>
                 <view class="fs-23 mt-8 color3">
                     延期办理
                 </view>
             </view>
             <view class="flex1 h-154 bgColor5 border4 shadow2 br-19 txt-center">
                 <view class="mt-37 fs-35 color11 font-bold">
                     8451
                 </view>
                 <view class="fs-23 mt-8 color3">
                     已办结
                 </view>
             </view>
         </view>
         <view class="pb-35 ml-29 mr-29 border2 br-15 mt-27 shadow1 flex j-between pl-19 pr-19">
             <view class="mt-19 flex1">
                 <view class="flex a-center">
                     <view class="w-12 h-12 br-6 border3"></view>
                     <view class="fs-23 ml-15 color3">
                         超时办理
                     </view>
                 </view>
                 <view class="fs-46 ml-27 mt-12 font-bold">
                     6850
                 </view>
             </view>
             <view class="flex1 flex j-between ml-50">
                 <view class="fs-23 mt-42 txt-center">
                     <view class="color6">
                         本月
                     </view>
                     <view class="fs-27 font-bold color5 mt-2">
                         4448
                     </view>
                 </view>
                 <view class="fs-23 mt-44">
                     <view class="color6">
                         同比上月
                     </view>
                     <view class="txt-aligin-r color4 font-bold mt-4">
                         +12%
                     </view>
                 </view>
             </view>
         </view>
         <view class="pb-35 ml-29 mr-29 border2 br-15 mt-27 shadow1 flex j-between pl-19 pr-19">
<template>
  <view class="content">
    <view
      v-if="userInfo.isAdmin == 1"
      class="flex a-center pl-31 pr-31 fs-31 color1 pt-38"
    >
      <text class="mr-15">查看范围:</text>
      <view
        class="h-77 flex a-center j-between flex1 pl-31 pr-23 border1 br-15 bgColor1"
        :class="!address && 'color2'"
      >
        <view @click.top="selectPopup = true" class="flex1">
          {{ address || "全部" }}
        </view>
        <u-icon
          class="shrink0"
          v-if="address"
          @click="clearAddress"
          name="close-circle"
        ></u-icon>
        <image
          v-else
          src="/static/down@2x.png"
          mode="aspectFill"
          class="w-31 h-31 shrink0"
        ></image>
      </view>
    </view>
    <view class="fs-35 font-bold pt-38 ml-27"> 处理满意率 </view>
    <view
      class="ml-29 mr-29 border2 br-15 mt-27 shadow1 flex j-between pl-19 pr-19 pb-35"
    >
      <view class="mt-19 flex1">
        <view class="flex a-center">
          <view class="w-12 h-12 br-6 border3"></view>
          <view class="fs-23 ml-15 color3"> 总体满意率 </view>
        </view>
        <view class="fs-46 ml-27 mt-12 font-bold">
          {{ statisticsData.satisfaction.total }}%
        </view>
      </view>
      <view class="flex1 flex j-between ml-50">
        <view class="fs-23 mt-42 txt-center">
          <view class="color6"> 本月 </view>
          <view class="fs-27 font-bold color5 mt-2">
            {{ statisticsData.satisfaction.month }}%
          </view>
        </view>
        <view class="fs-23 mt-44">
          <view class="color6"> 同比上月 </view>
          <view class="txt-aligin-r color4 font-bold mt-4">
            {{ statisticsData.satisfaction.compare > 0 ? "+" : ""
            }}{{ statisticsData.satisfaction.compare }}%
          </view>
        </view>
      </view>
    </view>
    <view class="fs-35 font-bold mt-38 ml-27"> 诉求单统计 </view>
    <view class="flex j-between a-center pl-29 pr-29 mt-27">
      <view class="pl-19 pr-21 shadow1 border2 pt-19 pb-42 flex1 br-15">
        <view class="flex a-center">
          <view class="w-12 h-12 br-6 border3"></view>
          <view class="fs-23 ml-15 color3"> 诉求单量总计 </view>
        </view>
        <view class="fs-46 mt-12 font-bold ml-27">
          {{ statisticsData.demands.total }}
        </view>
        <view class="flex j-between mt-21">
          <view class="fs-23">
            <view class=""> 本月 </view>
            <view class="fs-27 font-bold color3 mt-2">
              {{ statisticsData.demands.month }}
            </view>
          </view>
          <view class="fs-23">
            <view class=""> 同比上月 </view>
            <view class="font-bold color8 txt-aligin-r mt-4">
              {{ statisticsData.demands.compare > 0 ? "+" : ""
              }}{{ statisticsData.demands.compare }}
            </view>
          </view>
        </view>
      </view>
      <view class="pl-19 pr-21 shadow1 border2 pt-19 pb-42 flex1 ml-31 br-15">
        <view class="flex a-center">
          <view class="w-12 h-12 br-6 border3"></view>
          <view class="fs-23 ml-15 color3"> 平均处理时间(天) </view>
        </view>
        <view class="fs-46 mt-12 font-bold ml-27">
          {{ statisticsData.processTime.total }}
        </view>
        <view class="flex j-between mt-21">
          <view class="fs-23">
            <view class=""> 本月 </view>
            <view class="fs-27 font-bold color3 mt-2">
              {{ statisticsData.processTime.month }}
            </view>
          </view>
          <view class="fs-23">
            <view class=""> 同比上月 </view>
            <view class="font-bold color4 txt-aligin-r mt-4">
              {{ statisticsData.processTime.compare > 0 ? "+" : ""
              }}{{ statisticsData.processTime.compare }}
            </view>
          </view>
        </view>
      </view>
    </view>
    <view class="flex mt-27 gap25 pl-29 pr-29">
      <view class="flex1 h-154 bgColor2 border4 shadow2 br-19 txt-center">
        <view class="mt-37 fs-35 color4 font-bold">
          {{ statisticsData.status.processing }}
        </view>
        <view class="fs-23 mt-8 color3"> 正在办理 </view>
      </view>
      <view class="flex1 h-154 bgColor3 border4 shadow2 br-19 txt-center">
        <view class="mt-37 fs-35 color9 font-bold">
          {{ statisticsData.status.reviewing }}
        </view>
        <view class="fs-23 mt-8 color3"> 审核中 </view>
      </view>
      <view class="flex1 h-154 bgColor4 border4 shadow2 br-19 txt-center">
        <view class="mt-37 fs-35 color10 font-bold">
          {{ statisticsData.status.delayed }}
        </view>
        <view class="fs-23 mt-8 color3"> 延期办理 </view>
      </view>
      <view class="flex1 h-154 bgColor5 border4 shadow2 br-19 txt-center">
        <view class="mt-37 fs-35 color11 font-bold">
          {{ statisticsData.status.completed }}
        </view>
        <view class="fs-23 mt-8 color3"> 已办结 </view>
      </view>
    </view>
    <view
      class="pb-35 ml-29 mr-29 border2 br-15 mt-27 shadow1 flex j-between pl-19 pr-19"
    >
      <view class="mt-19 flex1">
        <view class="flex a-center">
          <view class="w-12 h-12 br-6 border3"></view>
          <view class="fs-23 ml-15 color3"> 超时办理 </view>
        </view>
        <view class="fs-46 ml-27 mt-12 font-bold">
          {{ statisticsData.overtime.total }}
        </view>
      </view>
      <view class="flex1 flex j-between ml-50">
        <view class="fs-23 mt-42 txt-center">
          <view class="color6"> 本月 </view>
          <view class="fs-27 font-bold color5 mt-2">
            {{ statisticsData.overtime.month }}
          </view>
        </view>
        <view class="fs-23 mt-44">
          <view class="color6"> 同比上月 </view>
          <view class="txt-aligin-r color4 font-bold mt-4">
            {{ statisticsData.overtime.compare > 0 ? "+" : ""
            }}{{ statisticsData.overtime.compare }}
          </view>
        </view>
      </view>
    </view>
    <!-- <view class="pb-35 ml-29 mr-29 border2 br-15 mt-27 shadow1 flex j-between pl-19 pr-19">
             <view class="mt-19 flex1">
                 <view class="flex a-center">
                     <view class="w-12 h-12 br-6 border3"></view>
@@ -203,685 +190,1004 @@
                     </view>
                 </view>
             </view>
         </view>
         <view class="mt-27 shadow1 border2 ml-29 mr-29 pt-31 br-15" style="height: 511rpx;">
             <view class="flex mlr-o tabs mb-40">
                 <view v-for="(item, index) in tabs" :key="index"
                     :class="['tab-item', currentTab === index ? 'active' : '']" @click="handleTabClick(index)">
                     {{item}}
                 </view>
             </view>
             <view ref="chartRef" id="chart" style="width: 100%; height: 405rpx;"></view>
         </view>
         <view class="fs-35 font-bold mt-38 ml-27">
             问题类型排名
         </view>
         <view class="ml-29 mr-29 mt-27 shadow1 pt-31 border2 pl-38 pr-38 br-15">
             <uni-data-select v-model="value1" :localdata="range"></uni-data-select>
             <view class="fs-23">
                 <view class="flex a-center mb-38">
                     <view class="w-130 color3">
                         教育
                     </view>
                     <u-line-progress :percentage="95" inactiveColor="#EEEEEE"
                         :activeColor="'linear-gradient(270deg, #FF4934 0%, #FF8064 100%)'" height="38rpx">
                         <text class="u-percentage-slot pr-19 fs-23">248</text>
                     </u-line-progress>
                 </view>
                 <view class="flex a-center mb-38">
                     <view class="w-130 color3">
                         就业
                     </view>
                     <u-line-progress :percentage="75" inactiveColor="#EEEEEE"
                         :activeColor="'linear-gradient(270deg, #FEA834 0%, #FFD364 100%)'" height="38rpx">
                         <text class="u-percentage-slot pr-19 fs-23">200</text>
                     </u-line-progress>
                 </view>
                 <view class="flex a-center mb-38">
                     <view class="w-130 color3">
                         医疗
                     </view>
                     <u-line-progress :percentage="55" inactiveColor="#EEEEEE"
                         :activeColor="'linear-gradient(270deg, #02BAC0 0%, #05DEE1 100%)'" height="38rpx">
                         <text class="u-percentage-slot pr-19 fs-23">174</text>
                     </u-line-progress>
                 </view>
                 <view class="flex a-center mb-38">
                     <view class="w-130 color3">
                         住房
                     </view>
                     <u-line-progress :percentage="35" inactiveColor="#EEEEEE"
                         :activeColor="'linear-gradient(270deg, #4791FF 0%, #7DC4FF 100%)'" height="38rpx">
                         <text class="u-percentage-slot pr-19 fs-23">132</text>
                     </u-line-progress>
                 </view>
                 <view class="flex a-center mb-38">
                     <view class="w-130 color3">
                         养老
                     </view>
                     <u-line-progress :percentage="15" inactiveColor="#EEEEEE"
                         :activeColor="'linear-gradient(270deg, #4791FF 0%, #7DC4FF 100%)'" height="38rpx">
                         <text class="u-percentage-slot pr-19 fs-23">89</text>
                     </u-line-progress>
                 </view>
             </view>
         </view>
         <view class="fs-35 font-bold mt-38 ml-27">
             评价占比
         </view>
         <view class="ml-29 mr-29 mt-27 shadow1 border2 br-15" style="height: 417rpx;">
             <view class="flex pl-38 pr-38">
                 <view ref="rateChartRef" id="rateChart" style="width: 288rpx; height: 417rpx;"></view>
                 <view style="width: 46rpx;"></view>
                 <view class="flex1 pt-54">
                     <view class="mb-27">
                         <view class="flex a-center j-between mb-13">
                             <text class="fs-23">非常满意</text>
                             <text class="fs-23 color12">45%</text>
                         </view>
                         <view class="progress-bar">
                             <view class="progress-inner very-satisfied" style="width: 45%;"></view>
                         </view>
                     </view>
                     <view class="mb-27">
                         <view class="flex a-center j-between mb-13">
                             <text class="fs-23">满意</text>
                             <text class="fs-23 color12">20%</text>
                         </view>
                         <view class="progress-bar">
                             <view class="progress-inner satisfied" style="width: 20%;"></view>
                         </view>
                     </view>
                     <view class="mb-27">
                         <view class="flex a-center j-between mb-13">
                             <text class="fs-23">一般</text>
                             <text class="fs-23 color12">30%</text>
                         </view>
                         <view class="progress-bar">
                             <view class="progress-inner normal" style="width: 30%;"></view>
                         </view>
                     </view>
                     <view class="mb-27">
                         <view class="flex a-center j-between mb-13">
                             <text class="fs-23">不满意</text>
                             <text class="fs-23 color12">5%</text>
                         </view>
                         <view class="progress-bar">
                             <view class="progress-inner unsatisfied" style="width: 5%;"></view>
                         </view>
                     </view>
                 </view>
             </view>
         </view>
         <view class="h-40 safe-b"></view>
         <!-- 选择服务社区 -->
         <u-popup :show="selectPopup" round="16rpx" @close="selectPopup = false" :safe-area-inset-bottom="false"
             @open="openSelectPopup">
             <view class="relative pb-40">
                 <image @tap.stop="selectPopup = false" src="@/static/closeImg.png" class="w-35 h-35 absolute"
                     style="right: 31rpx;top: 46rpx;" />
                 <view class="txt-center pt-38 pb-40 fs-35 lh-48 font-bold">请选择服务社区</view>
                 <view class="flex a-center j-between txt-center py-10 fs-27 font-bold bgColor1">
                     <view class="flex1">区县</view>
                     <view class="flex1">街道</view>
                     <view class="flex1">社区</view>
                 </view>
                 <view class="mb-20">
                     <picker-view :value="value" @change="bindChange" class="picker-view" immediate-change>
                         <picker-view-column>
                             <view class="item" v-for="(item, index) in county" :key="index">
                                 {{ item.name }}
                             </view>
                         </picker-view-column>
                         <picker-view-column>
                             <view class="item" v-for="(item, index) in street" :key="index">
                                 {{ item.name }}
                             </view>
                         </picker-view-column>
                         <picker-view-column>
                             <view class="item" v-for="(item, index) in community" :key="index">
                                 {{ item.name }}
                             </view>
                         </picker-view-column>
                     </picker-view>
                 </view>
                 <view class="submitBtn" @click="chooseCommunity">确认</view>
             </view>
         </u-popup>
     </view>
 </template>
 <script>
     import * as echarts from 'echarts';
     import {
         getRegionTree
     } from './service.js'
     export default {
         data() {
             return {
                userInfo: {},
                 address: '',
                 value: [0, 0, 0],
                 confirmValue: [0, 0, 0],
                 county: [], //区县
                 street: [], //街道
                 community: [], //社区
                 value1: 0,
                 selectPopup: false,
                 range: [{
                         value: 0,
                         text: "排名前五"
                     },
                     {
                         value: 1,
                         text: "排名前十"
                     },
                     {
                         value: 2,
                         text: "所有排名"
                     },
                 ],
                 chart: null,
                 rateChart: null,
                 tabs: ['近7天', '近15天', '近30天'],
                 currentTab: 0,
                 chartData: {
                     dates: [
                         '2025\n04.17',
                         '2025\n04.18',
                         '2025\n04.19',
                         '2025\n04.20',
                         '2025\n04.21',
                         '2025\n04.22',
                         '2025\n04.23'
                     ],
                     demands: [80, 170, 240, 70, 130, 90, 160],
                     completed: [40, 130, 200, 30, 90, 50, 110]
                 },
                 rateData: [{
                         value: 45,
                         name: '非常满意',
                         itemStyle: {
                             color: new echarts.graphic.LinearGradient(1, 0, 0, 0, [{
                                     offset: 0,
                                     color: '#FF8064'
                                 },
                                 {
                                     offset: 1,
                                     color: '#FF4934'
                                 }
                             ])
                         }
                     },
                     {
                         value: 20,
                         name: '满意',
                         itemStyle: {
                             color: new echarts.graphic.LinearGradient(1, 0, 0, 0, [{
                                     offset: 0,
                                     color: '#05DEE1'
                                 },
                                 {
                                     offset: 1,
                                     color: '#02BAC0'
                                 }
                             ])
                         }
                     },
                     {
                         value: 30,
                         name: '一般',
                         itemStyle: {
                             color: new echarts.graphic.LinearGradient(1, 0, 0, 0, [{
                                     offset: 0,
                                     color: '#7DC4FF'
                                 },
                                 {
                                     offset: 1,
                                     color: '#4791FF'
                                 }
                             ])
                         }
                     },
                     {
                         value: 5,
                         name: '不满意',
                         itemStyle: {
                             color: new echarts.graphic.LinearGradient(1, 0, 0, 0, [{
                                     offset: 0,
                                     color: '#FFD364'
                                 },
                                 {
                                     offset: 1,
                                     color: '#FEA834'
                                 }
                             ])
                         }
                     }
                 ]
             }
         },
         onLoad() {
            this.userInfo = uni.getStorageSync('userInfo')
             getRegionTree().then(res => {
                 this.county = res.data
             })
         },
         mounted() {
             this.$nextTick(() => {
                 this.initChart()
                 this.initRateChart()
             })
         },
         methods: {
            clearAddress() {
                this.address = '';
                this.value = [0, 0, 0];
                this.confirmValue = [0, 0, 0];
                this.street = [];
                this.community = [];
            },
             //选择服务社区
             chooseCommunity() {
                 let districts = this.county[this.value[0]].name
                 let street = this.street[this.value[1]].name
                 let community = this.community[this.value[2]].name
                 this.address = `${districts}-${street}-${community}`;
                 this.confirmValue = this.value
                 this.selectPopup = false
             },
             // 切换社区
             bindChange(e, index) {
                 if (e.detail.value[0] != this.value[0]) {
                     e.detail.value[1] = 0
                     e.detail.value[2] = 0
                 }
                 if (e.detail.value[1] != this.value[1]) {
                     e.detail.value[2] = 0
                 }
                 this.value = e.detail.value
                 this.street = this.county[this.value[0]].children
                 this.community = this.street[this.value[1]].children
             },
             openSelectPopup() {
                 this.value = this.confirmValue
                 this.street = this.county[this.value[0]].children
                 this.community = this.street[this.value[1]].children
             },
             handleTabClick(index) {
                 this.currentTab = index
                 // 这里可以根据不同的 tab 加载不同时间段的数据
                 // this.loadChartData(index)
             },
             initChart() {
                 // 在 H5 端使用 document.getElementById
                 // 在小程序端使用 this.$refs.chartRef
                 const chartDom = document.getElementById('chart') || this.$refs.chartRef;
                 this.chart = echarts.init(chartDom);
                 this.updateChart();
             },
             updateChart() {
                 const option = {
                     color: ['#FF7B7B', '#FFB75B'],
                     tooltip: {
                         trigger: 'axis',
                         axisPointer: {
                             type: 'shadow'
                         }
                     },
                     legend: {
                         data: ['诉求单量', '诉求办结数'],
                         bottom: '0',
                         itemGap: uni.upx2px(60),
                         selectedMode: true
                     },
                     grid: {
                         left: '3%',
                         right: '4%',
                         bottom: '15%',
                         top: '3%',
                         containLabel: true
                     },
                     xAxis: {
                         type: 'category',
                         data: this.chartData.dates,
                         axisLine: {
                             lineStyle: {
                                 color: '#E5E5E5'
                             }
                         },
                         axisTick: {
                             show: false
                         },
                         axisLabel: {
                             color: '#888888',
                             fontSize: uni.upx2px(19),
                             lineHeight: uni.upx2px(23),
                             formatter: function(value) {
                                 return value.split('\\n').join('\n')
                             }
                         }
                     },
                     yAxis: {
                         type: 'value',
                         splitLine: {
                             lineStyle: {
                                 type: 'dashed',
                                 color: '#fff'
                             }
                         },
                         axisLine: {
                             show: false
                         },
                         axisTick: {
                             show: false
                         }
                     },
                     series: [{
                             name: '诉求单量',
                             type: 'bar',
                             barWidth: uni.upx2px(38),
                             itemStyle: {
                                 borderRadius: [20, 20, 20, 20],
                                 color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
                                         offset: 0,
                                         color: '#FF807E'
                                     },
                                     {
                                         offset: 1,
                                         color: '#FF4948'
                                     }
                                 ])
                             },
                             data: this.chartData.demands
                         },
                         {
                             name: '诉求办结数',
                             type: 'line',
                             smooth: true,
                             symbol: 'circle',
                             symbolSize: 8,
                             itemStyle: {
                                 color: '#FFB75B',
                                 borderWidth: 2,
                                 borderColor: '#fff',
                                 shadowColor: '#FB9A0E',
                                 shadowBlur: 8,
                                 shadowOffsetY: 4
                             },
                             lineStyle: {
                                 width: 2,
                                 curveness: 0.3
                             },
                             data: this.chartData.completed
                         }
                     ]
                 };
                 this.chart && this.chart.setOption(option);
             },
             initRateChart() {
                 const chartDom = document.getElementById('rateChart') || this.$refs.rateChartRef;
                 this.rateChart = echarts.init(chartDom);
                 this.updateRateChart();
             },
             updateRateChart() {
                 const option = {
                     tooltip: {
                         trigger: 'item',
                         confine: true,
                         // formatter: '{b}: {c}%',
                         backgroundColor: 'rgba(255, 255, 255, 0.9)',
                         borderColor: '#FFE0E0',
                         borderWidth: 1,
                         textStyle: {
                             color: '#666666',
                             fontSize: 12
                         },
                         padding: [8, 12]
                     },
                     series: [{
                         name: '评价占比',
                         type: 'pie',
                         radius: ['55%', '100%'],
                         center: ['50%', '50%'],
                         avoidLabelOverlap: false,
                         label: {
                             show: false
                         },
                         labelLine: {
                             show: false
                         },
                         emphasis: {
                             scale: false,
                             scaleSize: 0
                         },
                         data: this.rateData
                     }]
                 };
                 this.rateChart && this.rateChart.setOption(option);
             }
         }
     }
 </script>
 <style scoped lang="scss">
     /deep/.uni-select {
         width: 231rpx;
         height: 65rpx;
         margin: 0 auto;
         margin-bottom: 38rpx;
         font-size: 27rpx;
         color: #797F81;
         border-color: #E5E5E5;
         border-radius: 33rpx;
         padding: 0 31rpx 0 40rpx;
         .uni-select__input-text {
             color: #797F81;
         }
     }
     .content {
         background: linear-gradient(180deg, #FFDCDB 0%, rgba(255, 255, 255, 0) 100rpx, #fff 100%);
     }
     .gap25 {
         gap: 25rpx;
     }
     .tabs {
         width: 412rpx;
         background-color: #FFF1F1;
         border-radius: 30rpx;
         height: 65rpx;
         line-height: 65rpx;
         font-size: 27rpx;
         position: relative;
         overflow: hidden;
     }
     .tab-item {
         color: #797F81;
         flex: 1;
         text-align: center;
         font-weight: 400;
         height: 54rpx;
         line-height: 54rpx;
         border-radius: 27rpx;
         margin: 6rpx;
         position: relative;
         z-index: 1;
         transform: translateZ(0);
         transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
         &.active {
             background-color: #fff;
             color: #FF4948;
             font-weight: 600;
             box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
             transform: scale(1.02);
         }
     }
     .chart-wrapper {
         background: #fff;
         border-radius: 20rpx;
         padding: 20rpx;
     }
     .color1 {
         color: #666565;
     }
     .color2 {
         color: #C1C1C1;
     }
     .color3 {
         color: #666666;
     }
     .color4 {
         color: #FF4948;
     }
     .color5 {
         color: #696969;
     }
     .color6 {
         color: #A4A4A4;
     }
     .color7 {
         color: #A7A7A7;
     }
     .color8 {
         color: #0FB269;
     }
     .color9 {
         color: #FF5600;
     }
     .color10 {
         color: #161998;
     }
     .color11 {
         color: #08AD60;
     }
     .color12 {
         color: #9C9C9E;
     }
     .bgColor1 {
         background-color: #fff;
     }
     .bgColor2 {
         background-color: #FFF1F4;
     }
     .bgColor3 {
         background-color: #FFF8F4;
     }
     .bgColor4 {
         background-color: #F4F5FF;
     }
     .bgColor5 {
         background-color: #F1FFF8;
     }
     .border1 {
         border: 2rpx solid #D9D9D9;
     }
     .border2 {
         border: 2rpx solid #FFE0E0;
     }
     .border3 {
         border: 4rpx solid #FF4948;
         box-sizing: border-box;
         box-shadow: 0rpx 4rpx 8rpx 0rpx rgba(255, 73, 72, 0.5);
     }
     .border4 {
         border: 2rpx solid #FFFFFF;
     }
     .shadow1 {
         box-shadow: 0rpx 0rpx 27rpx 0rpx rgba(0, 0, 0, 0.1);
     }
     .shadow2 {
         box-shadow: 0rpx 0rpx 15rpx 0rpx rgba(0, 0, 0, 0.1);
     }
     .progress-bar {
         width: 100%;
         height: 8rpx;
         background: #EEEEEE;
         border-radius: 4rpx;
         overflow: hidden;
     }
     .progress-inner {
         height: 100%;
         border-radius: 4rpx;
         transition: width 0.3s ease-in-out;
         &.very-satisfied {
             background: linear-gradient(270deg, #FF8064 0%, #FF4934 100%);
         }
         &.satisfied {
             background: linear-gradient(270deg, #05DEE1 0%, #02BAC0 100%);
         }
         &.normal {
             background: linear-gradient(270deg, #7DC4FF 0%, #4791FF 100%);
         }
         &.unsatisfied {
             background: linear-gradient(270deg, #FFD364 0%, #FEA834 100%);
         }
     }
     .picker-view {
         height: 460rpx;
         font-size: 35rpx;
     }
     /deep/.picker-view {
         margin: 0 auto;
         .item {
             text-align: center;
             font-family: PingFangSC, PingFang SC;
             font-weight: 600;
             font-size: 36rpx;
             color: #333333;
             line-height: 50rpx;
         }
     }
     .submitBtn {
         width: calc(100% - 62rpx);
         margin: 0 31rpx;
         line-height: 96rpx;
         text-align: center;
         background: linear-gradient(270deg, #FC8D55 0%, #FF4948 100%);
         border-radius: 48rpx;
         font-weight: 600;
         font-size: 35rpx;
         color: #fff;
     }
 </style>
         </view> -->
    <view
      class="mt-27 shadow1 border2 ml-29 mr-29 pt-31 br-15"
      style="height: 511rpx"
    >
      <view class="flex mlr-o tabs mb-40">
        <view
          v-for="(item, index) in tabs"
          :key="index"
          :class="['tab-item', currentTab === index ? 'active' : '']"
          @click="handleTabClick(index)"
        >
          {{ item }}
        </view>
      </view>
      <view
        ref="chartRef"
        id="chart"
        style="width: 100%; height: 405rpx"
      ></view>
    </view>
    <view class="fs-35 font-bold mt-38 ml-27"> 问题类型排名 </view>
    <view class="ml-29 mr-29 mt-27 shadow1 pt-31 border2 pl-38 pr-38 br-15">
      <uni-data-select v-model="value1" :localdata="range"></uni-data-select>
      <view class="fs-23 type-rank-list-scroll">
        <view class="flex a-center mb-38" v-for="(item, idx) in typeRankList" :key="idx">
          <view class="w-130 color3">{{ item.typeName }}</view>
          <u-line-progress
            :percentage="item.percent"
            inactiveColor="#EEEEEE"
            :activeColor="item.gradientColor"
            height="38rpx"
          >
            <text class="u-percentage-slot pr-19 fs-23">{{ item.count }}</text>
          </u-line-progress>
        </view>
      </view>
    </view>
    <view class="fs-35 font-bold mt-38 ml-27"> 评价占比 </view>
    <view
      class="ml-29 mr-29 mt-27 shadow1 border2 br-15"
      style="height: 417rpx"
    >
      <view class="flex pl-38 pr-38">
        <view
          ref="rateChartRef"
          id="rateChart"
          style="width: 288rpx; height: 417rpx"
        ></view>
        <view style="width: 46rpx"></view>
        <view class="flex1 pt-54">
          <view class="mb-27">
            <view class="flex a-center j-between mb-13">
              <text class="fs-23">非常满意</text>
              <text class="fs-23 color12">{{ greatSatisfactionRate }}%</text>
            </view>
            <view class="progress-bar">
              <view
                class="progress-inner very-satisfied"
                :style="{width: greatSatisfactionRate + '%'}"
              ></view>
            </view>
          </view>
          <view class="mb-27">
            <view class="flex a-center j-between mb-13">
              <text class="fs-23">满意</text>
              <text class="fs-23 color12">{{ satisfactionRate }}%</text>
            </view>
            <view class="progress-bar">
              <view
                class="progress-inner satisfied"
                :style="{width: satisfactionRate + '%'}"
              ></view>
            </view>
          </view>
          <view class="mb-27">
            <view class="flex a-center j-between mb-13">
              <text class="fs-23">一般</text>
              <text class="fs-23 color12">{{ generalSatisfactionRate }}%</text>
            </view>
            <view class="progress-bar">
              <view
                class="progress-inner normal"
                :style="{width: generalSatisfactionRate + '%'}"
              ></view>
            </view>
          </view>
          <view class="mb-27">
            <view class="flex a-center j-between mb-13">
              <text class="fs-23">不满意</text>
              <text class="fs-23 color12">{{ dissatisfactionRate }}%</text>
            </view>
            <view class="progress-bar">
              <view
                class="progress-inner unsatisfied"
                :style="{width: dissatisfactionRate + '%'}"
              ></view>
            </view>
          </view>
        </view>
      </view>
    </view>
    <view class="h-40 safe-b"></view>
    <!-- 选择服务社区 -->
    <u-popup
      :show="selectPopup"
      round="16rpx"
      @close="selectPopup = false"
      :safe-area-inset-bottom="false"
      @open="openSelectPopup"
    >
      <view class="relative pb-40">
        <image
          @tap.stop="selectPopup = false"
          src="@/static/closeImg.png"
          class="w-35 h-35 absolute"
          style="right: 31rpx; top: 46rpx"
        />
        <view class="txt-center pt-38 pb-40 fs-35 lh-48 font-bold"
          >请选择服务社区</view
        >
        <!-- <view
          class="flex a-center j-between txt-center py-10 fs-27 font-bold bgColor1"
        >
          <view v-if="hasTier(2)" class="flex1">区县</view>
          <view v-if="hasTier(3)" class="flex1">街道</view>
          <view v-if="hasTier(4)" class="flex1">社区</view>
        </view> -->
        <view class="mb-20">
          <picker-view
            :value="value"
            @change="bindChange"
            class="picker-view"
            immediate-change
          >
            <picker-view-column v-if="hasTier(2)">
              <view
                class="item"
                v-for="(item, index) in regionTree"
                :key="index"
              >
                {{ item.name }}
              </view>
            </picker-view-column>
            <picker-view-column v-if="hasTier(3)">
              <view
                class="item"
                v-for="(item, index) in getStreets()"
                :key="index"
              >
                {{ item.name }}
              </view>
            </picker-view-column>
            <picker-view-column v-if="hasTier(4)">
              <view
                class="item"
                v-for="(item, index) in getCommunities()"
                :key="index"
              >
                {{ item.name }}
              </view>
            </picker-view-column>
          </picker-view>
        </view>
        <view class="submitBtn" @click="chooseCommunity">确认</view>
      </view>
    </u-popup>
  </view>
</template>
<script>
import * as echarts from "echarts";
import {
  getRegionTree,
  getStaticsPartOne,
  getStaticsPartTwo,
  getStaticsPartThree,
  getStaticsPartFour
} from "./service.js";
export default {
  data() {
    return {
      userInfo: {},
      address: "",
      value: [0, 0, 0],
      confirmValue: [0, 0, 0],
      regionTree: [], // 区域树数据
      value1: 0,
      selectPopup: false,
      range: [
        {
          value: 0,
          text: "排名前五",
        },
        {
          value: 1,
          text: "排名前十",
        },
        {
          value: 2,
          text: "所有排名",
        },
      ],
      chart: null,
      rateChart: null,
      tabs: ["近7天", "近15天", "近30天"],
      currentTab: 0,
      chartData: null,
      rateData: [],
      statisticsData: {
        satisfaction: {
          total: 0,
          month: 0,
          compare: 0,
        },
        demands: {
          total: 0,
          month: 0,
          compare: 0,
        },
        processTime: {
          total: 0,
          month: 0,
          compare: 0,
        },
        status: {
          processing: 0,
          reviewing: 0,
          delayed: 0,
          completed: 0,
        },
        overtime: {
          total: 0,
          month: 0,
          compare: 0,
        },
        completionRate: {
          total: 0,
          month: 0,
          compare: 0,
        },
      },
      currentAreaId: '',
      currentTier: -1,
      typeRankList: [],
      greatSatisfactionRate: 0,
      satisfactionRate: 0,
      generalSatisfactionRate: 0,
      dissatisfactionRate: 0,
    };
  },
  onLoad() {
    this.userInfo = uni.getStorageSync("userInfo");
    if (this.userInfo.identity == 1) {
        this.userInfo.isAdmin = 0
    }
    this.initRegionData();
  },
  mounted() {
    this.$nextTick(() => {
      this.initChart();
      this.initRateChart();
    });
  },
  methods: {
    clearAddress() {
      this.address = "";
      this.value = [0, 0, 0];
      this.confirmValue = [0, 0, 0];
      this.currentAreaId = '';
      this.currentTier = -1;
      this.getStatisticsData("", -1);
      this.getChartData(1);
      this.getTypeRankData();
      this.getRateData();
    },
    //初始化区域数据
    initRegionData() {
      getRegionTree().then((res) => {
        if (res.code === 200) {
          this.regionTree = [{name: '全部', id: 'all', tier: 2, children: []}, ...(res.data || [])];
          this.value = [0, 0, 0];
          this.confirmValue = [0, 0, 0];
          this.address = "全部";
          this.currentAreaId = '';
          this.currentTier = -1;
          this.getStatisticsData("", -1);
          this.getChartData(1);
          this.getTypeRankData();
          this.getRateData();
        }
      });
    },
    // 获取当前选中的区域对象
    getSelectedRegion() {
      // 区县
      const county = this.regionTree[this.value[0]];
      if (!county || county.id === "all") return { id: "", tier: -1 };
      // 有街道
      const streets =
        county.children && county.children.filter((c) => c && c.tier === 3);
      if (streets && streets.length) {
        const street = streets[this.value[1] - 1]; // -1 因为有"全部"选项
        if (!street || this.value[1] === 0 || street.id === "all")
          return { id: county.id, tier: county.tier };
        const communities =
          street.children && street.children.filter((c) => c && c.tier === 4);
        if (communities && communities.length) {
          const community = communities[this.value[2] - 1];
          if (!community || this.value[2] === 0 || community.id === "all")
            return { id: street.id, tier: street.tier };
          return { id: community.id, tier: community.tier };
        }
        return { id: street.id, tier: street.tier };
      }
      // 区县下直接有社区
      const communities =
        county.children && county.children.filter((c) => c && c.tier === 4);
      if (communities && communities.length) {
        const community = communities[this.value[1] - 1];
        if (!community || this.value[1] === 0 || community.id === "all")
          return { id: county.id, tier: county.tier };
        return { id: community.id, tier: community.tier };
      }
      // 只选了区县
      return { id: county.id, tier: county.tier };
    },
    // 根据value设置地址显示
    setAddressByValue() {
      const names = [];
      const county = this.regionTree[this.value[0]];
      if (!county) {
        this.address = "全部";
        return;
      }
      // 如果选中区县的全部
      if (county.id === "all") {
        this.address = "全部";
        return;
      }
      names.push(county.name);
      // 检查是否有街道
      const streets =
        county.children && county.children.filter((c) => c && c.tier === 3);
      if (streets && streets.length) {
        const street = streets[this.value[1] - 1]; // -1 因为有"全部"选项
        if (street && street.id !== "all") {
          names.push(street.name);
          // 检查街道下是否有社区
          const communities =
            street.children && street.children.filter((c) => c && c.tier === 4);
          if (communities && communities.length) {
            const community = communities[this.value[2] - 1];
            if (community && community.id !== "all") {
              names.push(community.name);
            }
          }
        }
      } else {
        // 区县下直接有社区
        const communities =
          county.children && county.children.filter((c) => c && c.tier === 4);
        if (communities && communities.length) {
          const community = communities[this.value[1] - 1];
          if (community && community.id !== "all") {
            names.push(community.name);
          }
        }
      }
      this.address = names.join("-");
    },
    //选择服务社区
    chooseCommunity() {
      this.confirmValue = [...this.value];
      this.setAddressByValue();
      const { id, tier } = this.getSelectedRegion();
      this.currentAreaId = id;
      this.currentTier = tier;
      this.selectPopup = false;
      this.getStatisticsData(id, tier);
      this.getChartData(this.currentTab + 1);
      this.getTypeRankData();
      this.getRateData();
    },
    // 切换社区
    bindChange(e) {
      const newValue = e.detail.value;
      // 级联重置逻辑
      if (newValue[0] !== this.value[0]) {
        newValue[1] = 0;
        newValue[2] = 0;
      } else if (newValue[1] !== this.value[1]) {
        newValue[2] = 0;
      }
      this.value = newValue;
    },
    openSelectPopup() {
      this.value = this.confirmValue;
    },
    handleTabClick(index) {
      this.currentTab = index;
      this.getChartData(index + 1);
    },
    initChart() {
      const chartDom = document.getElementById("chart") || this.$refs.chartRef;
      this.chart = echarts.init(chartDom);
      if (this.chartData) this.updateChart();
    },
    updateChart() {
      if (!this.chartData) return;
      const option = {
        color: ["#FF7B7B", "#FFB75B"],
        tooltip: {
          trigger: "axis",
          axisPointer: {
            type: "shadow",
          },
        },
        legend: {
          data: ["诉求单量", "诉求办结数"],
          bottom: "0",
          itemGap: uni.upx2px(60),
          selectedMode: true,
        },
        grid: {
          left: "3%",
          right: "4%",
          bottom: "15%",
          top: "3%",
          containLabel: true,
        },
        xAxis: {
          type: "category",
          data: this.chartData.dates,
          axisLine: {
            lineStyle: {
              color: "#E5E5E5",
            },
          },
          axisTick: {
            show: false,
          },
          axisLabel: {
            color: "#888888",
            fontSize: uni.upx2px(19),
            lineHeight: uni.upx2px(23),
            formatter: function (value) {
              return value.split("\n").join("\n");
            },
          },
        },
        yAxis: {
          type: "value",
          splitLine: {
            lineStyle: {
              type: "dashed",
              color: "#fff",
            },
          },
          axisLine: {
            show: false,
          },
          axisTick: {
            show: false,
          },
        },
        series: [
          {
            name: "诉求单量",
            type: "bar",
            barWidth: uni.upx2px(38),
            itemStyle: {
              borderRadius: [20, 20, 20, 20],
              color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                {
                  offset: 0,
                  color: "#FF807E",
                },
                {
                  offset: 1,
                  color: "#FF4948",
                },
              ]),
            },
            data: this.chartData.demands,
          },
          {
            name: "诉求办结数",
            type: "line",
            smooth: true,
            symbol: "circle",
            symbolSize: 8,
            itemStyle: {
              color: "#FFB75B",
              borderWidth: 2,
              borderColor: "#fff",
              shadowColor: "#FB9A0E",
              shadowBlur: 8,
              shadowOffsetY: 4,
            },
            lineStyle: {
              width: 2,
              curveness: 0.3,
            },
            data: this.chartData.completed,
          },
        ],
      };
      this.chart && this.chart.setOption(option);
    },
    initRateChart() {
      const chartDom =
        document.getElementById("rateChart") || this.$refs.rateChartRef;
      this.rateChart = echarts.init(chartDom);
      this.updateRateChart();
    },
    updateRateChart() {
      const option = {
        tooltip: {
          trigger: "item",
          confine: true,
          // formatter: '{b}: {c}%',
          backgroundColor: "rgba(255, 255, 255, 0.9)",
          borderColor: "#FFE0E0",
          borderWidth: 1,
          textStyle: {
            color: "#666666",
            fontSize: 12,
          },
          padding: [8, 12],
        },
        series: [
          {
            name: "评价占比",
            type: "pie",
            radius: ["55%", "100%"],
            center: ["50%", "50%"],
            avoidLabelOverlap: false,
            label: {
              show: false,
            },
            labelLine: {
              show: false,
            },
            emphasis: {
              scale: false,
              scaleSize: 0,
            },
            data: this.rateData,
          },
        ],
      };
      this.rateChart && this.rateChart.setOption(option);
    },
    // 添加获取统计数据的方法
    async getStatisticsData(areaId = "", tier = -1) {
      try {
        const res = await getStaticsPartOne({
          areaId,
          tier,
        });
        if (res.code === 200) {
          this.statisticsData = {
            satisfaction: {
              total: res.data.satisfactionRate ?? 0,
              month: res.data.thisMonthSatisfactionRate ?? 0,
              compare: res.data.lastMonthCompareSatisfactionRate ?? 0,
            },
            demands: {
              total: res.data.allTotal ?? 0,
              month: res.data.thisMonthTotal ?? 0,
              compare: res.data.lastMonthCompareTotal ?? 0,
            },
            processTime: {
              total: res.data.averageTime ?? 0,
              month: res.data.thisMonthAverageTime ?? 0,
              compare: res.data.lastMonthCompareAverageTime ?? 0,
            },
            status: {
              processing: res.data.nowTransactTotal ?? 0,
              reviewing: res.data.auditTransactTotal ?? 0,
              delayed: res.data.postponeTransactTotal ?? 0,
              completed: res.data.completeTransactTotal ?? 0,
            },
            overtime: {
              total: res.data.overtimeTransactTotal ?? 0,
              month: res.data.thisMonthOvertimeTransactTotal ?? 0,
              compare: res.data.lastMonthOvertimeTransactCompareTotal ?? 0,
            },
            completionRate: {
              total: 0,
              month: 0,
              compare: 0,
            },
          };
          // 更新图表数据
          this.updateChart();
          this.updateRateChart();
        }
      } catch (error) {
        console.error("获取统计数据失败:", error);
      }
    },
    hasTier(tier) {
      // tier=2: 区县始终有
      if (tier === 2) return true;
      // tier=3: 当前区县children里有tier=3
      if (tier === 3) {
        const county = this.regionTree[this.value[0]];
        return (
          county &&
          Array.isArray(county.children) &&
          county.children.some((c) => c && c.tier === 3)
        );
      }
      // tier=4: 当前区县children里有tier=4,或街道children里有tier=4
      if (tier === 4) {
        const county = this.regionTree[this.value[0]];
        if (!county || !Array.isArray(county.children)) return false;
        // 区县下直接有社区
        if (county.children.some((c) => c && c.tier === 4)) return true;
        // 区县下有街道,街道下有社区
        const street = county.children[this.value[1]];
        return (
          street &&
          Array.isArray(street.children) &&
          street.children.some((c) => c && c.tier === 4)
        );
      }
      return false;
    },
    getStreets() {
      const county = this.regionTree[this.value[0]];
      if (!county || !Array.isArray(county.children)) return [];
      // 只返回tier=3的
      const streets = county.children.filter((c) => c && c.tier === 3);
      return streets.length
        ? [{ name: "全部", id: "all", tier: 3, children: [] }, ...streets]
        : [];
    },
    getCommunities() {
      const county = this.regionTree[this.value[0]];
      if (!county || !Array.isArray(county.children)) return [];
      // 区县下直接有社区
      const communities = county.children.filter((c) => c && c.tier === 4);
      if (communities.length)
        return [{ name: "全部", id: "all", tier: 4 }, ...communities];
      // 区县下有街道,街道下有社区
      const street = county.children[this.value[1]];
      if (street && Array.isArray(street.children)) {
        const comms = street.children.filter((c) => c && c.tier === 4);
        if (comms.length)
          return [{ name: "全部", id: "all", tier: 4 }, ...comms];
      }
      return [];
    },
    async getChartData(timeType = 1) {
      try {
        const res = await getStaticsPartTwo({
          areaId: this.currentAreaId,
          tier: this.currentTier,
          timeType
        });
        if (res.code === 200 && res.data) {
          // 转换数据格式
          const dates = res.data.map(item => item.time.replace(/-/g, '\n').replace(/\n(\d{2})$/, '.$1'));
          const demands = res.data.map(item => item.allTotal);
          const completed = res.data.map(item => item.completeTotal);
          this.chartData = {
            dates,
            demands,
            completed
          };
          this.updateChart();
        }
      } catch (e) {
        console.error('获取图表数据失败', e);
      }
    },
    async getTypeRankData() {
      let rank;
      if (this.value1 === 0) rank = 5;
      else if (this.value1 === 1) rank = 10;
      // value1 === 2 时不传rank
      const params = {
        areaId: this.currentAreaId,
        tier: this.currentTier
      };
      if (rank) params.rank = rank;
      try {
        const res = await getStaticsPartThree(params);
        console.log(JSON.stringify(res.data));
        if (res.code === 200 && Array.isArray(res.data)) {
          // 计算最大值
          const max = Math.max(...res.data.map(item => item.allTotal), 1);
          const gradients = [
            'linear-gradient(270deg, #FF4934 0%, #FF8064 100%)',
            'linear-gradient(270deg, #FEA834 0%, #FFD364 100%)',
            'linear-gradient(270deg, #02BAC0 0%, #05DEE1 100%)',
          ];
          const defaultGradient = 'linear-gradient(270deg, #4791FF 0%, #7DC4FF 100%)';
          // 处理数据,增加percent字段
          this.typeRankList = res.data.map((item, idx) => ({
            typeName: item.name,
            count: item.allTotal,
            percent: Math.round(item.allTotal / max * 100),
            gradientColor: gradients[idx] || defaultGradient
          }));
        }
      } catch (e) {
        console.error('获取问题类型排名失败', e);
      }
    },
    async getRateData() {
      try {
        const res = await getStaticsPartFour({
          areaId: this.currentAreaId,
          tier: this.currentTier
        });
        console.log(JSON.stringify(res.data));
        if (res.code === 200 && res.data) {
          // 饼图数据
          const values = [
            res.data.greatSatisfactionRate ?? 0,
            res.data.satisfactionRate ?? 0,
            res.data.generalSatisfactionRate ?? 0,
            res.data.dissatisfactionRate ?? 0
          ];
          const names = ['非常满意', '满意', '一般', '不满意'];
          const gradients = [
            ['#FF8064', '#FF4934'],
            ['#05DEE1', '#02BAC0'],
            ['#7DC4FF', '#4791FF'],
            ['#FFD364', '#FEA834']
          ];
          if (values.every(v => v === 0)) values[0] = 1; // 避免全为0
          this.rateData = values.map((v, idx) => ({
            value: v,
            name: names[idx],
            itemStyle: {
              color: new echarts.graphic.LinearGradient(1, 0, 0, 0, [
                { offset: 0, color: gradients[idx][0] },
                { offset: 1, color: gradients[idx][1] }
              ])
            }
          }));
          // 进度条数据
          this.greatSatisfactionRate = values[0];
          this.satisfactionRate = values[1];
          this.generalSatisfactionRate = values[2];
          this.dissatisfactionRate = values[3];
          this.updateRateChart();
        }
      } catch (e) {
        console.error('获取评价占比失败', e);
      }
    },
  },
  watch: {
    value1() {
      this.getTypeRankData();
    }
  },
};
</script>
<style scoped lang="scss">
/deep/.uni-select {
  width: 231rpx;
  height: 65rpx;
  margin: 0 auto;
  margin-bottom: 38rpx;
  font-size: 27rpx;
  color: #797f81;
  border-color: #e5e5e5;
  border-radius: 33rpx;
  padding: 0 31rpx 0 40rpx;
  .uni-select__input-text {
    color: #797f81;
  }
}
.content {
  background: linear-gradient(
    180deg,
    #ffdcdb 0%,
    rgba(255, 255, 255, 0) 100rpx,
    #fff 100%
  );
}
.gap25 {
  gap: 25rpx;
}
.tabs {
  width: 412rpx;
  background-color: #fff1f1;
  border-radius: 30rpx;
  height: 65rpx;
  line-height: 65rpx;
  font-size: 27rpx;
  position: relative;
  overflow: hidden;
}
.tab-item {
  color: #797f81;
  flex: 1;
  text-align: center;
  font-weight: 400;
  height: 54rpx;
  line-height: 54rpx;
  border-radius: 27rpx;
  margin: 6rpx;
  position: relative;
  z-index: 1;
  transform: translateZ(0);
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  &.active {
    background-color: #fff;
    color: #ff4948;
    font-weight: 600;
    box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
    transform: scale(1.02);
  }
}
.chart-wrapper {
  background: #fff;
  border-radius: 20rpx;
  padding: 20rpx;
}
.color1 {
  color: #666565;
}
.color2 {
  color: #c1c1c1;
}
.color3 {
  color: #666666;
}
.color4 {
  color: #ff4948;
}
.color5 {
  color: #696969;
}
.color6 {
  color: #a4a4a4;
}
.color7 {
  color: #a7a7a7;
}
.color8 {
  color: #0fb269;
}
.color9 {
  color: #ff5600;
}
.color10 {
  color: #161998;
}
.color11 {
  color: #08ad60;
}
.color12 {
  color: #9c9c9e;
}
.bgColor1 {
  background-color: #fff;
}
.bgColor2 {
  background-color: #fff1f4;
}
.bgColor3 {
  background-color: #fff8f4;
}
.bgColor4 {
  background-color: #f4f5ff;
}
.bgColor5 {
  background-color: #f1fff8;
}
.border1 {
  border: 2rpx solid #d9d9d9;
}
.border2 {
  border: 2rpx solid #ffe0e0;
}
.border3 {
  border: 4rpx solid #ff4948;
  box-sizing: border-box;
  box-shadow: 0rpx 4rpx 8rpx 0rpx rgba(255, 73, 72, 0.5);
}
.border4 {
  border: 2rpx solid #ffffff;
}
.shadow1 {
  box-shadow: 0rpx 0rpx 27rpx 0rpx rgba(0, 0, 0, 0.1);
}
.shadow2 {
  box-shadow: 0rpx 0rpx 15rpx 0rpx rgba(0, 0, 0, 0.1);
}
.progress-bar {
  width: 100%;
  height: 8rpx;
  background: #eeeeee;
  border-radius: 4rpx;
  overflow: hidden;
}
.progress-inner {
  height: 100%;
  border-radius: 4rpx;
  transition: width 0.3s ease-in-out;
  &.very-satisfied {
    background: linear-gradient(270deg, #ff8064 0%, #ff4934 100%);
  }
  &.satisfied {
    background: linear-gradient(270deg, #05dee1 0%, #02bac0 100%);
  }
  &.normal {
    background: linear-gradient(270deg, #7dc4ff 0%, #4791ff 100%);
  }
  &.unsatisfied {
    background: linear-gradient(270deg, #ffd364 0%, #fea834 100%);
  }
}
.picker-view {
  height: 460rpx;
  font-size: 35rpx;
}
/deep/.picker-view {
  margin: 0 auto;
  .item {
    text-align: center;
    font-family: PingFangSC, PingFang SC;
    font-weight: 600;
    font-size: 36rpx;
    color: #333333;
    line-height: 50rpx;
  }
}
.submitBtn {
  width: calc(100% - 62rpx);
  margin: 0 31rpx;
  line-height: 96rpx;
  text-align: center;
  background: linear-gradient(270deg, #fc8d55 0%, #ff4948 100%);
  border-radius: 48rpx;
  font-weight: 600;
  font-size: 35rpx;
  color: #fff;
}
.type-rank-list-scroll {
  max-height: 500rpx;
  overflow-y: auto;
}
</style>
H5/pages/statistics/service.js
@@ -1,9 +1,31 @@
import request from '@/utils/request.js'
// 获取区县-街道-社区树
export const getRegionTree =()=>{
    return request.get(`/api/huacheng-sangeshenbian/bc-region/regionTree-applet`)
// 获取统计分析-第一部分(处理满意率+诉求单量统计上面部分)
export const getStaticsPartOne = (params) => {
    return request.post(`/api/huacheng-sangeshenbian/applet/statics/part-one`,params)
// 获取统计分析-第二部分(处理满意率+诉求单量统计下面部分)
export const getStaticsPartTwo = (params) => {
    return request.post(`/api/huacheng-sangeshenbian/applet/statics/part-two`,params)
}
// 获取统计分析-第三部分(党员申请+编辑党员信息+党员信息详情)
export const getStaticsPartThree = (params) => {
    return request.post(`/api/huacheng-sangeshenbian/applet/statics/part-three`,params)
}
// 获取统计分析-第四部分(党员申请+编辑党员信息+党员信息详情)
export const getStaticsPartFour = (params) => {
    return request.post(`/api/huacheng-sangeshenbian/applet/statics/part-four`,params)
}
// 获取区域树
export const getRegionTree = (params) => {
    return request.get(`/api/huacheng-sangeshenbian/applet/statics/region-tree`,params)
}
// 党员申请
export const apply =(params)=>{
    return request.post(`/api/huacheng-sangeshenbian/applet/party-member/apply`,params)
H5/pages/supervision/edit-supervision-progress.vue
@@ -101,12 +101,13 @@
            }
        },
        onLoad(params) {
            console.log('JSON.parse(params.data)', JSON.parse(params.data))
            this.complaintId = params.id
            this.describe = JSON.parse(params.data).describe
            this.progressId = JSON.parse(params.data).id
            this.video = JSON.parse(params.data).video.split(',')
            this.imgUrls = JSON.parse(params.data).imgUrl.split(',')
            this.localImageUrls = JSON.parse(params.data).imgUrl.split(',')
            this.video = JSON.parse(params.data).video ? JSON.parse(params.data).video.split(',') : []
            this.imgUrls = JSON.parse(params.data).imgUrl ? JSON.parse(params.data).imgUrl.split(',') : []
            this.localImageUrls = JSON.parse(params.data).imgUrl ? JSON.parse(params.data).imgUrl.split(',') : []
            getComplaintDetail({
                id: params.id
            }).then(res => {
H5/pages/supervision/supervision.vue
@@ -219,14 +219,14 @@
        },
        methods: {
            searchList() {
                if (this.searchParams.keyword == '') {
                    uni.showToast({
                        title: '请输入关键字搜索',
                        icon: 'none',
                        mask: true
                    })
                    return
                }
                // if (this.searchParams.keyword == '') {
                //     uni.showToast({
                //         title: '请输入关键字搜索',
                //         icon: 'none',
                //         mask: true
                //     })
                //     return
                // }
                this.searchParams.pageNum = 1
                this.fetchList(this.searchParams, (e) => {
                    this.list = e.records || []
@@ -244,7 +244,7 @@
            },
            toDetailProgress(item) {
                uni.navigateTo({
                    url: `/pages/supervision/supervision-progress?id=${item.id}`
                    url: `/pages/supervision/supervision-progress?id=${this.type==1?item.id :item.complaintId}`
                })
            },
            fetchList(params, callback) {
H5/pages/work-detail/work-detail.vue
@@ -331,10 +331,10 @@
        <!-- 上级显示 -->
        <view class="btnButtom" v-if="(!isParty)&&orderInfo.listControlsButtonStatus == 0">
            <view class="btnDown" @click.stop="toUp" v-if="userInfo.accountLevel > 1"
                :class="[2, 3].includes(userInfo.accountLevel) ? '' : 'partyUp'">问题上报</view>
            <view class="btnDown" @click.stop="toDown" v-if="userInfo.accountLevel < 4"
                :class="[2, 3].includes(userInfo.accountLevel) ? '' : 'partyDown'">诉求下派</view>
            <view class="btnDown" @click.stop="toUp" v-if="userInfo.levelId > 1"
                :class="[2, 3].includes(userInfo.levelId) ? '' : 'partyUp'">问题上报</view>
            <view class="btnDown" @click.stop="toDown" v-if="userInfo.levelId < 4"
                :class="[2, 3].includes(userInfo.levelId) ? '' : 'partyDown'">诉求下派</view>
            <view class="btnAdd" @click="addProgress">添加办理进度</view>
            <view class="btnAdd" @click="resultEntery">办理结果录入</view>
        </view>
H5/static/home/Group 2@2x.png
H5/utils/request.js
@@ -20,7 +20,7 @@
    return new Promise(function(resolve, reject) {
        let token = uni.getStorageSync('token')
        token =
            'Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOjE4OTU1MjI1Nzk1MDcwODEyMTgsInR5cGUiOjEsImV4cCI6MTc0Nzk4MzIyMiwiY3JlYXRlZCI6MTc0NjY4NzIyMjAxMX0.pmUfTkxkbBirDMbnMR1IaLsbSiiwHc366_yyAetCzTOWYxTNgmQlmvw26_W62NHLOebB_ZAEgZsPvkJcaLOoPg'
            'Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOjE4OTI0MjE4MjgwODU4Mzc4MjYsInR5cGUiOjEsImV4cCI6MTc0OTAxMjMwMSwiY3JlYXRlZCI6MTc0NzcxNjMwMTM0OH0.8C2x0xHFW45XbehroA1uWziKsUdVHFck8VpIOVkS_s0KvAeTbFG7nIeEQEjGq0a8bn6kHIF3q58mJi4H34Q0Cg'
        uni.setStorageSync('token', token)
        let header = {
            'content-type': type ? 'application/x-www-form-urlencoded;charset=UTF-8' : 'application/json',
management/config/env.ts
@@ -1,6 +1,6 @@
export default {
  dev: {
    SERVER_URL: 'http://192.168.110.106:6194',
    SERVER_URL: 'http://192.168.110.111:6194',
    // SERVER_URL: 'https://huacheng.psciio.com',
  },
  test: {
management/config/routes.ts
@@ -34,6 +34,12 @@
    ],
  },
  {
    name: '统计分析',
    path: '/statistics',
    component: './statistics/index',
    // access: '/system_setting/position_management',
  },
  {
    path: '/setting',
    // layout: false,
    name: '系统设置',
@@ -46,7 +52,7 @@
        access: '/system_setting/position_management',
      },
      {
        name: '角色管理',
        name: '权限管理',
        path: '/setting/role',
        component: './setting/role',
        access: '/system_setting/role_management',
management/package.json
@@ -31,7 +31,7 @@
    "not ie <= 10"
  ],
  "dependencies": {
    "@ant-design/charts": "^2.1.2",
    "@ant-design/charts": "^2.3.0",
    "@ant-design/icons": "^4.8.0",
    "@ant-design/plots": "^2.2.6",
    "@ant-design/pro-components": "^2.6.35",
@@ -42,6 +42,8 @@
    "braft-editor": "^2.3.9",
    "classnames": "^2.3.2",
    "crypto-js": "^4.2.0",
    "echarts": "^5.6.0",
    "echarts-for-react": "^3.0.2",
    "moment": "^2.29.4",
    "omit.js": "^2.0.2",
    "rc-menu": "^9.8.2",
management/src/pages/appeal-management/detail.jsx
@@ -1,13 +1,14 @@
import { buildProTableDataSource, sendRequest, showDelConfirm } from '@/utils/antdUtils';
import { PageContainer, ProTable } from '@ant-design/pro-components';
import { Button, Card, Select, Space, Descriptions, Divider, Steps, message } from 'antd';
import { Button, Card, Select, Space, Descriptions, Divider, Steps, message, Modal, Form, Input, Upload, Popconfirm } from 'antd';
import { useRef, useState, useEffect } from 'react';
import { Access, useAccess } from 'umi'
import { history, useLocation } from "@umijs/max"
import { getDetail } from './service';
import { getDetail, editProgress, deleteProgress } from './service';
import moment from 'moment';
import './index.less';
import {downLoad } from '@/utils/utils';
import {downLoad, customRequest } from '@/utils/utils';
import { PlusOutlined } from '@ant-design/icons';
const Account = () => {
    const actionRef = useRef();
@@ -16,6 +17,22 @@
    const [detail, setDetail] = useState({});
    const searchParams = new URLSearchParams(useLocation().search);
    const id = searchParams.get('id');
    const [editModalVisible, setEditModalVisible] = useState(false);
    const [editProgressData, setEditProgressData] = useState(null);
    const [editForm] = Form.useForm();
    const [loading, setLoading] = useState(false);
    const [imgFileList, setImgFileList] = useState([]);
    const [videoFileList, setVideoFileList] = useState([]);
    const [previewVisible, setPreviewVisible] = useState(false);
    const [previewImage, setPreviewImage] = useState('');
    const [previewType, setPreviewType] = useState('image'); // 'image' or 'video'
    const uploadConfig = {
        name: 'file',
        action: 'https://huacheng.psciio.com/api/huacheng-communitybackstage/communitypartybuilding/uploadimage',
        headers: {
            Authorization: 'Bearer ' + localStorage.getItem('token'),
        },
    };
    useEffect(() => {
        getDetail({ id: id }).then((res) => {
@@ -111,6 +128,117 @@
            span: 4,
        },
    ];
    // 上传前校验
    const beforeUpload = (file) => {
        return new Promise((resolve, reject) => {
            if (file.name.includes(',')) {
                message.warning('上传文件不能包含英文逗号(,)');
                return Upload.LIST_IGNORE;
            }
            setLoading(true);
            resolve(file);
        });
    };
    // 图片上传change
    const handleImgChange = ({ file, fileList: newFileList }) => {
        if (file.status === 'error' || (file.status === 'done' && file.response && file.response.code !== 200)) {
            setLoading(false);
            setImgFileList([]);
            message.error('上传失败');
            editForm.setFieldValue('imgUrl', []);
            return;
        }
        if (file.status === 'done' && file.response && file.response.code === 200) {
            setLoading(false);
            message.success('上传成功');
        }
        let urls = newFileList.map(item => {
            if (item.status === 'done' && item.response && item.response.data) {
                item.url = item.response.data;
            }
            return item.url || item.url;
        }).filter(Boolean);
        setImgFileList(newFileList);
        editForm.setFieldValue('imgUrl', urls);
    };
    // 视频上传change
    const handleVideoChange = ({ file, fileList: newFileList }) => {
        if (file.status === 'error' || (file.status === 'done' && file.response && file.response.code !== 200)) {
            setLoading(false);
            setVideoFileList([]);
            message.error('上传失败');
            editForm.setFieldValue('videoUrl', []);
            return;
        }
        if (file.status === 'done' && file.response && file.response.code === 200) {
            setLoading(false);
            message.success('上传成功');
        }
        let urls = newFileList.map(item => {
            if (item.status === 'done' && item.response && item.response.data) {
                item.url = item.response.data;
            }
            return item.url || item.url;
        }).filter(Boolean);
        setVideoFileList(newFileList);
        editForm.setFieldValue('videoUrl', urls);
    };
    // 办理进度编辑提交
    const handleEditProgress = async () => {
        try {
            setLoading(true);
            console.log('editProgressData', editProgressData);
            const values = await editForm.validateFields();
            await editProgress({
                complaintId: id,
                describe: values.describe,
                id: editProgressData.id,
                imgUrl: (values.imgUrl || []).join(','),
                video: (values.videoUrl || []).join(',')
            });
            message.success('编辑成功');
            setEditModalVisible(false);
            getDetail({ id }).then((res) => setDetail(res.data));
        } catch (e) {
            // 校验或请求失败
        } finally {
            setLoading(false);
        }
    };
    // 办理进度删除
    const handleDeleteProgress = async (progressId) => {
        try {
            await deleteProgress({ id: progressId });
            message.success('删除成功');
            getDetail({ id }).then((res) => setDetail(res.data));
        } catch (e) {
            message.error('删除失败');
        }
    };
    // 在弹窗打开时同步 fileList
    useEffect(() => {
        if (editModalVisible && editProgressData) {
            setImgFileList((editProgressData.imgUrl ? editProgressData.imgUrl.split(',') : []).map((url, idx) => ({ uid: idx, url, status: 'done' })));
            setVideoFileList((editProgressData.video ? editProgressData.video.split(',') : []).map((url, idx) => ({ uid: idx, url, status: 'done' })));
            editForm.setFieldsValue({
                describe: editProgressData.describe,
                imgUrl: editProgressData.imgUrl ? editProgressData.imgUrl.split(',') : [],
                videoUrl: editProgressData.video ? editProgressData.video.split(',') : [],
            });
        }
        if (!editModalVisible) {
            setImgFileList([]);
            setVideoFileList([]);
        }
    }, [editModalVisible, editProgressData]);
    return (
        <div>
            <PageContainer className={'appeal-management-detail'} header={{ breadcrumb: {} }} title="述求详情" >
@@ -121,22 +249,34 @@
                        <Descriptions title="办理进度" column={1}>
                            {detail.complaintProgresses?.length > 0 ? detail.complaintProgresses.map((item, index) => (
                                <Descriptions.Item key={index}>
                                    <Card style={{ width: '800px' }} title={item.createByName} extra={moment(item.createTime).format('YYYY-MM-DD HH:mm:ss')}>
                                    <Card style={{ width: '800px', position: 'relative' }} title={item.createByName} extra={moment(item.createTime).format('YYYY-MM-DD HH:mm:ss')}>
                                        <Descriptions column={1} >
                                            <Descriptions.Item>
                                                {item.describe}
                                            </Descriptions.Item>
                                            <Descriptions.Item>{item.describe}</Descriptions.Item>
                                            <Descriptions.Item label={'上传图片'}>
                                                {item.imgUrl && (item.imgUrl || '').split(',').map((item, index) => (
                                                    <img width={80} style={{ marginRight: '10px' }} height={80} src={item} key={index} alt="example" />
                                                {item.imgUrl && (item.imgUrl || '').split(',').map((img, idx) => (
                                                    <img width={80} style={{ marginRight: '10px' }} height={80} src={img} key={idx} alt="example" />
                                                ))}
                                            </Descriptions.Item>
                                            <Descriptions.Item label={'上传视频'}>
                                                {item.videoUrl && (item.videoUrl || '').split(',').map((item, index) => (
                                                    <video width={280} style={{ marginRight: '10px' }} src={item} key={index} controls></video>
                                                {item.video && item.video.split(',').map((video, idx) => (
                                                    <video width={280} style={{ marginRight: '10px' }} src={video} key={idx} controls></video>
                                                ))}
                                            </Descriptions.Item>
                                        </Descriptions>
                                        <div style={{ position: 'absolute', right: 24, bottom: 16 }}>
                                            <Button size="small" type="primary" style={{ marginRight: 8 }} onClick={() => {
                                                setEditProgressData(item);
                                                setEditModalVisible(true);
                                                editForm.setFieldsValue({
                                                    describe: item.describe,
                                                    imgUrl: item.imgUrl ? item.imgUrl.split(',') : [],
                                                    videoUrl: item.video ? item.video.split(',') : [],
                                                });
                                            }}>编辑</Button>
                                            <Popconfirm title="确定要删除该办理进度吗?" onConfirm={() => handleDeleteProgress(item.id)} okText="确定" cancelText="取消">
                                                <Button size="small" danger>删除</Button>
                                            </Popconfirm>
                                        </div>
                                    </Card>
                                </Descriptions.Item>
                            )) : <Descriptions.Item span={4} >暂无办理进度</Descriptions.Item>}
@@ -252,7 +392,80 @@
                    </div>
                </Card>
                <Modal
                    title="编辑办理进度"
                    open={editModalVisible}
                    onCancel={() => setEditModalVisible(false)}
                    onOk={handleEditProgress}
                    confirmLoading={loading}
                    destroyOnClose
                    width={600}
                >
                    <Form form={editForm} layout="vertical" initialValues={{ describe: '', imgUrl: [], videoUrl: [] }}>
                        <Form.Item name="describe" label="办理进度描述" rules={[{ required: true, message: '请输入办理进度描述' }]}>
                            <Input.TextArea rows={4} maxLength={500} showCount />
                        </Form.Item>
                        <Form.Item name="imgUrl" label="图片:">
                            <Upload
                                {...uploadConfig}
                                listType="picture-card"
                                maxCount={9}
                                beforeUpload={beforeUpload}
                                onChange={handleImgChange}
                                onRemove={() => {
                                    setImgFileList([]);
                                    editForm.setFieldValue('imgUrl', []);
                                }}
                                showUploadList={{ showPreviewIcon: true }}
                                accept="image/*"
                                fileList={imgFileList}
                                onPreview={file => {
                                    setPreviewType('image');
                                    setPreviewImage(file.url || file.thumbUrl);
                                    setPreviewVisible(true);
                                }}
                            >
                                {imgFileList.length < 9 && <PlusOutlined />}
                            </Upload>
                        </Form.Item>
                        <Form.Item name="videoUrl" label="视频:">
                            <Upload
                                {...uploadConfig}
                                listType="picture-card"
                                maxCount={9}
                                beforeUpload={beforeUpload}
                                onChange={handleVideoChange}
                                onRemove={() => {
                                    setVideoFileList([]);
                                    editForm.setFieldValue('videoUrl', []);
                                }}
                                showUploadList={{ showPreviewIcon: true }}
                                accept="video/*"
                                fileList={videoFileList}
                                onPreview={file => {
                                    setPreviewType('video');
                                    setPreviewImage(file.url || file.thumbUrl);
                                    setPreviewVisible(true);
                                }}
                            >
                                {videoFileList.length < 9 && <PlusOutlined />}
                            </Upload>
                        </Form.Item>
                    </Form>
                </Modal>
                <Modal
                    open={previewVisible}
                    footer={null}
                    onCancel={() => setPreviewVisible(false)}
                    width={previewType === 'video' ? 800 : 600}
                >
                    {previewType === 'image' ? (
                        <img alt="预览" style={{ width: '100%' }} src={previewImage} />
                    ) : (
                        <video style={{ width: '100%' }} src={previewImage} controls autoPlay />
                    )}
                </Modal>
            </PageContainer>
        </div>
    );
management/src/pages/appeal-management/service.js
@@ -15,3 +15,19 @@
        data
    });
}
//编辑办理进度
export const editProgress = async (data) => {
    return request(`/api/huacheng-sangeshenbian/complaint/update-progress`, {
        method: 'POST',
        data
    });
}
//删除办理进度
export const deleteProgress = async (data) => {
    return request(`/api/huacheng-sangeshenbian/complaint/del-progress/${data.id}`, {
        method: 'DELETE',
        data
    });
}
management/src/pages/party/manage/index.jsx
@@ -66,6 +66,18 @@
      }
    },
    {
      title: '创建时间',
      dataIndex: 'createTime',
      sorter: true,
      hideInSearch: true,
    },
    {
      title: '更新时间',
      dataIndex: 'updateTime',
      sorter: true,
      hideInSearch: true,
    },
    {
      title: '状态',
      dataIndex: 'freezeStatus',
      valueEnum: {
@@ -75,80 +87,84 @@
      }
    },
    {
      title: '身份证号',
      dataIndex: 'idNumber',
    },
    {
      title: '操作',
      hideInSearch: true,
      render: (text, record) => {
        return (
          <Space>
            <Access accessible={access['/party_member/edit']}>
            <Button
              type="link"
              onClick={() => {
                history.push(`/party/manage/add?type=edit&id=${record.id}`)
              }}
            >
              编辑
            </Button>
            </Access>
            <Access accessible={access['/party_member/del']}>
            <Button
              type="link"
              onClick={() => {
                showDelConfirm(async () => {
                  let status = await sendRequest(deleteBanner, {id:record.id})
                  if (status) {
                    actionRef.current.reload();
                  }
                }, '确认删除所选信息吗?');
              }}
            >
              删除
            </Button>
            </Access>
            <Access accessible={access['/party_member/detail']}>
            <Button
              type="link"
              onClick={() => {
                history.push(`/party/manage/add?detail=true&id=${record.id}`)
              }}
            >
              查看详情
            </Button>
            </Access>
            <Access accessible={access['/party_member/freeze']}>
            {record.freezeStatus == 0 && (
              <Button
                type="link"
                onClick={() => {
                  showDelConfirm1(async () => {
                    let status = await sendRequest(freeze, { id: record.id })
                  history.push(`/party/manage/add?type=edit&id=${record.id}`)
                }}
              >
                编辑
              </Button>
            </Access>
            <Access accessible={access['/party_member/del']}>
              <Button
                type="link"
                onClick={() => {
                  showDelConfirm(async () => {
                    let status = await sendRequest(deleteBanner, { id: record.id })
                    if (status) {
                      actionRef.current.reload();
                    }
                  }, '确认冻结该党员信息吗?', '冻结', '', '确认冻结该党员信息吗?');
                  }, '确认删除所选信息吗?');
                }}
              >
                冻结
                删除
              </Button>
            )}
            </Access>
            <Access accessible={access['/party_member/detail']}>
              <Button
                type="link"
                onClick={() => {
                  history.push(`/party/manage/add?detail=true&id=${record.id}`)
                }}
              >
                查看详情
              </Button>
            </Access>
            <Access accessible={access['/party_member/freeze']}>
              {record.freezeStatus == 0 && (
                <Button
                  type="link"
                  onClick={() => {
                    showDelConfirm1(async () => {
                      let status = await sendRequest(freeze, { id: record.id })
                      if (status) {
                        actionRef.current.reload();
                      }
                    }, '确认冻结该党员信息吗?', '冻结', '', '确认冻结该党员信息吗?');
                  }}
                >
                  冻结
                </Button>
              )}
            </Access>
            <Access accessible={access['/party_member/freeze']}>
            {record.freezeStatus == 1 && (
              <Button
                type="link"
                onClick={() => {
                  showDelConfirm1(async () => {
                    let status = await sendRequest(freeze, { id: record.id })
                    if (status) {
                      actionRef.current.reload();
                    }
                  }, '确认解冻该党员信息吗?', '解冻', '', '确认解冻该党员信息吗?');
                }}
              >
                解冻
              </Button>
            )}
              {record.freezeStatus == 1 && (
                <Button
                  type="link"
                  onClick={() => {
                    showDelConfirm1(async () => {
                      let status = await sendRequest(freeze, { id: record.id })
                      if (status) {
                        actionRef.current.reload();
                      }
                    }, '确认解冻该党员信息吗?', '解冻', '', '确认解冻该党员信息吗?');
                  }}
                >
                  解冻
                </Button>
              )}
            </Access>
          </Space >
@@ -175,7 +191,15 @@
          showQuickJumper: true,
          defaultPageSize: 10,
        }}
        request={(params) => {
        request={(params, sorter) => {
          console.log('weqweqeqwe', sorter);
          if (sorter.createTime) {
            params.createTimeSort = sorter.createTime === 'ascend' ? 2 : 1;
          }
          if (sorter.updateTime) {
            params.updateTimeSort = sorter.updateTime === 'ascend' ? 2 : 1;
          }
          params.communityId = params.community ? params.community[params.community.length - 1] : ''
          params.auditStatus = 1
          setExcelParams(() => params)
@@ -184,35 +208,35 @@
        toolBarRender={(action, selectRows) => [
          <Space>
            <Access accessible={access['/party_member/add']}>
            <Button
              type="primary"
              onClick={() => {
                history.push('/party/manage/add?type=add')
              }}
            >
              添加
            </Button>
              <Button
                type="primary"
                onClick={() => {
                  history.push('/party/manage/add?type=add')
                }}
              >
                添加
              </Button>
            </Access>
            <Access accessible={access['/party_member/export']}>
            <Button
              type="primary"
              onClick={() => {
                exportExcell('党员列表', excelParams, '/api/huacheng-sangeshenbian/party-member/export')
              }}
            >
              导出
            </Button>
              <Button
                type="primary"
                onClick={() => {
                  exportExcell('党员列表', excelParams, '/api/huacheng-sangeshenbian/party-member/export')
                }}
              >
                导出
              </Button>
            </Access>
            <Access accessible={access['/party_member/import']}>
            <Button
              type="primary"
              onClick={() => {
                handleModalExport(true)
                modalExportRef.current.clean()
              }}
            >
              导入
            </Button>
              <Button
                type="primary"
                onClick={() => {
                  handleModalExport(true)
                  modalExportRef.current.clean()
                }}
              >
                导入
              </Button>
            </Access>
          </Space>
        ]}
management/src/pages/setting/role/components/addAndEdit.jsx
@@ -128,7 +128,7 @@
            getContainer={false}
            width="20%"
            destroyOnClose
            title={detailType ? '角色详情' : data.id ? '编辑角色' : '添加角色'}
            title={detailType ? '权限详情' : data.id ? '编辑权限' : '添加权限'}
            open={visible}
            onCancel={() => onCancel(false)}
            afterClose={() => {
@@ -150,10 +150,10 @@
            <Form layout="horizontal" {...formItemLayout} form={form} scrollToFirstError>
                <Form.Item
                    name="name"
                    label="角色名称"
                    rules={[{ required: true, message: '请输入角色名称' }]}
                    label="权限名称"
                    rules={[{ required: true, message: '请输入权限名称' }]}
                >
                    <Input disabled={detailType} placeholder='请输入角色名称' />
                    <Input disabled={detailType} placeholder='请输入权限名称' />
                </Form.Item>
                <Form.Item
                    name="tree"
management/src/pages/setting/role/index.jsx
@@ -14,7 +14,7 @@
    const columns = [
        {
            title: '角色名称',
            title: '权限名称',
            dataIndex: 'name',
        },
        {
management/src/pages/setting/user/components/addAndEdit.jsx
@@ -1,7 +1,9 @@
import { Form, Input, Modal, Tree, Button, Spin, Row, Col, Select, Radio, Cascader } from 'antd';
import { Form, Input, Modal, Tree, Button, Spin, Row, Col, Select, Radio, Cascader, Card } from 'antd';
import { forwardRef, useImperativeHandle, useState } from 'react';
import { useEffect } from 'react';
import { getDepartmentList, systemPostList, systemRoleList, getSystemUserInfo, getCityList, addSystemUserInfo, editSystemUserInfo } from '../service';
import { DeleteOutlined } from '@ant-design/icons';
import '../index.less'
import { getDepartmentList, systemPostList, systemRoleList, getSystemUserInfo, getCityList, addSystemUserInfo, editSystemUserInfo, getCascaderData } from '../service';
import CryptoJS from 'crypto-js';
const formItemLayout = {
    labelCol: { span: 8 },
@@ -27,7 +29,10 @@
    //所属角色
    const [roleList, setRoleList] = useState([])
    //账号层级
    const [levelList, setLevelList] = useState([{ name: '市级账号', value: 1 }, { name: '区县账号', value: 2 }, { name: '街道账号', value: 3 }, { name: '社区账号', value: 4 }, { name: '党员账号', value: 5 }])
    const [levelList, setLevelList] = useState([])
    //级联数据
    const [cascaderData, setCascaderData] = useState([])
    //选择的层级
    const [activeLevel, setActiveLevel] = useState('')
    //所属区县
@@ -37,11 +42,11 @@
    const [streetList, setStreetList] = useState([])
    const [activeStreet, setActiveStreet] = useState({})
    //所属社区
    const [communityList, setCommunityList] = useState([])
    const [activeCommunity, setActiveCommunity] = useState({})
    const [accountLevels, setAccountLevels] = useState([
        { area: [], isDiscipline: 0, options: [] }
    ]);
    useEffect(() => {
        // 获取单位
@@ -51,61 +56,14 @@
    useImperativeHandle(ref, () => {
        return {
            refreshData: (data, companyList) => {
                getCountyList()
                getCascaderDatas()
                setOneCompanyList(() => companyList)
                console.log('companyList', companyList)
                systemPostList({ pageNum: 1, pageSize: 10000 }).then(res => {
                    setPositionList(() => res.data.records)
                })
                systemRoleList({ pageNum: 1, pageSize: 10000 }).then(res => {
                    setRoleList(() => res.data.records)
                })
                // 权限判断
                let adminInfo = JSON.parse(localStorage.getItem('userInfo'))
                setAdminLevel(() => adminInfo.accountLevel)
                switch (adminInfo.accountLevel) {
                    case 1:
                        setLevelList(() => [{ name: '市级账号', value: 1 }, { name: '区县账号', value: 2 }, { name: '街道账号', value: 3 }, { name: '社区账号', value: 4 }, { name: '党员账号', value: 5 }])
                        break;
                    case 2:
                        getStreetList(adminInfo.districtsCode)
                        setLevelList(() => [{ name: '区县账号', value: 2 }, { name: '街道账号', value: 3 }, { name: '社区账号', value: 4 }, { name: '党员账号', value: 5 }])
                        form.setFieldsValue({ districtsCode: adminInfo.districtsCode })
                        break;
                    case 3:
                        getStreetList(adminInfo.districtsCode)
                        getcommunityList(adminInfo.streetId)
                        setLevelList(() => [{ name: '街道账号', value: 3 }, { name: '社区账号', value: 4 }, { name: '党员账号', value: 5 }])
                        form.setFieldsValue({ districtsCode: adminInfo.districtsCode })
                        form.setFieldsValue({ streetId: adminInfo.streetId })
                        break;
                    case 4:
                        getStreetList(adminInfo.districtsCode)
                        getcommunityList(adminInfo.streetId)
                        setLevelList(() => [{ name: '社区账号', value: 4 }, { name: '党员账号', value: 5 }])
                        // this.$nextTick(() => {
                        form.setFieldsValue({ districtsCode: adminInfo.districtsCode })
                        form.setFieldsValue({ streetId: adminInfo.streetId })
                        form.setFieldsValue({ communityId: adminInfo.communityId * 1 })
                        // })
                        break;
                    case 5:
                        getStreetList(adminInfo.districtsCode)
                        getcommunityList(adminInfo.streetId)
                        setLevelList(() => [{ name: '党员账号', value: 5 }])
                        form.setFieldsValue({ districtsCode: adminInfo.districtsCode })
                        form.setFieldsValue({ streetId: adminInfo.streetId })
                        form.setFieldsValue({ communityId: adminInfo.communityId * 1 })
                        break;
                    default:
                        break;
                }
                if (data.id) {
                    getInfo(data.id)
@@ -126,6 +84,48 @@
    // 保存
    const okHandle = () => {
        form.validateFields().then((values) => {
            // 校验accountLevels必填
            if (!accountLevels.length || accountLevels.some(item => !item.area || item.area.length === 0)) {
                if (window?.antd?.message?.error) {
                    window.antd.message.error('请完整选择账号所属层级区域!');
                } else {
                    alert('请完整选择账号所属层级区域!');
                }
                return;
            }
            // 打印提交时的accountLevels数组
            console.log('提交时的accountLevels:', accountLevels);
            // 组装 systemUserLevels,严格按照接口字段,优先用原始字段
            const systemUserLevels = accountLevels.map(item => {
                // Get the first selected option's id as the level
                const firstSelectedOption = item.selectedOptions?.[0];
                const level = firstSelectedOption?.id || item.level || '';
                // 判断不同级别(假设市级别id为1,区县级别id为2,街道级别id为3,社区级别id为4)
                const isCityLevel = level === '1';
                const isDistrictLevel = level === '2';
                const isStreetLevel = level === '3';
                const isCommunityLevel = level === '4';
                return {
                    // 市级别:所有下级字段为空
                    // 区县级别:街道和社区字段为空
                    // 街道级别:社区字段为空
                    // 社区级别:不做处理
                    community: (isCityLevel || isDistrictLevel || isStreetLevel) ? '' : (item.community || item.selectedOptions?.[3]?.name || ''),
                    communityId: (isCityLevel || isDistrictLevel || isStreetLevel) ? '' : (item.communityId || item.selectedOptions?.[3]?.id || ''),
                    districts: isCityLevel ? '' : (item.districts || item.selectedOptions?.[1]?.name || ''),
                    districtsCode: isCityLevel ? '' : (item.districtsCode || item.selectedOptions?.[1]?.id || ''),
                    id: item.id || '', // 编辑时可用
                    level: level, // Use the determined level value
                    status: 1,
                    street: (isCityLevel || isDistrictLevel) ? '' : (item.street || item.selectedOptions?.[2]?.name || ''),
                    streetId: (isCityLevel || isDistrictLevel) ? '' : (item.streetId || item.selectedOptions?.[2]?.id || ''),
                    superviseFlag: typeof item.isDiscipline === 'number' ? item.isDiscipline : (item.isDiscipline ? 1 : 0),
                    // systemUserId: 可选,如有需要补充
                };
            });
            values.systemUserLevels = systemUserLevels;
            if (values.password) {
                values.password = CryptoJS.MD5(values.password).toString();
            } else {
@@ -142,54 +142,73 @@
                onUpdate(values)
            } else {
                onSave(values);
                setAccountLevels([{ area: [], isDiscipline: 0, options: [] }]); // 清空
            }
        });
    };
    const getCountyList = (id) => {
        getCityList({ id: '510400', tier: 2 }).then(res => {
            setCountyList(() => res.data)
        })
    }
    const getStreetList = (id) => {
        getCityList({ id: id, tier: 3 }).then(res => {
            setStreetList(() => res.data)
        })
    }
    const getcommunityList = (id) => {
        getCityList({ id: id, tier: 4 }).then(res => {
            setCommunityList(() => res.data)
        })
    }
    const changeCountry = (value, label) => {
        setActiveCounty(label)
        getStreetList(value)
        form.setFieldsValue({ streetId: '', communityId: '' })
        setActiveCommunity({ name: '', id: '' })
        setActiveStreet({ name: '', id: '' })
    // 获取级联数据
    const getCascaderDatas = () => {
        getCascaderData().then(res => {
            // 处理级联数据
            const processedData = res.data.map(item => {
                // 根据第一级类型处理数据
                if (item.children) {
                    // 区县级别
                    if (item.id == 2) { // 假设type=2表示区县
                        // 只保留第一级children
                        item.children = item.children.map(child => {
                            const { children, ...childWithoutChildren } = child;
                            return childWithoutChildren;
                        });
                    }
                    // 街道级别
                    else if (item.id == 3) { // 假设type=3表示街道
                        // 保留区县和街道两级
                        item.children = item.children.map(child => {
                            if (child.children) {
                                child.children = child.children.map(street => {
                                    const { children, ...streetWithoutChildren } = street;
                                    return streetWithoutChildren;
                                });
                            }
                            return child;
                        });
                    }
                    // 社区级别
                    else if (item.id == 4) { // 假设type=4表示社区
                        // 保留所有层级数据
                        return item;
                    }
                }
                return item;
            });
            let arr = JSON.parse(JSON.stringify(processedData))
            let accountLevels = localStorage.getItem('userInfo')
            let accountLevel = JSON.parse(accountLevels).accountLevel
            if (accountLevel == 2) {
                arr = arr.filter(item => item.id != 1)
            } else if (accountLevel == 3) {
                arr = arr.filter(item => item.id != 1 && item.id != 2)
            } else if (accountLevel == 4) {
                arr = arr.filter(item => item.id != 1 && item.id != 2 && item.id != 3)
            }
            setCascaderData(() => arr);
        });
    }
    const changeStreet = (value, label) => {
        setActiveStreet(label)
        getcommunityList(value)
        form.setFieldsValue({ communityId: '' })
        setActiveCommunity({ name: '', id: '' })
    }
    const changeCommunity = (value, label) => {
        setActiveCommunity(label)
    }
    const getInfo = (id) => {
        getSystemUserInfo(id).then(res => {
            if (res.data.districtsCode) {
                setActiveStreet({ name: res.data.districts, id: res.data.districtsCode })
                getStreetList(res.data.districtsCode)
            }
            if (res.data.streetId) {
                setActiveCounty({ name: res.data.street, id: res.data.streetId })
                getcommunityList(res.data.streetId)
            }
            if (res.data.communityId) {
                setActiveCommunity({ name: res.data.community, id: res.data.communityId })
            }
            // delete res.data.password
            let departmentId = []
@@ -206,8 +225,89 @@
                departmentId.push(res.data.fourDepartmentId)
            }
            res.data.DepartmentId = departmentId
            console.log('departmentId', departmentId)
            setActiveLevel(() => res.data.accountLevel)
            // 先获取级联数据再回显
            getCascaderData().then(cascadeRes => {
                // 处理级联数据
                const processedData = cascadeRes.data.map(item => {
                    // 根据第一级类型处理数据
                    if (item.children) {
                        // 区县级别
                        if (item.id == 2) { // 假设type=2表示区县
                            // 只保留第一级children
                            item.children = item.children.map(child => {
                                const { children, ...childWithoutChildren } = child;
                                return childWithoutChildren;
                            });
                        }
                        // 街道级别
                        else if (item.id == 3) { // 假设type=3表示街道
                            // 保留区县和街道两级
                            item.children = item.children.map(child => {
                                if (child.children) {
                                    child.children = child.children.map(street => {
                                        const { children, ...streetWithoutChildren } = street;
                                        return streetWithoutChildren;
                                    });
                                }
                                return child;
                            });
                        }
                        // 社区级别
                        else if (item.id == 4) { // 假设type=4表示社区
                            // 保留所有层级数据
                            return item;
                        }
                    }
                    return item;
                });
                let arr = JSON.parse(JSON.stringify(processedData))
                let accountLevels = localStorage.getItem('userInfo')
                let accountLevel = JSON.parse(accountLevels).accountLevel
                if (accountLevel == 2) {
                    arr = arr.filter(item => item.id != 1)
                } else if (accountLevel == 3) {
                    arr = arr.filter(item => item.id != 1 && item.id != 2)
                } else if (accountLevel == 4) {
                    arr = arr.filter(item => item.id != 1 && item.id != 2 && item.id != 3)
                }
                setCascaderData(() => arr);
                // 回显accountLevels,area始终为4级,id类型统一
                if (res.data.systemUserLevels && Array.isArray(res.data.systemUserLevels)) {
                    const newAccountLevels = res.data.systemUserLevels.map(level => {
                        const toId = v => (v === undefined || v === null) ? undefined : String(v);
                        const area = [
                            toId(level.level),
                            toId(level.twoLevelId || level.districtsCode),
                            toId(level.threeLevelId || level.streetId),
                            toId(level.fourLevelId || level.communityId)
                        ].filter(v => v !== undefined && v !== null && v !== '');
                        return {
                            area,
                            isDiscipline: Number(level.superviseFlag) === 1 ? 1 : 0,
                            options: [],
                            selectedOptions: [
                                level.level ? { id: toId(level.level), name: '' } : undefined,
                                level.twoLevelName ? { id: toId(level.twoLevelId), name: level.twoLevelName } : undefined,
                                level.threeLevelName ? { id: toId(level.threeLevelId), name: level.threeLevelName } : undefined,
                                level.fourLevelName ? { id: toId(level.fourLevelId), name: level.fourLevelName } : undefined,
                            ].filter(Boolean),
                            level: toId(level.level) || '',
                            community: level.community || '',
                            communityId: level.communityId || '',
                            districts: level.districts || '',
                            districtsCode: level.districtsCode || '',
                            id: level.id || '',
                            street: level.street || '',
                            streetId: level.streetId || '',
                        };
                    });
                    setAccountLevels(newAccountLevels.length ? newAccountLevels : [{ area: [], isDiscipline: 0, options: [], level: '', community: '', communityId: '', districts: '', districtsCode: '', id: '', street: '', streetId: '' }]);
                }
            });
            form.setFieldsValue({ ...res.data, password: '' })
        })
@@ -233,13 +333,32 @@
        return false
    }
    // 动态添加账号层级项
    const addAccountLevel = () => {
        setAccountLevels([...accountLevels, { area: [], isDiscipline: 0, options: [] }]);
    };
    // 删除账号层级项
    const removeAccountLevel = (idx) => {
        if (accountLevels.length === 1) return;
        setAccountLevels(accountLevels.filter((_, i) => i !== idx));
    };
    // 纪检委单选变更
    const handleDisciplineChange = (e, idx) => {
        setAccountLevels(levels => {
            const newLevels = [...levels];
            newLevels[idx].isDiscipline = e.target.value;
            return newLevels;
        });
    };
    return (
        <Modal
            getContainer={false}
            width="65%"
            width="75%"
            destroyOnClose
            title={data.type == 'detail' ? '人员详情' : data.type == 'edit' ? '编辑人员' : '添加人员'}
            open={visible}
            className='addAndEditModal'
            onCancel={() => onCancel(false)}
            afterClose={() => {
                form.resetFields()
@@ -256,8 +375,8 @@
                    <Button key="back" onClick={() => onCancel(false)}>关闭</Button>
            }
        >
            <Form layout="horizontal" {...formItemLayout} form={form} scrollToFirstError>
                <Row>
            <Form layout="horizontal" form={form} scrollToFirstError labelCol={{ span: 6 }} wrapperCol={{ span: 20 }}>
                <Row gutter={16}>
                    <Col span={8}>
                        <Form.Item
                            name="name"
@@ -267,90 +386,6 @@
                            <Input disabled={data.type == 'detail'} placeholder='请输入人员姓名' />
                        </Form.Item>
                    </Col>
                </Row>
                <Row>
                    <Col span={16}>
                        <Form.Item
                            name="DepartmentId"
                            label="所属单位"
                            labelCol={{ span: 4 }}
                            rules={[{ required: true, message: '请选择所属单位' }]}
                        >
                            {/* <Select
                                key="searchSelect"
                                allowClear
                                disabled={data.type == 'detail'}
                                placeholder="请选择"
                                options={oneCompanyList}
                                fieldNames={{ label: 'name', value: 'id' }}
                                filterOption={false}
                            >
                            </Select > */}
                            <Cascader
                                changeSelect
                                options={oneCompanyList}
                                fieldNames={{ value: 'key', label: 'name' }}
                                placeholder="请选择"
                                // displayRender={(label) => label[label.length - 1]}
                                changeOnSelect={true}
                            />
                        </Form.Item>
                    </Col>
                    {/* <Col span={8}>
                        <Form.Item
                            name="twoDepartmentId"
                            label="所属二级单位"
                        >
                            <Select
                                key="searchSelect"
                                allowClear
                                disabled={data.type == 'detail'}
                                placeholder="请选择"
                                options={twoCompanyList}
                                fieldNames={{ label: 'name', value: 'id' }}
                                filterOption={false}
                            >
                            </Select >
                        </Form.Item>
                    </Col>
                    <Col span={8}>
                        <Form.Item
                            name="threeDepartmentId"
                            label="所属三级单位"
                        >
                            <Select
                                key="searchSelect"
                                allowClear
                                disabled={data.type == 'detail'}
                                placeholder="请选择"
                                options={threeCompanyList}
                                fieldNames={{ label: 'name', value: 'id' }}
                                filterOption={false}
                            >
                            </Select >
                        </Form.Item>
                    </Col> */}
                </Row>
                {/* <Row>
                    <Col span={8}>
                        <Form.Item
                            name="fourDepartmentId"
                            label="所属四级单位"
                        >
                            <Select
                                key="searchSelect"
                                allowClear
                                disabled={data.type == 'detail'}
                                placeholder="请选择"
                                options={fourCompanyList}
                                fieldNames={{ label: 'name', value: 'id' }}
                                filterOption={false}
                            >
                            </Select >
                        </Form.Item>
                    </Col>
                </Row> */}
                <Row>
                    <Col span={8}>
                        <Form.Item
                            name="systemPostId"
@@ -358,197 +393,120 @@
                            rules={[{ required: true, message: '请选择所属职位' }]}
                        >
                            <Select
                                key="searchSelect"
                                allowClear
                                disabled={data.type == 'detail'}
                                placeholder="请选择"
                                options={positionList}
                                fieldNames={{ label: 'name', value: 'id' }}
                                filterOption={false}
                            >
                            </Select >
                            />
                        </Form.Item>
                    </Col>
                    <Col span={8}>
                        <Form.Item
                            name="systemRoleId"
                            label="所属角色"
                            rules={[{ required: true, message: '请选择所属角色' }]}
                            label="后台权限"
                            rules={[{ required: true, message: '请选择后台权限' }]}
                        >
                            <Select
                                key="searchSelect"
                                allowClear
                                disabled={data.type == 'detail'}
                                placeholder="请选择"
                                options={roleList}
                                fieldNames={{ label: 'name', value: 'id' }}
                                filterOption={false}
                            >
                            </Select >
                            />
                        </Form.Item>
                    </Col>
                </Row>
                <Row>
                <Row gutter={16}>
                    <Col span={24}>
                        <Form.Item
                            label="账号所属层级"
                            colon={true}
                            required
                            labelCol={{ span: 2 }}
                            wrapperCol={{ span: 12 }}
                            style={{ marginBottom: 0 }}
                        >
                            <Button type={'primary'} onClick={addAccountLevel} style={{ marginBottom: 8 }}>添加</Button>
                            {accountLevels.map((item, idx) => (
                                <Card key={idx} style={{ marginBottom: 8 }} size='small' >
                                    <div style={{ display: 'flex', alignItems: 'center' }}>
                                        <Cascader
                                            style={{ width: 320, marginRight: 16 }}
                                            placeholder="请选择区域"
                                            options={cascaderData}
                                            fieldNames={{ label: 'name', value: 'id' }}
                                            value={item.area}
                                            onChange={(value, selectedOptions) => {
                                                setAccountLevels(levels => {
                                                    const newLevels = [...levels];
                                                    newLevels[idx] = {
                                                        ...newLevels[idx],
                                                        area: value,
                                                        selectedOptions: selectedOptions,
                                                        // 清空原有数据,确保新选择的数据生效
                                                        community: '',
                                                        communityId: '',
                                                        districts: '',
                                                        districtsCode: '',
                                                        street: '',
                                                        streetId: '',
                                                        level: selectedOptions?.[0]?.id || ''
                                                    };
                                                    return newLevels;
                                                });
                                            }}
                                        />
                                        <div style={{ marginLeft: 16, marginTop: 22 }}>
                                            <span style={{ marginRight: 8 }}>是否为纪检委账号:</span>
                                            <Radio.Group
                                                style={{ marginRight: 16 }}
                                                value={item.isDiscipline ?? 0}
                                                onChange={e => handleDisciplineChange(e, idx)}
                                                options={[
                                                    { label: '否', value: 0 },
                                                    { label: '是', value: 1 }
                                                ]}
                                            />
                                        </div>
                                        {idx > 0 && <DeleteOutlined style={{ marginRight: 16 }} onClick={() => removeAccountLevel(idx)} disabled={accountLevels.length === 1} />}
                                    </div>
                                </Card>
                            ))}
                        </Form.Item>
                    </Col>
                </Row>
                <Row gutter={16}>
                    <Col span={8}>
                        <Form.Item
                            name="accountLevel"
                            label="账号层级"
                            rules={[{ required: true, message: '请选择账号层级' }]}
                        >
                            <Select
                                key="searchSelect"
                                allowClear
                                onChange={(e) => setActiveLevel(e)}
                                disabled={data.type == 'detail'}
                                placeholder="请选择"
                                options={levelList}
                                fieldNames={{ label: 'name', value: 'value' }}
                            // filterOption={false}
                            >
                            </Select >
                        </Form.Item>
                    </Col>
                    <Col span={16}>
                        <Form.Item
                            label="是否管理员"
                            labelCol={{ span: 4 }}
                            wrapperCol={{ span: 20 }}
                            style={{ marginBottom: 0 }}
                        >
                            <div style={{ display: "flex", flex: 1 }}>
                                <Form.Item
                                    name="isAdmin"
                                    rules={[{ required: true, message: '是否管理员' }]}
                                >
                                    <Radio.Group
                                        disabled={data.type == 'detail'}
                                        // style={style}
                                        // onChange={onChange}
                                        // value={value}
                                        options={[{ value: 0, label: '否', }, { value: 1, label: '是', },
                                        ]}
                                    />
                                </Form.Item>
                                <div style={{ fontSize: '12px', color: "rgba(0,0,0,0.5)", marginLeft: "10px" }}>管理员主要用于接收实现临期提醒,以及上级端登录</div>
                            </div>
                        </Form.Item>
                    </Col>
                </Row>
                {activeLevel != 1 && (
                    <Row>
                        {[2, 3, 4, 5].includes(activeLevel) && (
                            <Col span={8}>
                                <Form.Item
                                    name="districtsCode"
                                    label="所属区县"
                                    rules={[{ required: true, message: '请选择所属区县' }]}
                                >
                                    {/* <Select
                                // onChange={changeCountry}
                                // value={activeCounty}
                                placeholder="请选择"
                                options={levelList}
                                fieldNames={{ label: 'name', value: 'id' }}
                            >
                            </Select > */}
                                    <Select
                                        key="searchSelect"
                                        allowClear
                                        disabled={data.type == 'detail' || [2, 3, 4, 5].includes(adminLevel)}
                                        onChange={changeCountry}
                                        value={activeCounty.id}
                                        placeholder="请选择"
                                        options={countyList}
                                        fieldNames={{ label: 'name', value: 'id' }}
                                    // filterOption={false}
                                    >
                                    </Select >
                                </Form.Item>
                            </Col>
                        )}
                        {[3, 4, 5].includes(activeLevel) && (
                            <Col span={8}>
                                <Form.Item
                                    name="streetId"
                                    label="所属街道"
                                    rules={[{ required: true, message: '请选择所属街道' }]}
                                >
                                    <Select
                                        onChange={changeStreet}
                                        disabled={!activeCounty || data.type == 'detail' || [3, 4, 5].includes(adminLevel)}
                                        key="searchSelect"
                                        allowClear
                                        value={activeStreet.id}
                                        placeholder="请选择"
                                        options={streetList}
                                        fieldNames={{ label: 'name', value: 'id' }}
                                    >
                                    </Select >
                                </Form.Item>
                            </Col>
                        )}
                        {[4, 5].includes(activeLevel) && (
                            <Col span={8}>
                                <Form.Item
                                    name="communityId"
                                    label="所属社区"
                                    rules={[{ required: true, message: '请选择所属社区' }]}
                                >
                                    <Select
                                        onChange={changeCommunity}
                                        disabled={!activeStreet || data.type == 'detail' || [4, 5].includes(adminLevel)}
                                        key="searchSelect"
                                        allowClear
                                        value={activeCommunity.id}
                                        placeholder="请选择"
                                        options={communityList}
                                        fieldNames={{ label: 'name', value: 'id' }}
                                    >
                                    </Select >
                                </Form.Item>
                            </Col>
                        )}
                    </Row>
                )}
                <Row>
                    <Col span={16}>
                        <Form.Item
                            label="联系方式"
                            required
                            labelCol={{ span: 4 }}
                            wrapperCol={{ span: 20 }}
                            style={{ marginBottom: 0 }}
                            name="phone"
                            extra={'联系方式将作为登录账号使用'}
                            rules={[{
                                validator: (rule, value) => {
                                    return new Promise((resolve, reject) => {
                                        if (!value) {
                                            reject('请输入联系方式');
                                        }
                                        const phoneRegex = /^((\+86)?(13|14|15|16|17|18|19)[0-9]{9})|((\+86)?(0[0-9]{2,3})?([2-9][0-9]{6,7}))$/;
                                        if (!phoneRegex.test(value)) {
                                            reject('请输入正确的电话号码');
                                        }
                                        resolve('');
                                    });
                                },
                            }]}
                        >
                            <div style={{ display: "flex", flex: 1 }}>
                                <Form.Item
                                    name="phone"
                                    rules={[{
                                        validator: (rule, value) => {
                                            return new Promise((resolve, reject) => {
                                                if (!value) {
                                                    reject('请输入联系方式');
                                                }
                                                const phoneRegex = /^((\+86)?(13|14|15|16|17|18|19)[0-9]{9})|((\+86)?(0[0-9]{2,3})?([2-9][0-9]{6,7}))$/;
                                                if (!phoneRegex.test(value)) {
                                                    reject('请输入正确的电话号码');
                                                }
                                                resolve('');
                                            });
                                        },
                                    }]}
                                >
                                    <Input disabled={data.type == 'detail'} placeholder='请输入联系方式' />
                                </Form.Item>
                                <div style={{ fontSize: '12px', color: "rgba(0,0,0,0.5)", marginLeft: "10px" }}>联系方式将作为登录账号使用</div>
                            </div>
                            <Input disabled={data.type == 'detail'} placeholder='请输入联系方式' />
                        </Form.Item>
                    </Col>
                </Row>
                <Row>
                <Row gutter={16}>
                    <Col span={8}>
                        <Form.Item
                            required={data.type == 'add'}
@@ -572,39 +530,32 @@
                            <Input.Password disabled={data.type == 'detail'} placeholder='请输入' />
                        </Form.Item>
                    </Col>
                    <Col span={16}>
                </Row>
                <Row gutter={16}>
                    <Col span={8}>
                        <Form.Item
                            label="确认密码"
                            name="surePassword"
                            extra={'密码需要包含大小写字母,数字和特殊符号,且长度为8位以上'}
                            required={data.type == 'add'}
                            labelCol={{ span: 4 }}
                            wrapperCol={{ span: 20 }}
                            style={{ marginBottom: 0 }}
                            rules={[{
                                validator: (rule, value) => {
                                    return new Promise((resolve, reject) => {
                                        if (!value && data.type == 'add') {
                                            reject('请再次输入新密码');
                                        }
                                        if (value != form.getFieldValue('password') && data.type == 'add') {
                                            reject('两次密码请保持一致');
                                        }
                                        resolve('');
                                    });
                                },
                            }]}
                        >
                            <div style={{ display: "flex", flex: 1 }}>
                                <Form.Item
                                    name="surePassword"
                                    rules={[{
                                        validator: (rule, value) => {
                                            return new Promise((resolve, reject) => {
                                                if (!value && data.type == 'add') {
                                                    reject('请再次输入新密码');
                                                }
                                                if (value != form.getFieldValue('password') && data.type == 'add') {
                                                    reject('两次密码请保持一致');
                                                }
                                                resolve('');
                                            });
                                        },
                                    },]}
                                >
                                    <Input.Password disabled={data.type == 'detail'} placeholder='请输入' />
                                </Form.Item>
                                <div style={{ fontSize: '12px', color: "rgba(0,0,0,0.5)", marginLeft: "10px" }}>密码需要包含大小写字母,数字和特殊符号,且长度为8位以上</div>
                            </div>
                            <Input.Password disabled={data.type == 'detail'} placeholder='请输入' />
                        </Form.Item>
                    </Col>
                </Row>
            </Form>
            <Spin spinning={spinning} fullscreen />
management/src/pages/setting/user/index.jsx
@@ -1,7 +1,7 @@
import { buildProTableDataSource, sendRequest, showDelConfirm, showConfirm } from '@/utils/antdUtils';
import { DeleteOutlined, EditOutlined, PlusOutlined } from '@ant-design/icons';
import { PageContainer, ProFormText, ProTable, QueryFilter } from '@ant-design/pro-components';
import { Button, Cascader, Col, Menu, Row, Select, Space } from 'antd';
import { PageContainer, ProFormText, ProTable, QueryFilter, ProFormSelect } from '@ant-design/pro-components';
import { Button, Cascader, Col, Menu, Row, Select, Space, Form } from 'antd';
import { useEffect, useRef, useState } from 'react';
import { Access, useAccess } from 'umi';
import AddAndEdit from './components/addAndEdit';
@@ -21,6 +21,7 @@
} from './service';
const Role = () => {
  const [form] = Form.useForm();
  const actionRef = useRef();
  const addViewRef = useRef();
  const addViewRef1 = useRef();
@@ -48,7 +49,7 @@
        }}
      >
        <span>{item.name}</span>
        <div>
        {/* <div>
          {item.tier < 4 && (
            <Access accessible={access['/system_setting/unit_management/add']}>
              <PlusOutlined
@@ -82,31 +83,40 @@
              }}
            />
          </Access>
        </div>
        </div> */}
      </div >
    );
  };
  const renderMenuItems = (items) => {
  const renderMenuItems = (items, level = 0) => {
    return items.map((item) => {
      if (item.children && item.children.length > 0) {
        return (
          <SubMenu
            key={item.key}
            title={node(item)}
            onTitleClick={(item) => {
              setUnitId(item.key);
              actionRef.current.reload();
            onTitleClick={(e) => {
              if (e && e.domEvent) {
                e.domEvent.stopPropagation();
              }
              setUnitId(item.id);
              const values = form.getFieldsValue();
              actionRef.current.reload(values);
            }}
          >
            {renderMenuItems(item.children)}
            {renderMenuItems(item.children, level + 1)}
          </SubMenu>
        );
      }
      return (
        <Menu.Item
          onClick={(item) => {
            setUnitId(item.key);
            actionRef.current.reload();
          onClick={(e) => {
            if (e && e.domEvent) {
              e.domEvent.stopPropagation();
            }
            const itemId = item.id || item.key;
            setUnitId(itemId);
            const values = form.getFieldsValue();
            actionRef.current.reload(values);
          }}
          key={item.key}
        >
@@ -120,6 +130,8 @@
      if (res.code == 200 && res.data) {
        const traverseItems = (items) => {
          return items.map((item) => {
            if (!item.id) {
            }
            item.key = item.id;
            item.title = '1';
            if (item.child && item.child.length > 0) {
@@ -128,7 +140,8 @@
            return item;
          });
        };
        setItems(traverseItems(res.data));
        const processedItems = traverseItems(res.data);
        setItems(processedItems);
      }
    });
  };
@@ -141,50 +154,50 @@
      title: '联系方式',
      dataIndex: 'phone',
    },
    {
      title: '所在单位',
      dataIndex: 'departmentName',
      hideInSearch: true,
      renderFormItem: () => {
        return (
          <Cascader
            options={items}
            fieldNames={{ value: 'key', label: 'name' }}
            placeholder="请选择"
            displayRender={(label) => label[label.length - 1]}
            changeOnSelect={true}
          />
        );
      },
    },
    {
      hideInTable: true,
      title: '所在单位',
      dataIndex: 'departmentId',
      renderFormItem: () => {
        return (
          <Cascader
            options={items}
            fieldNames={{ value: 'key', label: 'name' }}
            placeholder="请选择"
            displayRender={(label) => label[label.length - 1]}
            changeOnSelect={true}
          />
        );
      },
    },
    // {
    //   title: '所在单位',
    //   dataIndex: 'departmentName',
    //   hideInSearch: true,
    //   renderFormItem: () => {
    //     return (
    //       <Cascader
    //         options={items}
    //         fieldNames={{ value: 'key', label: 'name' }}
    //         placeholder="请选择"
    //         displayRender={(label) => label[label.length - 1]}
    //         changeOnSelect={true}
    //       />
    //     );
    //   },
    // },
    // {
    //   hideInTable: true,
    //   title: '所属权限',
    //   dataIndex: 'departmentId',
    //   renderFormItem: () => {
    //     return (
    //       <Cascader
    //         options={items}
    //         fieldNames={{ value: 'key', label: 'name' }}
    //         placeholder="请选择"
    //         displayRender={(label) => label[label.length - 1]}
    //         changeOnSelect={true}
    //       />
    //     );
    //   },
    // },
    {
      title: '所属职位',
      dataIndex: 'systemPostName',
      hideInSearch: true,
    },
    {
      title: '所属角色',
      title: '后台权限',
      dataIndex: 'systemRoleName',
      hideInSearch: true,
    },
    {
      title: '所属角色',
      title: '所属权限',
      dataIndex: 'systemRoleId',
      hideInTable: true,
      renderFormItem: () => {
@@ -201,31 +214,24 @@
      },
    },
    {
      title: '账号层级',
      title: '账号所属层级',
      dataIndex: 'accountLevel',
      // (1=市级账号,2=区县账号,3=街道账号,4=社区账号)
      render: (text, record) => {
        let role = '';
        switch (record.accountLevel) {
          case 1:
            role = '市';
            break;
          case 2:
            role = '区县';
            break;
          case 3:
            role = '街道';
            break;
          case 4:
            role = '社区';
            break;
          case 5:
            role = '党员';
            break;
          default:
            role = '';
        }
        return role;
        record.list.length>0&&record.list.map(item=>{
          item.str = ''
          if(item.level==1){
           item.str = '市'
          }else if(item.level==2){
           item.str = '区县' + '/' + item.districts
          }else if(item.level==3){
            item.str = '街道' + '/' + item.districts + '/' + item.street
          }else{
            item.str = '社区' + '/' + item.districts + '/' + item.street + '/' + item.community
          }
          return item.str;
        })
        return record.list.map(item=>item.str).join('、');
      },
      valueEnum: {
        1: '市',
@@ -265,7 +271,7 @@
              <Access accessible={access['/system_setting/people_management/edit']}>
                <a
                  onClick={() => {
                    addViewRef.current.refreshData({ ...record, type: 'edit' },items);
                    addViewRef.current.refreshData({ ...record, type: 'edit' }, items);
                    handleModalVisibles(true);
                  }}
                >
@@ -292,7 +298,7 @@
            <Access accessible={access['/system_setting/people_management/detail']}>
              <a
                onClick={() => {
                  addViewRef.current.refreshData({ ...record, type: 'detail' },items);
                  addViewRef.current.refreshData({ ...record, type: 'detail' }, items);
                  handleModalVisibles(true);
                }}
              >
@@ -302,7 +308,7 @@
            <Access accessible={access['/system_setting/people_management/freeze']}>
              <a
                onClick={() => {
                  showConfirm(`确认${record.status === 1 ? '冻结' : '解冻'}该人员吗?`,'', async () => {
                  showConfirm(`确认${record.status === 1 ? '冻结' : '解冻'}该人员吗?`, '', async () => {
                    let status = await sendRequest(
                      record.status === 1 ? freezeApi : unfreezeApi,
                      record.id,
@@ -331,6 +337,8 @@
    >
      <div style={{ background: '#fff' }}>
        <QueryFilter
          form={form}
          labelWidth={100}
          onReset={(values) => {
            fetchUnit(values);
            setUnitId('');
@@ -342,14 +350,21 @@
            actionRef.current.reload();
          }}
        >
          <ProFormText name="name" label="单位名称" />
          <ProFormText name="name" label="组织结构名称" />
          <ProFormSelect name="type" label="筛选维度" options={[{
            label: '当前组织结构',
            value: 1,
          }, {
            label: '当前及下级组织结构',
            value: 2,
          }]} />
        </QueryFilter>
      </div>
      <Row style={{ marginTop: 20, background: '#fff' }}>
        <Col span={4}>
          <Space style={{ margin: '10px 0' }}>
            <span style={{ margin: '0 27px' }}>单位管理</span>
            <Button
            <span style={{ margin: '0 27px' }}>组织结构</span>
            {/* <Button
              type="primary"
              onClick={() => {
                addViewRef1.current.refreshData({});
@@ -357,10 +372,17 @@
              }}
            >
              添加
            </Button>
            </Button> */}
          </Space>
          <Menu mode="inline">{renderMenuItems(items)}</Menu>
          <Menu
            mode="inline"
            onClick={({ key, domEvent }) => {
              domEvent.stopPropagation();
            }}
          >
            {renderMenuItems(items)}
          </Menu>
        </Col>
        <Col span={20} style={{ minHeight: 650 }}>
          <ProTable
@@ -385,6 +407,9 @@
              if (params.departmentId) {
                obj.departmentId = params.departmentId[params.departmentId.length - 1];
              }
              if (form.getFieldValue('type')) {
                obj.type = form.getFieldValue('type');
              }
              return buildProTableDataSource(getList, obj);
            }}
            toolBarRender={(action, selectRows) => [
@@ -393,7 +418,7 @@
                  <Button
                    type="primary"
                    onClick={() => {
                      addViewRef.current.refreshData({ type: 'add', unitId : unitId  },items);
                      addViewRef.current.refreshData({ type: 'add', unitId: unitId }, items);
                      handleModalVisibles(true);
                    }}
                  >
management/src/pages/setting/user/index.less
New file
@@ -0,0 +1,5 @@
.addAndEditModal{
    .ant-modal-body{
        padding: 0 !important;
    }
}
management/src/pages/setting/user/service.js
@@ -134,4 +134,12 @@
    return request(`/api/huacheng-sangeshenbian/systemUser/unfreeze/${id}`, {
        method: 'PUT',
    });
}
}
//获取级联数据
export const getCascaderData = async (data) => {
    return request(`/api/huacheng-sangeshenbian/systemUser/getAdministrativeDivisionTwo`, {
        method: 'GET',
    });
}
management/src/pages/statistics/components/addAndEdit.jsx
New file
@@ -0,0 +1,72 @@
import { Form, Input, Modal,Button } from 'antd';
import { forwardRef, useImperativeHandle, useState } from 'react';
const formItemLayout = {
  labelCol: { span: 7 },
  wrapperCol: { span: 12 },
};
const AddEditView = ({ visible, onSave, onUpdate, onCancel }, ref) => {
  const [form] = Form.useForm();
  const [editData, setEditData] = useState({});
  const [rolesList, setRolesList] = useState();
  /**
   * 确定按钮事件
   */
  const okHandle = () => {
    form.validateFields().then((values) => {
      if (editData.id) {
        values.id = editData.id;
        onUpdate(values);
      } else {
        onSave(values);
      }
    });
  };
  useImperativeHandle(ref, () => {
    return {
      refreshData: (data) => {
        form.resetFields();
        form.setFieldsValue(data);
        setEditData(data);
      },
      clean: () => {
        form.resetFields();
      },
    };
  });
  return (
    <Modal
      getContainer={false}
      width="25%"
      destroyOnClose
      title={editData.id ? '编辑职位' : '添加职位'}
      open={visible}
      onCancel={() => onCancel(false)}
      footer={[
        <Button key="back" onClick={() => onCancel(false)}>
          关闭
        </Button>,
        <Button key="submit" type="primary" onClick={okHandle}>
          确认
        </Button>
      ]}
    >
      <Form layout="horizontal" {...formItemLayout} form={form} initialValues={{ isAuctioneer: 1 }}>
        <Form.Item
          name="name"
          required
          label="职位名称"
          rules={[{ required: true, message: '请输入职位名称' }]}
        >
          <Input placeholder="请输入职位名称" />
        </Form.Item>
      </Form>
    </Modal>
  );
};
export default forwardRef(AddEditView);
management/src/pages/statistics/index.jsx
New file
@@ -0,0 +1,558 @@
import { buildProTableDataSource, sendRequest, showDelConfirm } from '@/utils/antdUtils';
import { PageContainer } from '@ant-design/pro-components';
import { Button, InputNumber, Select, Space, Form, Card, Row, Col, Statistic, DatePicker } from 'antd';
import { useEffect, useState } from 'react';
import { ArrowUpOutlined, ArrowDownOutlined } from '@ant-design/icons';
import { Access, useAccess } from 'umi';
import AddAndEdit from './components/addAndEdit';
import { getCommunityList, getNextRegion, getStatisticsData } from './service';
import ReactECharts from 'echarts-for-react';
import moment from 'moment';
// 同比趋势组件
const Trend = ({ value }) => {
  if (value > 0) {
    return (
      <span style={{ color: 'red', marginLeft: 4 }}>
        <ArrowUpOutlined /> {Math.abs(value)}%
      </span>
    );
  }
  if (value < 0) {
    return (
      <span style={{ color: 'green', marginLeft: 4 }}>
        <ArrowDownOutlined /> {Math.abs(value)}%
      </span>
    );
  }
  return <span style={{ marginLeft: 4 }}>0%</span>;
};
const statistics = () => {
  const [adminLevel, setAdminLevel] = useState(JSON.parse(localStorage.getItem('userInfo')).accountLevel);
  const [form] = Form.useForm();
  const [districtOptions, setDistrictOptions] = useState([]);
  const [streetOptions, setStreetOptions] = useState([]);
  const [communityOptions, setCommunityOptions] = useState([]);
  const [dateRange, setDateRange] = useState([]);
  const [loading, setLoading] = useState(false);
  const [rankType, setRankType] = useState('top5');
  // 统计数据
  const [stats, setStats] = useState({
    total: 0,
    month: 0,
    totalMonthRate: 0,
    processing: 0,
    reviewing: 0,
    delayed: 0,
    finished: 0,
    overtime: 0,
    overtimeMonth: 0,
    overtimeMonthRate: 0,
    avgTime: 0,
    avgTimeMonth: 0,
    avgTimeRate: 0,
    satisfaction: 0,
    satisfactionMonth: 0,
    satisfactionRate: 0,
  });
  // 图表数据
  const [chartData, setChartData] = useState([]);
  const [allTypeData, setAllTypeData] = useState([]);
  const [pieData, setPieData] = useState([]);
  // 诉求单量统计图表配置
  const echartsOption = {
    tooltip: {
      trigger: 'axis',
    },
    legend: {
      data: ['诉求单量', '诉求办结数'],
    },
    grid: {
      left: '3%',
      right: '3%',
      bottom: '10%',
      containLabel: true,
    },
    dataZoom: [
      {
        type: 'slider',
        show: true,
        startValue: 0,
        endValue: 10,
        height: 12,
        bottom: 0,
        showDetail: false,
        showDataShadow: false,
        fillerColor: '#dbdee5',
        borderColor: 'transparent',
        zoomLock: true,
        brushSelect: false,
        handleStyle: {
          opacity: 0
        }
      },
      {
        type: "inside",
        zoomOnMouseWheel: false,
        moveOnMouseMove: true,
        moveOnMouseWheel: true,
      },
    ],
    xAxis: {
      type: 'category',
      data: chartData.map(item => item.date),
      axisLabel: {
        interval: 'auto',
      }
    },
    yAxis: [
      {
        type: 'value',
        name: '诉求单量',
        min: 0,
      },
      {
        type: 'value',
        name: '诉求办结数',
        min: 0,
      },
    ],
    series: [
      {
        name: '诉求单量',
        type: 'bar',
        data: chartData.map(item => item.count),
        yAxisIndex: 0,
        itemStyle: { color: '#3b7cff' },
        barWidth: 30,
      },
      {
        name: '诉求办结数',
        type: 'line',
        data: chartData.map(item => item.finish),
        yAxisIndex: 1,
        itemStyle: { color: '#3bcfcf' },
        lineStyle: { width: 2 },
        smooth: true,
      },
    ],
  };
  // 问题类型排名图表配置
  const typeRankOption = {
    tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
    grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
    dataZoom: [
      {
        type: 'slider',
        show: true,
        startValue: 0,
        endValue: 4,
        bottom: 0,
        showDetail: false,
        showDataShadow: false,
        height: '100%',
        width: 10,
        fillerColor: '#dbdee5',
        borderColor: 'transparent',
        zoomLock: true,
        orient: 'vertical',
        brushSelect: false,
        handleStyle: {
          opacity: 0
        }
      },
      {
        type: "inside",
        zoomOnMouseWheel: false,
        moveOnMouseMove: true,
        moveOnMouseWheel: true,
        orient: 'vertical'
      },
    ],
    xAxis: { type: 'value', boundaryGap: [0, 0.01] },
    yAxis: { type: 'category', data: allTypeData.map(item => item.name) },
    series: [
      {
        name: '数量',
        type: 'bar',
        data: allTypeData.map(item => item.value),
        itemStyle: { color: '#4a90e2' },
        barWidth: 20,
        label: { show: true, position: 'right' },
      },
    ],
  };
  // 问题评价占比图表配置
  const pieOption = {
    tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' },
    legend: { orient: 'vertical', left: 'right', data: pieData.map(i => i.name) },
    color: ['#4a90e2', '#6dd400', '#f5a623', '#f44336'],
    series: [
      {
        name: '评价占比',
        type: 'pie',
        radius: ['50%', '70%'],
        avoidLabelOverlap: false,
        label: { show: false, position: 'center' },
        emphasis: { label: { show: true, fontSize: 18, fontWeight: 'bold' } },
        labelLine: { show: false },
        data: pieData,
      },
    ],
  };
  // 处理表单和rank筛选,组装参数并请求接口
  const handleSearch = async (values) => {
    setLoading(true);
    let time = '';
    if (dateRange && dateRange.length === 2) {
      time = `${dateRange[0].format('YYYY-MM-DD')} - ${dateRange[1].format('YYYY-MM-DD')}`;
    }
    const params = {
      cityCode: values.cityCode,
      districtCode: values.districtId,
      streetId: values.streetId,
      communityId: values.communityId,
      time,
      rank: rankType === 'all' ? undefined : rankType.replace('top', ''),
    };
    try {
      const res = await getStatisticsData(params);
      const data = res.data.analyticStatisticsOneVo;
      setStats({
        total: data.allTotal,
        month: data.thisMonthTotal,
        totalMonthRate: data.lastMonthCompareTotal,
        processing: data.nowTransactTotal,
        reviewing: data.auditTransactTotal,
        delayed: data.postponeTransactTotal,
        finished: data.completeTransactTotal,
        overtime: data.overtimeTransactTotal,
        overtimeMonth: data.thisMonthOvertimeTransactTotal,
        overtimeMonthRate: data.lastMonthOvertimeTransactCompareTotal,
        avgTime: data.averageTime,
        avgTimeMonth: data.thisMonthAverageTime,
        avgTimeRate: data.lastMonthCompareAverageTime,
        satisfaction: data.satisfactionRate,
        satisfactionMonth: data.thisMonthSatisfactionRate,
        satisfactionRate: data.lastMonthCompareSatisfactionRate,
      });
      setChartData((res.data.analyticStatisticsTwoVos || []).map(item => ({
        date: item.time,
        count: item.allTotal,
        finish: item.completeTotal,
      })));
      setAllTypeData((res.data.analyticStatisticsThreeVos || []).map(item => ({
        name: item.name,
        value: item.allTotal,
      })));
      const fourVo = res.data.analyticStatisticsFourVo || {};
      setPieData([
        { value: fourVo.greatSatisfactionRate, name: '非常满意' },
        { value: fourVo.satisfactionRate, name: '满意' },
        { value: fourVo.generalSatisfactionRate, name: '一般' },
        { value: fourVo.dissatisfactionRate, name: '不满意' },
      ]);
    } catch (e) {
      // 错误处理
    }
    setLoading(false);
  };
  // rankType变化时自动触发表单查询
  useEffect(() => {
    form.submit();
  }, [rankType]);
  useEffect(() => {
    getCommunityList().then(res => {
      const { bcRegions, comActs, comStreets } = res.data;
      if (adminLevel <= 2) {
        setDistrictOptions(bcRegions);
        return;
      }
      if (adminLevel <= 3) {
        setStreetOptions(comStreets);
        return;
      }
      setCommunityOptions(comActs);
    });
  }, [adminLevel]);
  // 处理区县选择
  const handleDistrictChange = async (value) => {
    if (!value) {
      setStreetOptions([]);
      setCommunityOptions([]);
      form.setFieldsValue({ streetId: undefined, communityId: undefined });
      return;
    }
    const res = await getNextRegion({ id: value, type: 1 });
    setStreetOptions(res.data || []);
    setCommunityOptions([]);
    form.setFieldsValue({ streetId: undefined, communityId: undefined });
  };
  // 处理街道选择
  const handleStreetChange = async (value) => {
    if (!value) {
      setCommunityOptions([]);
      form.setFieldsValue({ communityId: undefined });
      return;
    }
    const res = await getNextRegion({ id: value, type: 2 });
    setCommunityOptions(res.data || []);
    form.setFieldsValue({ communityId: undefined });
  };
  // 根据管理员级别获取可用的筛选项
  const getFilterItems = () => {
    const baseItems = [
      {
        name: 'communityId',
        label: '社区',
        component: <Select
          style={{ width: 200 }}
          options={communityOptions}
          placeholder="请选择社区"
          fieldNames={{ label: 'name', value: 'communityId' }}
          disabled={!form.getFieldValue('streetId')}
        />,
      },
    ];
    if (adminLevel <= 3) {
      baseItems.unshift({
        name: 'streetId',
        label: '街道',
        component: <Select
          style={{ width: 200 }}
          options={streetOptions}
          placeholder="请选择街道"
          fieldNames={{ label: 'name', value: 'streetId' }}
          onChange={handleStreetChange}
          disabled={!form.getFieldValue('districtId')}
        />,
      });
    }
    if (adminLevel <= 2) {
      baseItems.unshift({
        name: 'districtId',
        label: '区县',
        component: <Select
          style={{ width: 200 }}
          options={districtOptions}
          placeholder="请选择区县"
          fieldNames={{ label: 'regionName', value: 'regionCode' }}
          onChange={handleDistrictChange}
        />,
      });
    }
    return baseItems;
  };
  return (
    <div>
      <PageContainer header={{
        breadcrumb: {},
      }}
      title={'统计分析'}
      >
        {/* 筛选表单 */}
        <Card style={{ marginBottom: 24 }}>
          <Form
            form={form}
            layout="inline"
            style={{ marginBottom: 24 }}
            onFinish={handleSearch}
          >
            {getFilterItems().map((item) => (
              <Form.Item
                key={item.name}
                name={item.name}
                label={item.label}
              >
                {item.component}
              </Form.Item>
            ))}
            <Form.Item>
              <Space>
                <Button type="primary" onClick={() => form.submit()}>
                  查询
                </Button>
                <Button onClick={() => {
                  form.resetFields();
                  setStreetOptions([]);
                  setCommunityOptions([]);
                }}>
                  重置
                </Button>
              </Space>
            </Form.Item>
          </Form>
        </Card>
        {/* 标题 */}
        <div style={{ fontWeight: 600, fontSize: 18, marginBottom: 16, borderLeft: '4px solid #3b7cff', paddingLeft: 8 }}>
          诉求单量统计
        </div>
        {/* 统计卡片区 */}
        <Row gutter={[16, 16]} wrap={false}>
          <Col flex="1">
            <Card style={{ height: 120, display: 'flex', flexDirection: 'column', justifyContent: 'center' }}>
              <Statistic title="诉求单量总计" value={stats.total} />
              <div>
                本月 {stats.month}
                <span style={{ marginLeft: 8 }}>
                  同比上月
                  <Trend value={stats.totalMonthRate} />
                </span>
              </div>
            </Card>
          </Col>
          <Col flex="0 0 180px">
            <Card style={{ height: 120, display: 'flex', flexDirection: 'column', justifyContent: 'center' }}>
              <Statistic title="正在办理" value={stats.processing} />
            </Card>
          </Col>
          <Col flex="0 0 180px">
            <Card style={{ height: 120, display: 'flex', flexDirection: 'column', justifyContent: 'center' }}>
              <Statistic title="审核中" value={stats.reviewing} />
            </Card>
          </Col>
          <Col flex="0 0 180px">
            <Card style={{ height: 120, display: 'flex', flexDirection: 'column', justifyContent: 'center' }}>
              <Statistic title="延期办理" value={stats.delayed} />
            </Card>
          </Col>
          <Col flex="0 0 180px">
            <Card style={{ height: 120, display: 'flex', flexDirection: 'column', justifyContent: 'center' }}>
              <Statistic title="已办结" value={stats.finished} />
            </Card>
          </Col>
          <Col flex="1">
            <Card style={{ height: 120, display: 'flex', flexDirection: 'column', justifyContent: 'center' }}>
              <Statistic title="超时办理" value={stats.overtime} />
              <div>
                本月 {stats.overtimeMonth}
                <span style={{ marginLeft: 8 }}>
                  同比上月
                  <Trend value={stats.overtimeMonthRate} />
                </span>
              </div>
            </Card>
          </Col>
        </Row>
        <Row gutter={[16, 16]} style={{ marginTop: 16 }}>
          <Col>
            <Card>
              <Statistic title="平均处理时间" value={stats.avgTime + '天'} />
              <div>
                本月 {stats.avgTimeMonth}天
                <span style={{ marginLeft: 8 }}>
                  同比上月
                  <Trend value={stats.avgTimeRate} />
                </span>
              </div>
            </Card>
          </Col>
          <Col>
            <Card>
              <Statistic title="总体满意率" value={stats.satisfaction + '%'} />
              <div>
                本月 {stats.satisfactionMonth}%
                <span style={{ marginLeft: 8 }}>
                  同比上月
                  <Trend value={stats.satisfactionRate} />
                </span>
              </div>
            </Card>
          </Col>
        </Row>
        {/* 时间筛选区 */}
        <div style={{ margin: '24px 0' }}>
          <Space>
            <DatePicker.RangePicker
              value={dateRange}
              onChange={(dates) => {
                setDateRange(dates);
                form.setFieldsValue({ time: dates });
                form.submit();
              }}
            />
            <Button onClick={() => {
              const end = moment();
              const start = moment().subtract(7, 'days');
              const dates = [start, end];
              setDateRange(dates);
              form.setFieldsValue({ time: dates });
              form.submit();
            }}>近7天</Button>
            <Button onClick={() => {
              const end = moment();
              const start = moment().subtract(30, 'days');
              const dates = [start, end];
              setDateRange(dates);
              form.setFieldsValue({ time: dates });
              form.submit();
            }}>近30天</Button>
          </Space>
        </div>
        {/* 图表区 */}
        <div style={{ background: '#fff', padding: 24 }}>
          <div style={{ width: '100%', overflowX: 'auto' }}>
            <div style={{ minWidth: '100%', height: 300 }}>
              <ReactECharts
                option={echartsOption}
                style={{ height: '100%', width: '100%' }}
                opts={{ renderer: 'svg' }}
              />
            </div>
          </div>
        </div>
        {/* 问题类型排名 */}
        <div style={{ background: '#fff', padding: 24, marginTop: 32 }}>
          <div style={{ fontWeight: 600, fontSize: 16, marginBottom: 16, borderLeft: '4px solid #3b7cff', paddingLeft: 8 }}>
            问题类型排名
            <Select
              style={{ width: 120, marginLeft: 16 }}
              value={rankType}
              onChange={setRankType}
              options={[
                { label: '排名前5', value: 'top5' },
                { label: '排名前10', value: 'top10' },
                { label: '全部排名', value: 'all' },
              ]}
            />
          </div>
          <div style={{ maxHeight: 320, overflowY: 'auto' }}>
            <ReactECharts option={typeRankOption} style={{ height: Math.max(60 * allTypeData.length, 300) }} />
          </div>
        </div>
        {/* 问题评价占比 */}
        <div style={{ background: '#fff', padding: 24, marginTop: 32 }}>
          <div style={{ fontWeight: 600, fontSize: 16, marginBottom: 16, borderLeft: '4px solid #3b7cff', paddingLeft: 8 }}>
            问题评价占比
          </div>
          <ReactECharts option={pieOption} style={{ height: 300 }} />
        </div>
      </PageContainer>
    </div>
  );
};
export default statistics;
management/src/pages/statistics/service.js
New file
@@ -0,0 +1,23 @@
import { request } from '@umijs/max';
// 获取社区列表
export const getCommunityList = async (params) => {
    return request('/api/huacheng-sangeshenbian/analytic-statistics/getRegion', {
        method: 'GET',
        params
    });
}
// 获取下一级区域列表
export const getNextRegion = async (params) => {
    return request('/api/huacheng-sangeshenbian/analytic-statistics/getNextRegion', {
        method: 'GET',
        params
    });
}
// 获取统计数据
export const getStatisticsData = async (data) => {
    return request('/api/huacheng-sangeshenbian/analytic-statistics/data', {
        method: 'post',
        data
    });
}
management/src/requestErrorConfig.ts
@@ -26,7 +26,7 @@
 * @doc https://umijs.org/docs/max/request#配置
 */
export const errorConfig: RequestConfig = {
  baseURL: BASE_URL,
  // baseURL: BASE_URL,
  // 请求拦截器
  requestInterceptors: [