| | |
| | | trialNode.innerHTML = '试运行版本'; |
| | | document.body.appendChild(trialNode); |
| | | // #endif |
| | | |
| | | }, |
| | | onShow: function() { |
| | | |
| | |
| | | console.log('当前录音片段URL:', res.localUrl) |
| | | console.log('当前已存储的片段数:', this.recordSegments.length) |
| | | console.log('当前片段:', this.currentSegment) |
| | | |
| | | |
| | | // 只有当当前片段与上一个不同时才添加 |
| | | if (!this.currentSegment || this.currentSegment.url !== res.localUrl) { |
| | | console.log('添加新的录音片段') |
| | |
| | | try { |
| | | const audioContext = new (window.AudioContext || window.webkitAudioContext)(); |
| | | const audioBuffers = []; |
| | | |
| | | |
| | | // 加载所有音频文件 |
| | | for (const url of audioUrls) { |
| | | const response = await fetch(url); |
| | |
| | | |
| | | // 计算总时长 |
| | | const totalLength = audioBuffers.reduce((acc, buffer) => acc + buffer.length, 0); |
| | | |
| | | |
| | | // 创建新的音频缓冲区 |
| | | const mergedBuffer = audioContext.createBuffer( |
| | | audioBuffers[0].numberOfChannels, |
| | |
| | | // 将合并后的音频转换为 Blob |
| | | const wavBlob = await this.audioBufferToWav(mergedBuffer); |
| | | const mergedUrl = URL.createObjectURL(wavBlob); |
| | | |
| | | |
| | | return mergedUrl; |
| | | } catch (error) { |
| | | console.error('合并音频失败:', error); |
| | |
| | | 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() { |
| | |
| | | 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', |
| | | } |
| | |
| | | </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> |
| | |
| | | <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"> |
| | |
| | | }) |
| | | }, |
| | | methods: { |
| | | callPhone(phoneNumber) { |
| | | uni.makePhoneCall({ |
| | | phoneNumber |
| | | }); |
| | | }, |
| | | back() { |
| | | uni.navigateBack({ |
| | | delta: 2 |
| | |
| | | } |
| | | |
| | | .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 { |
| | |
| | | <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> |
| | |
| | | <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> |
| | |
| | | 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", |
| | |
| | | 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 |
| | | } |
| | | |
| | |
| | | <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> |
| | |
| | | </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> |
| | |
| | | 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) |
| | |
| | | } |
| | | }, |
| | | 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 => { |
| | |
| | | }, |
| | | 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 || [] |
| | |
| | | }, |
| | | 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) { |
| | |
| | | |
| | | <!-- 上级显示 --> |
| | | <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> |
| | |
| | | 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', |
| | |
| | | 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: { |
| | |
| | | ], |
| | | }, |
| | | { |
| | | name: '统计分析', |
| | | path: '/statistics', |
| | | component: './statistics/index', |
| | | // access: '/system_setting/position_management', |
| | | }, |
| | | { |
| | | path: '/setting', |
| | | // layout: false, |
| | | name: '系统设置', |
| | |
| | | access: '/system_setting/position_management', |
| | | }, |
| | | { |
| | | name: '角色管理', |
| | | name: '权限管理', |
| | | path: '/setting/role', |
| | | component: './setting/role', |
| | | access: '/system_setting/role_management', |
| | |
| | | "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", |
| | |
| | | "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", |
| | |
| | | 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(); |
| | |
| | | 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) => { |
| | |
| | | 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="述求详情" > |
| | |
| | | <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>} |
| | |
| | | </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> |
| | | ); |
| | |
| | | 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 |
| | | }); |
| | | } |
| | |
| | | } |
| | | }, |
| | | { |
| | | title: '创建时间', |
| | | dataIndex: 'createTime', |
| | | sorter: true, |
| | | hideInSearch: true, |
| | | }, |
| | | { |
| | | title: '更新时间', |
| | | dataIndex: 'updateTime', |
| | | sorter: true, |
| | | hideInSearch: true, |
| | | }, |
| | | { |
| | | title: '状态', |
| | | dataIndex: 'freezeStatus', |
| | | valueEnum: { |
| | |
| | | } |
| | | }, |
| | | { |
| | | 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 > |
| | |
| | | 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) |
| | |
| | | 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> |
| | | ]} |
| | |
| | | getContainer={false} |
| | | width="20%" |
| | | destroyOnClose |
| | | title={detailType ? '角色详情' : data.id ? '编辑角色' : '添加角色'} |
| | | title={detailType ? '权限详情' : data.id ? '编辑权限' : '添加权限'} |
| | | open={visible} |
| | | onCancel={() => onCancel(false)} |
| | | afterClose={() => { |
| | |
| | | <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" |
| | |
| | | |
| | | const columns = [ |
| | | { |
| | | title: '角色名称', |
| | | title: '权限名称', |
| | | dataIndex: 'name', |
| | | }, |
| | | { |
| | |
| | | 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 }, |
| | |
| | | //所属角色 |
| | | 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('') |
| | | //所属区县 |
| | |
| | | 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(() => { |
| | | // 获取单位 |
| | |
| | | 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) |
| | |
| | | // 保存 |
| | | 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 { |
| | |
| | | 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 = [] |
| | |
| | | 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: '' }) |
| | | }) |
| | |
| | | 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() |
| | |
| | | <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" |
| | |
| | | <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" |
| | |
| | | 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'} |
| | |
| | | <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 /> |
| | |
| | | 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'; |
| | |
| | | } from './service'; |
| | | |
| | | const Role = () => { |
| | | const [form] = Form.useForm(); |
| | | const actionRef = useRef(); |
| | | const addViewRef = useRef(); |
| | | const addViewRef1 = useRef(); |
| | |
| | | }} |
| | | > |
| | | <span>{item.name}</span> |
| | | <div> |
| | | {/* <div> |
| | | {item.tier < 4 && ( |
| | | <Access accessible={access['/system_setting/unit_management/add']}> |
| | | <PlusOutlined |
| | |
| | | }} |
| | | /> |
| | | </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} |
| | | > |
| | |
| | | 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) { |
| | |
| | | return item; |
| | | }); |
| | | }; |
| | | setItems(traverseItems(res.data)); |
| | | const processedItems = traverseItems(res.data); |
| | | setItems(processedItems); |
| | | } |
| | | }); |
| | | }; |
| | |
| | | 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: () => { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | 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: '市', |
| | |
| | | <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); |
| | | }} |
| | | > |
| | |
| | | <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); |
| | | }} |
| | | > |
| | |
| | | <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, |
| | |
| | | > |
| | | <div style={{ background: '#fff' }}> |
| | | <QueryFilter |
| | | form={form} |
| | | labelWidth={100} |
| | | onReset={(values) => { |
| | | fetchUnit(values); |
| | | setUnitId(''); |
| | |
| | | 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({}); |
| | |
| | | }} |
| | | > |
| | | 添加 |
| | | </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 |
| | |
| | | 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) => [ |
| | |
| | | <Button |
| | | type="primary" |
| | | onClick={() => { |
| | | addViewRef.current.refreshData({ type: 'add', unitId : unitId },items); |
| | | addViewRef.current.refreshData({ type: 'add', unitId: unitId }, items); |
| | | handleModalVisibles(true); |
| | | }} |
| | | > |
New file |
| | |
| | | .addAndEditModal{ |
| | | .ant-modal-body{ |
| | | padding: 0 !important; |
| | | } |
| | | } |
| | |
| | | return request(`/api/huacheng-sangeshenbian/systemUser/unfreeze/${id}`, { |
| | | method: 'PUT', |
| | | }); |
| | | } |
| | | } |
| | | |
| | | |
| | | //获取级联数据 |
| | | export const getCascaderData = async (data) => { |
| | | return request(`/api/huacheng-sangeshenbian/systemUser/getAdministrativeDivisionTwo`, { |
| | | method: 'GET', |
| | | }); |
| | | } |
New file |
| | |
| | | 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); |
New file |
| | |
| | | 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; |
New file |
| | |
| | | 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 |
| | | }); |
| | | } |
| | |
| | | * @doc https://umijs.org/docs/max/request#配置 |
| | | */ |
| | | export const errorConfig: RequestConfig = { |
| | | baseURL: BASE_URL, |
| | | // baseURL: BASE_URL, |
| | | |
| | | // 请求拦截器 |
| | | requestInterceptors: [ |