From 20e32587ecbab27e4436f2f64a15faa3c89e4f41 Mon Sep 17 00:00:00 2001 From: pyt <626651354@qq.com> Date: 星期二, 20 五月 2025 21:39:59 +0800 Subject: [PATCH] Merge branch 'master' of http://120.76.84.145:10101/gitblit/r/H5/threeSide --- management/src/pages/setting/user/index.jsx | 181 ++- H5/uni_modules/mumu-recorder/package.json | 87 ++ management/src/pages/appeal-management/service.js | 16 H5/uni_modules/mumu-recorder/readme.md | 117 ++ H5/components/voiceInputPopup.vue | 368 ++++++++ management/src/pages/setting/role/components/addAndEdit.jsx | 8 management/src/pages/logManagement/service.js | 15 management/src/requestErrorConfig.ts | 2 management/src/pages/setting/user/index.less | 5 management/src/pages/appeal-management/statistics/service.js | 15 H5/pages/supervision/service.js | 22 management/src/pages/setting/role/index.jsx | 2 H5/uni_modules/mumu-recorder/components/mumu-recorder/mumu-recorder.vue | 113 ++ management/src/pages/appeal-management/statistics/index.jsx | 42 H5/pages/supervision/supervision.vue | 66 management/src/pages/party/manage/index.jsx | 188 ++- H5/package.json | 5 H5/pages/supervision/supervision-progress.vue | 24 H5/pages/work-detail/work-detail.vue | 20 H5/utils/request.js | 6 management/src/pages/logManagement/index.jsx | 66 management/src/pages/setting/user/components/addAndEdit.jsx | 699 +++++++-------- management/src/pages/setting/user/service.js | 10 H5/pages/add-progress/index.vue | 2 H5/uni_modules/mumu-recorder/changelog.md | 4 management/src/pages/appeal-management/detail.jsx | 235 +++++ H5/manifest.json | 171 ++- H5/pages/supervision/edit-supervision-progress.vue | 13 management/config/routes.ts | 2 H5/pages/Appeal/Appeal.vue | 72 + 30 files changed, 1,774 insertions(+), 802 deletions(-) diff --git a/H5/components/voiceInputPopup.vue b/H5/components/voiceInputPopup.vue index 4737331..b0393ee 100644 --- a/H5/components/voiceInputPopup.vue +++ b/H5/components/voiceInputPopup.vue @@ -1,25 +1,32 @@ <template> - <u-popup :show="show" mode="bottom" :closeOnClickOverlay="false" @close="closePopup" zIndex="10071"> + <u-popup :show="show" mode="bottom" :closeOnClickOverlay="false" @close="closePopup" :zIndex="9999" :overlay="true" + :safeAreaInsetBottom="true"> <view class="voice-popup"> <view class="header"> <text class="pl-30">语音输入</text> <image src="/static/Appeal/close.png" class="w-34 h-34 mr-30" mode="" @click="closePopup"></image> </view> <view class="record-anim"> - <image src="/static/Appeal/step.png" class="w-153 h-119" mode="" @click="onPlay"></image> + <image src="/static/Appeal/step.png" class="w-153 h-119" mode=""></image> </view> <view class="timer">{{ time }}</view> <view class="btns"> - <image src="/static/Appeal/start.png" class="w-153 h-153 mr-14" mode="" @click="onPlay"></image> - <image src="/static/Appeal/stop.png" class="w-153 h-153 mr-14" mode="" @click="onPause"></image> + <image :src="getRecordButtonSrc" class="w-153 h-153 mr-14" mode="" @click="handlerOnCahnger"></image> <image src="/static/Appeal/cancel.png" class="w-153 h-153 mr-14" mode="" @click="onStop"></image> </view> </view> + <mumu-recorder ref="mumuRecorder" @success="handlerSuccess" @error="handleError"></mumu-recorder> </u-popup> </template> <script> +import { getSignature } from '../pages/index/service' +import MumuRecorder from '@/uni_modules/mumu-recorder/components/mumu-recorder/mumu-recorder.vue' export default { + name: 'VoiceInputPopup', + components: { + MumuRecorder + }, props: { show: { type: Boolean, @@ -29,16 +36,317 @@ data() { return { time: '00:00:00', - barHeights: [20, 30, 40, 30, 20], // 可做动画 + isRecording: false, + isPaused: false, + tempFilePath: '', + timer: null, + seconds: 0, + minutes: 0, + hours: 0, + recordSegments: [], // 存储录音片段 + status: false, + recorder: null, + currentSegment: null // 当前录音片段 } }, + computed: { + getRecordButtonSrc() { + if (this.isPaused) { + return '/static/Appeal/start.png' + } + return this.isRecording ? '/static/Appeal/stop.png' : '/static/Appeal/start.png' + } + }, + mounted() { + }, methods: { - closePopup() { - this.$emit('update:show', false) + handlerSave() { + let tag = document.createElement('a') + tag.href = this.recorder.localUrl + tag.download = '录音' + tag.click() }, - onPlay() {}, - onPause() {}, - onStop() {}, + handlerOnCahnger() { + if (!this.isRecording && !this.isPaused) { + // 开始新的录音 + this.startNewRecording() + } else if (this.isRecording) { + // 暂停当前录音 + this.pauseRecording() + } else if (this.isPaused) { + // 继续录音 + this.resumeRecording() + } + }, + startNewRecording() { + console.log('开始新的录音') + this.isRecording = true + this.isPaused = false + this.startTimer() + this.$refs.mumuRecorder.start() + }, + pauseRecording() { + console.log('暂停录音') + this.isRecording = false + this.isPaused = true + this.stopTimer() + this.$refs.mumuRecorder.stop() + }, + resumeRecording() { + console.log('继续录音') + this.isRecording = true + this.isPaused = false + this.startTimer() + this.$refs.mumuRecorder.start() + }, + handlerSuccess(res) { + console.log('录音成功回调:', res) + this.recorder = res + // 保存当前录音片段 + if (res.localUrl) { + console.log('当前录音片段URL:', res.localUrl) + console.log('当前已存储的片段数:', this.recordSegments.length) + console.log('当前片段:', this.currentSegment) + + // 只有当当前片段与上一个不同时才添加 + if (!this.currentSegment || this.currentSegment.url !== res.localUrl) { + console.log('添加新的录音片段') + this.currentSegment = { + url: res.localUrl, + data: res.data, + duration: this.seconds + this.minutes * 60 + this.hours * 3600 + } + this.recordSegments.push(this.currentSegment) + console.log('更新后的片段列表:', this.recordSegments) + } else { + console.log('跳过重复的录音片段') + } + } + }, + handlerError(code) { + switch (code) { + case '101': + uni.showModal({ + content: '当前浏览器版本较低,请更换浏览器使用,推荐在微信中打开。' + }) + break; + case '201': + uni.showModal({ + content: '麦克风权限被拒绝,请刷新页面后授权麦克风权限。' + }) + break + default: + uni.showModal({ + content: '未知错误,请刷新页面重试' + }) + break + } + }, + closePopup() { + // 清空录音片段 + this.recordSegments = [] + this.currentSegment = null + // 重置计时器 + this.seconds = 0 + this.minutes = 0 + this.hours = 0 + this.updateTimeDisplay() + // 关闭弹窗 + this.$emit('close') + }, + startTimer() { + this.stopTimer(); + this.timer = setInterval(() => { + this.seconds++; + if (this.seconds >= 60) { + this.seconds = 0; + this.minutes++; + if (this.minutes >= 60) { + this.minutes = 0; + this.hours++; + } + } + this.updateTimeDisplay(); + }, 1000); + }, + stopTimer() { + if (this.timer) { + clearInterval(this.timer); + this.timer = null; + } + }, + updateTimeDisplay() { + this.time = `${String(this.hours).padStart(2, '0')}:${String(this.minutes).padStart(2, '0')}:${String(this.seconds).padStart(2, '0')}`; + }, + // 合并音频文件 + async mergeAudioFiles(audioUrls) { + try { + const audioContext = new (window.AudioContext || window.webkitAudioContext)(); + const audioBuffers = []; + + // 加载所有音频文件 + for (const url of audioUrls) { + const response = await fetch(url); + const arrayBuffer = await response.arrayBuffer(); + const audioBuffer = await audioContext.decodeAudioData(arrayBuffer); + audioBuffers.push(audioBuffer); + } + + // 计算总时长 + const totalLength = audioBuffers.reduce((acc, buffer) => acc + buffer.length, 0); + + // 创建新的音频缓冲区 + const mergedBuffer = audioContext.createBuffer( + audioBuffers[0].numberOfChannels, + totalLength, + audioBuffers[0].sampleRate + ); + + // 合并音频数据 + let offset = 0; + for (const buffer of audioBuffers) { + for (let channel = 0; channel < buffer.numberOfChannels; channel++) { + const channelData = buffer.getChannelData(channel); + mergedBuffer.copyToChannel(channelData, channel, offset); + } + offset += buffer.length; + } + + // 将合并后的音频转换为 Blob + const wavBlob = await this.audioBufferToWav(mergedBuffer); + const mergedUrl = URL.createObjectURL(wavBlob); + + return mergedUrl; + } catch (error) { + console.error('合并音频失败:', error); + throw error; + } + }, + + // 将 AudioBuffer 转换为 WAV 格式 + audioBufferToWav(buffer) { + const numOfChan = buffer.numberOfChannels; + const length = buffer.length * numOfChan * 2; + const buffer2 = new ArrayBuffer(44 + length); + const view = new DataView(buffer2); + const channels = []; + let sample; + let offset = 0; + let pos = 0; + + // 写入 WAV 文件头 + setUint32(0x46464952); // "RIFF" + setUint32(36 + length); // 文件长度 + setUint32(0x45564157); // "WAVE" + setUint32(0x20746d66); // "fmt " chunk + setUint32(16); // 长度 = 16 + setUint16(1); // PCM (uncompressed) + setUint16(numOfChan); + setUint32(buffer.sampleRate); + setUint32(buffer.sampleRate * 2 * numOfChan); // avg. bytes/sec + setUint16(numOfChan * 2); // block-align + setUint16(16); // 16-bit + setUint32(0x61746164); // "data" - chunk + setUint32(length); // chunk length + + // 写入音频数据 + for (let i = 0; i < buffer.numberOfChannels; i++) { + channels.push(buffer.getChannelData(i)); + } + + while (pos < buffer.length) { + for (let i = 0; i < numOfChan; i++) { + sample = Math.max(-1, Math.min(1, channels[i][pos])); + sample = (0.5 + sample < 0 ? sample * 32768 : sample * 32767) | 0; + view.setInt16(44 + offset, sample, true); + offset += 2; + } + pos++; + } + + return new Blob([buffer2], { type: 'audio/wav' }); + + function setUint16(data) { + view.setUint16(pos, data, true); + pos += 2; + } + + function setUint32(data) { + view.setUint32(pos, data, true); + pos += 4; + } + }, + + // 修改 onStop 方法 + async onStop() { + console.log('停止录音') + // 如果正在录音,先停止当前录音 + 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 + } + + 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: 'none', + duration: 2000 + }) + } + } + }, + beforeDestroy() { + this.stopTimer(); } } </script> @@ -48,6 +356,9 @@ background: #fff; border-radius: 24rpx 24rpx 0 0; padding: 40rpx 0 30rpx 0; + position: relative; + z-index: 9999; + .header { display: flex; justify-content: center; @@ -56,25 +367,30 @@ font-size: 32rpx; font-weight: bold; margin-bottom: 80rpx; + text { flex: 1; text-align: center; } + .u-icon { position: absolute; right: 30rpx; top: 0; } } + .record-anim { display: flex; justify-content: center; align-items: center; margin-bottom: 57rpx; + .bars { display: flex; align-items: flex-end; height: 60rpx; + .bar { width: 10rpx; margin: 0 4rpx; @@ -84,26 +400,34 @@ } } } + .timer { text-align: center; font-size: 28rpx; color: #888; - // margin-bottom: 30rpx; } + .btns { display: flex; justify-content: space-around; align-items: center; - padding: 40rpx 67rpx 76rpx 67rpx; - // .u-button { - // width: 100rpx; - // height: 100rpx; - // border-radius: 50%; - // display: flex; - // justify-content: center; - // align-items: center; - // margin: 0 10rpx; - // } + padding: 40rpx 67rpx 6rpx 67rpx; } } -</style> \ No newline at end of file + +::v-deep .uni-toast { + z-index: 10099 !important; +} + +.uni-sample-toast { + z-index: 10099 !important; +} + +::v-deep .uni-toast .uni-sample-toast { + z-index: 10099 !important; +} + +/deep/ .u-transition.u-fade-enter-to.u-fade-enter-active { + z-index: 997 !important; +} +</style> \ No newline at end of file diff --git a/H5/manifest.json b/H5/manifest.json index 1d6f98c..ec80cdc 100644 --- a/H5/manifest.json +++ b/H5/manifest.json @@ -1,85 +1,88 @@ { - "name" : "“三个身边”群众工作机制", - "appid" : "__UNI__DB035F5", - "description" : "", - "versionName" : "1.0.0", - "versionCode" : "100", - "transformPx" : false, - /* 5+App特有相关 */ - "app-plus" : { - "usingComponents" : true, - "nvueStyleCompiler" : "uni-app", - "compilerVersion" : 3, - "splashscreen" : { - "alwaysShowBeforeRender" : true, - "waiting" : true, - "autoclose" : true, - "delay" : 0 - }, - /* 模块配置 */ - "modules" : {}, - /* 应用发布信息 */ - "distribute" : { - /* android打包配置 */ - "android" : { - "permissions" : [ - "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>", - "<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>", - "<uses-permission android:name=\"android.permission.VIBRATE\"/>", - "<uses-permission android:name=\"android.permission.READ_LOGS\"/>", - "<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>", - "<uses-feature android:name=\"android.hardware.camera.autofocus\"/>", - "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>", - "<uses-permission android:name=\"android.permission.CAMERA\"/>", - "<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>", - "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>", - "<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>", - "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>", - "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>", - "<uses-feature android:name=\"android.hardware.camera\"/>", - "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>" - ] - }, - /* ios打包配置 */ - "ios" : {}, - /* SDK配置 */ - "sdkConfigs" : {} - } - }, - "sassImplementationName" : "node-sass", - /* 快应用特有相关 */ - "quickapp" : {}, - /* 小程序特有相关 */ - "mp-weixin" : { - "appid" : "", - "setting" : { - "urlCheck" : false - }, - "usingComponents" : true - }, - "mp-alipay" : { - "usingComponents" : true - }, - "mp-baidu" : { - "usingComponents" : true - }, - "mp-toutiao" : { - "usingComponents" : true - }, - "uniStatistics" : { - "enable" : false - }, - "vueVersion" : "2", - "h5" : { - "template" : "index.html", - "title" : "“三个身边”群众工作机制", - "optimization" : { - "treeShaking" : { - "enable" : false - } - }, - "router" : { - "base" : "./" - } - } -} + "name": "“三个身边”群众工作机制", + "appid": "__UNI__DB035F5", + "description": "", + "versionName": "1.0.0", + "versionCode": "100", + "transformPx": false, + /* 5+App特有相关 */ + "app-plus": { + "usingComponents": true, + "nvueStyleCompiler": "uni-app", + "compilerVersion": 3, + "splashscreen": { + "alwaysShowBeforeRender": true, + "waiting": true, + "autoclose": true, + "delay": 0 + }, + /* 模块配置 */ + "modules": {}, + /* 应用发布信息 */ + "distribute": { + /* android打包配置 */ + "android": { + "permissions": [ + "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>", + "<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>", + "<uses-permission android:name=\"android.permission.VIBRATE\"/>", + "<uses-permission android:name=\"android.permission.READ_LOGS\"/>", + "<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>", + "<uses-feature android:name=\"android.hardware.camera.autofocus\"/>", + "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>", + "<uses-permission android:name=\"android.permission.CAMERA\"/>", + "<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>", + "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>", + "<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>", + "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>", + "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>", + "<uses-feature android:name=\"android.hardware.camera\"/>", + "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>" + ] + }, + /* ios打包配置 */ + "ios": {}, + /* SDK配置 */ + "sdkConfigs": {} + } + }, + "sassImplementationName": "node-sass", + /* 快应用特有相关 */ + "quickapp": {}, + /* 小程序特有相关 */ + "mp-weixin": { + "appid": "", + "setting": { + "urlCheck": false + }, + "usingComponents": true + }, + "mp-alipay": { + "usingComponents": true + }, + "mp-baidu": { + "usingComponents": true + }, + "mp-toutiao": { + "usingComponents": true + }, + "uniStatistics": { + "enable": false + }, + "vueVersion": "2", + "h5": { + "template": "index.html", + "title": "“三个身边”群众工作机制", + "optimization": { + "treeShaking": { + "enable": false + } + }, + "devServer": { + "https": true + }, + "router": { + "base": "./" + } + } +} \ No newline at end of file diff --git a/H5/package.json b/H5/package.json index 13ca88e..997f0f6 100644 --- a/H5/package.json +++ b/H5/package.json @@ -1,5 +1,10 @@ { "dependencies": { "echarts": "^5.6.0" + }, + "permission": { + "scope.record": { + "desc": "录音功能需要使用您的录音权限" + } } } diff --git a/H5/pages/Appeal/Appeal.vue b/H5/pages/Appeal/Appeal.vue index 9275f7f..dc38ba7 100644 --- a/H5/pages/Appeal/Appeal.vue +++ b/H5/pages/Appeal/Appeal.vue @@ -62,7 +62,7 @@ <view class="flex a-center j-between"> <text class="w-108 fs-27 font-bold mr-85">问题描述</text> <view class="flex a-center" @click="voiceInput"> - + <image src="/static/Appeal/yuyin.png" class="w-30 h-30 mr-11" mode=""></image> <text class="fs-23 red">语音输入</text> </view> </view> @@ -154,9 +154,9 @@ import config from '@/config/index.js' import voiceInputPopup from '@/components/voiceInputPopup.vue' import { - mapActions, - mapState - } from "vuex"; + mapActions, + mapState +} from "vuex"; export default { components: { @@ -178,14 +178,12 @@ detailedAddress: '', descriptionTitle: '', descriptionContent: '', - videoContent: [ - { url: 'xxx1', playing: false }, - { url: 'xxx2', playing: false } - ], + videoContent: [], latitude: '', longitude: '', images: [], videos: [], + voiceFile: '',//语音文件多个逗号拼接 userInfo: uni.getStorageSync('userInfo'), //个人信息 voiceInputShow: false, }; @@ -202,7 +200,7 @@ this.getproblem() this.time = dayjs().format('YYYY-MM-DD') }, - + methods: { ...mapActions(["playRecording", "pausePlaying"]), onPlayRecording(index) { @@ -224,7 +222,8 @@ this.voiceInputShow = false; }, submitVoiceInput(e) { - this.videoContent.push(e); + console.log('eeeeeeeeeeeeeeeeeee', e) + this.videoContent.push({ url: e.url, data: e.data, playing: false }); this.voiceInputShow = false; }, previewImage(index) { @@ -262,7 +261,55 @@ })] })) }, - submit(type) { + 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.uploadFile({ + url: config.imageUrl, + file: item.data, + 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('上传失败')); + } + }, + 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({ @@ -308,6 +355,7 @@ }) return } + const data = { time: this.time, problemType: this.problemType, @@ -320,6 +368,7 @@ descriptionContent: this.descriptionContent, images: this.images.join(','), videos: this.videos.join(','), + voiceFile: this.voiceFile, } // 问题上报 if (type == 1) { @@ -382,6 +431,7 @@ success: (res) => { uni.showLoading() + console.log('res.tempFilePaths[0]', res.tempFilePaths[0]) uni.uploadFile({ url: config.imageUrl, filePath: res.tempFilePaths[0], diff --git a/H5/pages/add-progress/index.vue b/H5/pages/add-progress/index.vue index a94cd59..58bc82d 100644 --- a/H5/pages/add-progress/index.vue +++ b/H5/pages/add-progress/index.vue @@ -236,7 +236,7 @@ timeout: 1000 * 45, name: 'file', header: { - Authorization: cuni.getStorageSync('token') + Authorization: uni.getStorageSync('token') }, success: (res) => { if (JSON.parse(res.data).code == 200) { diff --git a/H5/pages/supervision/edit-supervision-progress.vue b/H5/pages/supervision/edit-supervision-progress.vue index 4cc0ced..18a6bac 100644 --- a/H5/pages/supervision/edit-supervision-progress.vue +++ b/H5/pages/supervision/edit-supervision-progress.vue @@ -74,7 +74,7 @@ </view> </view> <view @click.stop="submit" class="mt-38 fs-35 lh-96 br-48 txt-center font-bold bgcolor6 color4 fixed" - style="width: calc(100% - 62rpx);bottom: calc(env(safe-area-inset-bottom) + 10rpx);">确认添加</view> + style="width: calc(100% - 62rpx);bottom: calc(env(safe-area-inset-bottom) + 10rpx);">确认</view> <view class="btn-box"></view> <view class="safe-box"></view> </view> @@ -82,7 +82,7 @@ <script> import { - saveProcess, + editProgress, getComplaintDetail } from './service' import config from '@/config/index.js' @@ -97,10 +97,16 @@ video: [], againCklicFlag: true, rulsFlag: false, + progressId: '' } }, onLoad(params) { 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(',') getComplaintDetail({ id: params.id }).then(res => { @@ -122,12 +128,13 @@ }) } let obj = { + id: this.progressId, complaintId: this.complaintId, describe: this.describe, imgUrl: this.imgUrls.join(','), video: this.video.join(',') } - saveProcess(obj).then(res => { + editProgress(obj).then(res => { if (res.code == 200) { uni.showToast({ title: '提交成功', diff --git a/H5/pages/supervision/service.js b/H5/pages/supervision/service.js index b9cb14f..bea37fd 100644 --- a/H5/pages/supervision/service.js +++ b/H5/pages/supervision/service.js @@ -1,11 +1,25 @@ import request from '@/utils/request.js' -// 工单列表 -export const getList = (params) => { - return request.post(`/api/huacheng-sangeshenbian/applet/complaint/list`, params) +// 处理记录 +export const getProcessingList = (data) => { + return request.post(`/api/huacheng-sangeshenbian/applet/supervise/processing-record-page`, data) +} +// 问题驳回池 +export const getRejectList = (data) => { + return request.post(`/api/huacheng-sangeshenbian/applet/supervise/reject-record-page`, data) } -// 工单详情 +// 详情 export const getComplaintDetail = (params) => { return request.get('/api/huacheng-sangeshenbian/applet/complaint/detail', params) +} + +// 编辑办理进度 +export const editProgress = (params) => { + return request.put('/api/huacheng-sangeshenbian/applet/complaint-progress/edit', params) +} + +// 删除办理进度 +export const delProgress = (id) => { + return request.delete(`/api/huacheng-sangeshenbian/applet/complaint-progress/${id}`) } \ No newline at end of file diff --git a/H5/pages/supervision/supervision-progress.vue b/H5/pages/supervision/supervision-progress.vue index b0a7520..fc44a39 100644 --- a/H5/pages/supervision/supervision-progress.vue +++ b/H5/pages/supervision/supervision-progress.vue @@ -99,7 +99,8 @@ <script> import dayjs from '../../uni_modules/uview-ui/libs/util/dayjs' import { - getComplaintDetail + getComplaintDetail, + delProgress } from './service' export default { data() { @@ -129,7 +130,6 @@ getComplaintDetail({ id: this.id }).then(res => { - this.info = { ...res.data } @@ -137,7 +137,23 @@ }, methods: { deleteProgress() { - + delProgress(this.row.id).then(res => { + uni.showToast({ + icon: 'none', + mask: true, + title: '删除成功' + }) + this.$refs.customPopup.show = false + setTimeout(() => { + getComplaintDetail({ + id: this.id + }).then(res => { + this.info = { + ...res.data + } + }) + }, 1500) + }) }, toDelete(item) { this.row = item @@ -145,7 +161,7 @@ }, toEdit(item) { uni.navigateTo({ - url: `/pages/supervision/edit-supervision-progress?id=${this.id}` + url: `/pages/supervision/edit-supervision-progress?id=${this.id}&data=${JSON.stringify(item)}` }) }, callPhone(phoneNumber) { diff --git a/H5/pages/supervision/supervision.vue b/H5/pages/supervision/supervision.vue index a55d20b..8e72eae 100644 --- a/H5/pages/supervision/supervision.vue +++ b/H5/pages/supervision/supervision.vue @@ -2,25 +2,23 @@ <view class="content"> <view class="bgColor2 shadow1 pb-19" style="position: sticky;top: 0;z-index: 999;"> <view class="h-96 flex a-center fs-27 j-between txt-center font-w400 color1"> - <view @click="changeType('1')" class="flex1 bgColor2 relative" - :class="searchParams.type == '1' && 'color2 font-bold'"> + <view @click="changeType(1)" class="flex1 bgColor2 relative" :class="type == 1 && 'color2 font-bold'"> <view class="relative zIndex1000"> 处理记录 </view> - <view v-if="searchParams.type == '1'" class="bgColor1"></view> + <view v-if="type == 1" class="bgColor1"></view> </view> - <view @click="changeType('2')" class="flex1 bgColor2 relative" - :class="searchParams.type == '2' && 'color2 font-bold'"> + <view @click="changeType(2)" class="flex1 bgColor2 relative" :class="type == 2 && 'color2 font-bold'"> <view class="relative zIndex1000"> 问题驳回池 </view> - <view v-if="searchParams.type == '2'" class="bgColor1"></view> + <view v-if="type == 2" class="bgColor1"></view> </view> </view> <view class="flex a-center j-between mx-27 pl-38 border2"> <view class="flex a-center flex1"> <image src="../../static/search.png" mode="widthFix" class="w-31 h-31 shrink0 mr-13" /> - <input v-model="searchParams.searchStr" class="fs-27 flex1" placeholder="输入姓名、联系电话关键字搜索" /> + <input v-model="searchParams.keyword" class="fs-27 flex1" placeholder="输入姓名、联系电话关键字搜索" /> </view> <view @click="searchList" class="fs-23 lh-69 txt-center px-29 br-48 my-4 mx-4 bgcolor88">搜索</view> </view> @@ -101,18 +99,18 @@ <view class="mt-31 border1"> <view class="left"></view> <view class="right"></view> - <view v-if="searchParams.type == '1'"> + <view v-if="type == 1"> <view class="flex a-center j-between lh-38 mt-29 ml-38 mr-23 fs-27"> <view>添加办理进度数</view> - <view class="font-bold">10</view> + <view class="font-bold">{{item.progressCount}}</view> </view> <view class="flex a-center j-between lh-38 mt-29 ml-38 mr-23 fs-27"> <view>已下派次数</view> - <view class="font-bold">10</view> + <view class="font-bold">{{item.assignmentCount}}</view> </view> <view class="flex a-center j-between lh-38 mt-29 ml-38 mr-23 fs-27"> <view>已上报次数</view> - <view class="font-bold">10</view> + <view class="font-bold">{{item.reportCount}}</view> </view> <view class="flex a-center j-between lh-38 mt-29 ml-38 mr-23 fs-27"> <view>创建时间</view> @@ -122,31 +120,32 @@ <view v-else> <view class="flex a-center j-between lh-38 mt-29 ml-38 mr-23 fs-27"> <view>申请时间</view> - <view class="font-bold">2025-09-09 11:09:09</view> + <view class="font-bold">{{ item.applyTime | formatTime }}</view> </view> <view class="flex a-center j-between lh-38 mt-29 ml-38 mr-23 fs-27"> <view>申请人</view> <view class="flex a-center"> - <view class="font-bold">李雷 13987654321</view> - <image @click.stop="callPhone(item.contactNumber)" src="../../static/tell.png" + <view class="font-bold">{{item.reporter}} {{item.reporterPhone}}</view> + <image @click.stop="callPhone(item.reporterPhone)" src="../../static/tell.png" class="w-58 h-58 shrink0 ml-19" /> </view> </view> <view class="flex a-center j-between lh-38 mt-29 ml-38 mr-23 fs-27"> <view>审批时间</view> - <view class="font-bold">2025-09-09 11:09:09</view> + <view class="font-bold">{{ item.auditTime | formatTime }}</view> </view> <view class="flex a-center j-between lh-38 mt-29 ml-38 mr-23 fs-27"> <view>审批人</view> <view class="flex a-center"> - <view class="font-bold">李雷 13987654321</view> - <image @click.stop="callPhone(item.contactNumber)" src="../../static/tell.png" + <view class="font-bold">{{item.auditorName}} {{item.auditorPhone}}</view> + <image @click.stop="callPhone(item.auditorPhone)" src="../../static/tell.png" class="w-58 h-58 shrink0 ml-19" /> </view> </view> - <view class="flex a-center j-between lh-38 mt-29 ml-38 mr-23 fs-27"> + <view class="flex j-between lh-38 mt-29 ml-38 mr-23 fs-27"> <view class="shrink0">驳回理由</view> - <view style="max-width: 458rpx; text-align: right;font-weight: bold;">不同意当前申请不同意当前申请不同意当前申请 + <view style="max-width: 458rpx; text-align: right;font-weight: bold;"> + {{item.rejectReason}} </view> </view> </view> @@ -160,17 +159,17 @@ <script> import dayjs from '@/uni_modules/uview-ui/libs/util/dayjs.js' import { - getList + getProcessingList, + getRejectList } from './service.js' export default { data() { return { - active: 1, + type: 1, searchParams: { pageNum: 1, pageSize: 10, - type: '1', - searchStr: '' + keyword: '' }, list: [], status: 'loadMore', @@ -185,7 +184,7 @@ '6': '上级驳回', '7': '延期待审核', '8': '已办结' - } + }, } }, onReachBottom() { @@ -220,6 +219,14 @@ }, methods: { searchList() { + 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 || [] @@ -242,24 +249,19 @@ }, fetchList(params, callback) { if (this.status == 'loading') return - this.status = 'loading' - getList(params).then(res => { + this.status = 'loading'; + [getProcessingList, getRejectList][this.type - 1](params).then(res => { if (res.code == 200) { - res.data.records.map(item => { - if (item.images) { - item.images = item.images.split(',') - } - }) callback(res.data) } }) }, // 切换状态筛选 changeType(type) { + this.type = type this.searchParams = { pageNum: 1, pageSize: 10, - type, //全部:不传,上报待审核:0,正在办理:1, 办结:2 } this.fetchList(this.searchParams, (e) => { this.list = e.records || [] diff --git a/H5/pages/work-detail/work-detail.vue b/H5/pages/work-detail/work-detail.vue index f0d15bd..a3bdef8 100644 --- a/H5/pages/work-detail/work-detail.vue +++ b/H5/pages/work-detail/work-detail.vue @@ -165,9 +165,9 @@ <!-- 问题描述 --> <view class="problem"> <view class="title">问题描述</view> - <view v-for="(item,index) in 2" :key="index" + <view v-for="(item,index) in getVoiceFile(orderInfo.voiceFile)" :key="index" class="flex a-center j-between py-17 px-19 br-8 bgcolor1 mb-19"> - <view class="fs-27 lh-38">语音名字</view> + <view class="fs-27 lh-38">语音名字{{(index + 1) | numToWords}}</view> <image v-if="!playFlag" @click.stop="playRecording(item)" src="../../static/24gf-playCircle@2x.png" class="w-27 h-27 shrink0" /> <image v-else @click.stop="pausePlaying" src="../../static/pausePlaying.png" @@ -432,6 +432,18 @@ formatTime(val) { if (!val) return '' return dayjs(val).format('YYYY-MM-DD HH:mm:ss') + }, + numToWords(val) { + const words = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十'] + // 处理 0-10 + if (val >= 0 && val <= 10) return words[val]; + // 处理 11-99 + if (val > 10 && val < 100) { + const ten = Math.floor(val / 10); + const unit = val % 10; + return `${ten > 1 ? words[ten] : ''}十${unit > 0 ? words[unit] : ''}`; + } + return val; // 超过99返回原数字 } }, onShow() { @@ -454,6 +466,10 @@ }, methods: { ...mapActions(["playRecording", "pausePlaying"]), + getVoiceFile(voiceFile) { + if (!voiceFile || voiceFile.length == 0) return [] + return voiceFile.split(',') + }, callPhone(phoneNumber) { uni.makePhoneCall({ phoneNumber diff --git a/H5/uni_modules/mumu-recorder/changelog.md b/H5/uni_modules/mumu-recorder/changelog.md new file mode 100644 index 0000000..eb2c332 --- /dev/null +++ b/H5/uni_modules/mumu-recorder/changelog.md @@ -0,0 +1,4 @@ +## 1.0.1(2022-06-11) +修复苹果手机在微信中无法获取音频长度问题 +## 1.0.0(2022-06-10) +版本上线 diff --git a/H5/uni_modules/mumu-recorder/components/mumu-recorder/mumu-recorder.vue b/H5/uni_modules/mumu-recorder/components/mumu-recorder/mumu-recorder.vue new file mode 100644 index 0000000..b890c7d --- /dev/null +++ b/H5/uni_modules/mumu-recorder/components/mumu-recorder/mumu-recorder.vue @@ -0,0 +1,113 @@ +<template> + <view class="recorder"> + </view> +</template> + +<script> + export default { + data() { + return { + isUserMedia: false, + stream: null, + audio: null, + recorder: null, + chunks: [], + startTime: 0 + } + }, + mounted() { + /** + * error 事件的返回状态 + * 100: 请在HTTPS环境中使用 + * 101: 浏览器不支持 + * 201: 用户拒绝授权 + * 500: 未知错误 + * */ + if (origin.indexOf('https') === -1) { + this.$emit('error', '100') + throw '请在 https 环境中使用本插件。' + } + if (!navigator.mediaDevices || !window.MediaRecorder) { + this.$emit('error', '101') + throw '当前浏览器不支持' + } + + this.getRecorderManager() + }, + methods: { + getRecorderManager() { + this.audio = document.createElement('audio') + navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => { + this.isUserMedia = true + stream.getTracks().forEach((track) => { + track.stop() + }) + }).catch(err => { + this.onErrorHandler(err) + }) + }, + start() { + if (!this.isUserMedia) return console.log('设备不支持') + + navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => { + this.startTime = new Date().getTime() + this.stream = stream + this.recorder = new MediaRecorder(stream) + this.recorder.ondataavailable = this.getRecordingData + this.recorder.onstop = this.saveRecordingData + this.recorder.start() + }).catch(err => { + this.onErrorHandler(err) + }) + }, + stop() { + this.recorder.stop() + this.stream.getTracks().forEach((track) => { + track.stop() + }) + }, + getRecordingData(e) { + this.chunks.push(e.data) + }, + saveRecordingData() { + const blob = new Blob(this.chunks, { 'type': 'audio/mpeg' }), + localUrl = URL.createObjectURL(blob) + + const endTime = new Date().getTime() + + let duration = (endTime - this.startTime).toString().split('') + duration.splice(duration.length - 2) + duration.splice(duration.length - 1, 0, '.') + duration = parseFloat(duration.join('')) + + const recorder = { + data: blob, + duration: duration, + localUrl: localUrl + } + this.$emit('success', recorder) + }, + onErrorHandler(err) { + console.log(err) + if (err.name === 'NotAllowedError') { + this.$emit('error', '201') + throw '用户拒绝了当前的浏览器实例的访问请求' + } + + if (err.name === 'NotReadableError') { + this.$emit('error', '101') + throw '当前浏览器不支持' + } + + this.$emit('error', '500') + throw '调用失败,原因不详' + + } + }, + destroyed() { + this.stop() + } + } +</script> +<style> +</style> diff --git a/H5/uni_modules/mumu-recorder/package.json b/H5/uni_modules/mumu-recorder/package.json new file mode 100644 index 0000000..00d5e90 --- /dev/null +++ b/H5/uni_modules/mumu-recorder/package.json @@ -0,0 +1,87 @@ +{ + "id": "mumu-recorder", + "displayName": "h5录音组件,调用H5原生功能使用麦克风进行录音", + "version": "1.0.1", + "description": "演示案例中模仿了微信的长按发送语音,与普通录音demo。", + "keywords": [ + "录音", + "麦克风", + "模仿微信" +], + "repository": "", + "engines": { + "HBuilderX": "^3.1.0" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "麦克风" + }, + "npmurl": "" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "Vue": { + "vue2": "y", + "vue3": "y" + }, + "App": { + "app-vue": "n", + "app-nvue": "n" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "n", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "u", + "阿里": "u", + "百度": "u", + "字节跳动": "u", + "QQ": "u", + "钉钉": "u", + "快手": "u", + "飞书": "u", + "京东": "u", + "小红书": "u" + }, + "快应用": { + "华为": "u", + "联盟": "u" + } + } + } + } +} \ No newline at end of file diff --git a/H5/uni_modules/mumu-recorder/readme.md b/H5/uni_modules/mumu-recorder/readme.md new file mode 100644 index 0000000..1f888e5 --- /dev/null +++ b/H5/uni_modules/mumu-recorder/readme.md @@ -0,0 +1,117 @@ +## 插件简绍 + +### 实现原理 + +> 通过 navigator.mediaDevices.getUserMedia(需要https环境) 这个api调用麦克风,获取到到音频流数据。 +> +> 通过 MediaRecorder 这个构造函数对音频流进行接收,完成录制后会返回一个存储`Blob`内容的录制数据。 + + +### 使用环境 + +需要https环境才能使用,本地测试可以在 manifest.json 中点击源码展示,找到h5 ,添加:"devServer" : { "https" : true} + +**请勿使用 UC浏览器 与 夸克等阿里旗下的浏览器,发现他们使用的内核都较低,无法正常获取音频流,并且都有对接音频流截取的插件,导致无法正常获取音频流的数据。在微信中可以正常使用,推荐在微信内打开演示案例 ** + +需要https环境才能使用!!! + +需要https环境才能使用!!! + +需要https环境才能使用!!! + +### 插件使用 + +**插件已支持 uni_modules 支持组件easycom,以下代码演示的是普通使用** + +``` html +<!-- HTML --> + <view> + <audio :src='recorder.localUrl' v-if='recorder' name='本地录音' controls="true"></audio> + <view @click='handlerOnCahnger'> + {{!status?'开始录音':'结束录音'}} + </view> + <mumu-recorder ref='recorder' @success='handlerSuccess' @error='handlerError'></mumu-recorder> + </view> +``` + +``` javascript +// js + import MumuRecorder from '@/uni_modules/mumu-recorder/components/mumu-recorder/mumu-recorder.vue' + export default { + components: { MumuRecorder }, + data() { + return { + status: false, + recorder: null + } + }, + onLoad() { + + }, + methods: { + handlerSave() { + let tag = document.createElement('a') + tag.href = this.recorder.localUrl + tag.download = '录音' + tag.click() + }, + handlerOnCahnger() { + if (this.status) { + this.$refs.recorder.stop() + } else { + this.$refs.recorder.start() + } + this.status = !this.status + }, + handlerSuccess(res) { + console.log(res) + this.recorder = res + }, + handlerError(code) { + switch (code) { + case '101': + uni.showModal({ + content: '当前浏览器版本较低,请更换浏览器使用,推荐在微信中打开。' + }) + break; + case '201': + uni.showModal({ + content: '麦克风权限被拒绝,请刷新页面后授权麦克风权限。' + }) + break + default: + uni.showModal({ + content: '未知错误,请刷新页面重试' + }) + break + } + } + } + } +``` + +### 相关API + +##### 组件内部方法($refs 调用) + +| 方法名 | 说明 | 参数 | +| ------ | -------- | ---- | +| start | 开始录音 | 无 | +| stop | 结束录音 | 无 | + + + +##### 事件(Events) + +| 事件名 | 说明 | 回调参数 | +| ------- | -------------------- | ------------------------------------------------------------ | +| success | 停止录音后调用此事件 | 返回录音数据,是一个对象<br />{ data: 音频的 blob 数据,上传请使用这个 <br />duration: 当前音频长度<br/>localUrl: 当前音频的本地链接,可直接通过 audio 标签进行播放 } | +| error | 组件内部发生错误 | 错误码:<100 当前不是https环境> <101 浏览器不支持> <201 麦克风权限被拒绝> <500 未知错误> | + +### 案例演示 + + + +## 支持作者 + + \ No newline at end of file diff --git a/H5/utils/request.js b/H5/utils/request.js index 52fceac..765290c 100644 --- a/H5/utils/request.js +++ b/H5/utils/request.js @@ -19,9 +19,9 @@ return new Promise(function(resolve, reject) { let token = uni.getStorageSync('token') - // token = - // 'Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOjE2MjA0LCJ0eXBlIjoxLCJleHAiOjE3NDM1NTY3NzYsImNyZWF0ZWQiOjE3NDIyNjA3NzY2NTR9.SAmdlprtz_Z1cNSNB4ANYsKJFC7E7Jfxo6XbT9vpbO6zHBpTRfq_oyp9UlDQtHNiYgv-MuyqCBPw1-x88C_-8A' - // uni.setStorageSync('token', token) + token = + 'Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOjE4OTU1MjI1Nzk1MDcwODEyMTgsInR5cGUiOjEsImV4cCI6MTc0Nzk4MzIyMiwiY3JlYXRlZCI6MTc0NjY4NzIyMjAxMX0.pmUfTkxkbBirDMbnMR1IaLsbSiiwHc366_yyAetCzTOWYxTNgmQlmvw26_W62NHLOebB_ZAEgZsPvkJcaLOoPg' + uni.setStorageSync('token', token) let header = { 'content-type': type ? 'application/x-www-form-urlencoded;charset=UTF-8' : 'application/json', 'Authorization': token, diff --git a/management/config/routes.ts b/management/config/routes.ts index 67a18dc..0bba9d2 100644 --- a/management/config/routes.ts +++ b/management/config/routes.ts @@ -52,7 +52,7 @@ access: '/system_setting/position_management', }, { - name: '角色管理', + name: '权限管理', path: '/setting/role', component: './setting/role', access: '/system_setting/role_management', diff --git a/management/src/pages/appeal-management/detail.jsx b/management/src/pages/appeal-management/detail.jsx index e8d4ae5..950943f 100644 --- a/management/src/pages/appeal-management/detail.jsx +++ b/management/src/pages/appeal-management/detail.jsx @@ -1,13 +1,14 @@ import { buildProTableDataSource, sendRequest, showDelConfirm } from '@/utils/antdUtils'; import { PageContainer, ProTable } from '@ant-design/pro-components'; -import { Button, Card, Select, Space, Descriptions, Divider, Steps, message } from 'antd'; +import { Button, Card, Select, Space, Descriptions, Divider, Steps, message, Modal, Form, Input, Upload, Popconfirm } from 'antd'; import { useRef, useState, useEffect } from 'react'; import { Access, useAccess } from 'umi' import { history, useLocation } from "@umijs/max" -import { getDetail } from './service'; +import { getDetail, editProgress, deleteProgress } from './service'; import moment from 'moment'; import './index.less'; -import {downLoad } from '@/utils/utils'; +import {downLoad, customRequest } from '@/utils/utils'; +import { PlusOutlined } from '@ant-design/icons'; const Account = () => { const actionRef = useRef(); @@ -16,6 +17,22 @@ const [detail, setDetail] = useState({}); const searchParams = new URLSearchParams(useLocation().search); const id = searchParams.get('id'); + const [editModalVisible, setEditModalVisible] = useState(false); + const [editProgressData, setEditProgressData] = useState(null); + const [editForm] = Form.useForm(); + const [loading, setLoading] = useState(false); + const [imgFileList, setImgFileList] = useState([]); + const [videoFileList, setVideoFileList] = useState([]); + const [previewVisible, setPreviewVisible] = useState(false); + const [previewImage, setPreviewImage] = useState(''); + const [previewType, setPreviewType] = useState('image'); // 'image' or 'video' + const uploadConfig = { + name: 'file', + action: 'https://huacheng.psciio.com/api/huacheng-communitybackstage/communitypartybuilding/uploadimage', + headers: { + Authorization: 'Bearer ' + localStorage.getItem('token'), + }, + }; useEffect(() => { getDetail({ id: id }).then((res) => { @@ -111,6 +128,117 @@ span: 4, }, ]; + + // 上传前校验 + const beforeUpload = (file) => { + return new Promise((resolve, reject) => { + if (file.name.includes(',')) { + message.warning('上传文件不能包含英文逗号(,)'); + return Upload.LIST_IGNORE; + } + setLoading(true); + resolve(file); + }); + }; + + // 图片上传change + const handleImgChange = ({ file, fileList: newFileList }) => { + if (file.status === 'error' || (file.status === 'done' && file.response && file.response.code !== 200)) { + setLoading(false); + setImgFileList([]); + message.error('上传失败'); + editForm.setFieldValue('imgUrl', []); + return; + } + if (file.status === 'done' && file.response && file.response.code === 200) { + setLoading(false); + message.success('上传成功'); + } + let urls = newFileList.map(item => { + if (item.status === 'done' && item.response && item.response.data) { + item.url = item.response.data; + } + return item.url || item.url; + }).filter(Boolean); + setImgFileList(newFileList); + editForm.setFieldValue('imgUrl', urls); + }; + + // 视频上传change + const handleVideoChange = ({ file, fileList: newFileList }) => { + if (file.status === 'error' || (file.status === 'done' && file.response && file.response.code !== 200)) { + setLoading(false); + setVideoFileList([]); + message.error('上传失败'); + editForm.setFieldValue('videoUrl', []); + return; + } + if (file.status === 'done' && file.response && file.response.code === 200) { + setLoading(false); + message.success('上传成功'); + } + let urls = newFileList.map(item => { + if (item.status === 'done' && item.response && item.response.data) { + item.url = item.response.data; + } + return item.url || item.url; + }).filter(Boolean); + setVideoFileList(newFileList); + editForm.setFieldValue('videoUrl', urls); + }; + + // 办理进度编辑提交 + const handleEditProgress = async () => { + try { + setLoading(true); + console.log('editProgressData', editProgressData); + + const values = await editForm.validateFields(); + await editProgress({ + complaintId: id, + describe: values.describe, + id: editProgressData.id, + imgUrl: (values.imgUrl || []).join(','), + video: (values.videoUrl || []).join(',') + }); + message.success('编辑成功'); + setEditModalVisible(false); + getDetail({ id }).then((res) => setDetail(res.data)); + } catch (e) { + // 校验或请求失败 + } finally { + setLoading(false); + } + }; + + // 办理进度删除 + const handleDeleteProgress = async (progressId) => { + try { + await deleteProgress({ id: progressId }); + message.success('删除成功'); + getDetail({ id }).then((res) => setDetail(res.data)); + } catch (e) { + message.error('删除失败'); + } + }; + + // 在弹窗打开时同步 fileList + useEffect(() => { + if (editModalVisible && editProgressData) { + setImgFileList((editProgressData.imgUrl ? editProgressData.imgUrl.split(',') : []).map((url, idx) => ({ uid: idx, url, status: 'done' }))); + setVideoFileList((editProgressData.video ? editProgressData.video.split(',') : []).map((url, idx) => ({ uid: idx, url, status: 'done' }))); + editForm.setFieldsValue({ + describe: editProgressData.describe, + imgUrl: editProgressData.imgUrl ? editProgressData.imgUrl.split(',') : [], + videoUrl: editProgressData.video ? editProgressData.video.split(',') : [], + }); + } + if (!editModalVisible) { + setImgFileList([]); + setVideoFileList([]); + } + }, [editModalVisible, editProgressData]); + return ( <div> <PageContainer className={'appeal-management-detail'} header={{ breadcrumb: {} }} title="述求详情" > @@ -121,22 +249,34 @@ <Descriptions title="办理进度" column={1}> {detail.complaintProgresses?.length > 0 ? detail.complaintProgresses.map((item, index) => ( <Descriptions.Item key={index}> - <Card style={{ width: '800px' }} title={item.createByName} extra={moment(item.createTime).format('YYYY-MM-DD HH:mm:ss')}> + <Card style={{ width: '800px', position: 'relative' }} title={item.createByName} extra={moment(item.createTime).format('YYYY-MM-DD HH:mm:ss')}> <Descriptions column={1} > - <Descriptions.Item> - {item.describe} - </Descriptions.Item> + <Descriptions.Item>{item.describe}</Descriptions.Item> <Descriptions.Item label={'上传图片'}> - {item.imgUrl && (item.imgUrl || '').split(',').map((item, index) => ( - <img width={80} style={{ marginRight: '10px' }} height={80} src={item} key={index} alt="example" /> + {item.imgUrl && (item.imgUrl || '').split(',').map((img, idx) => ( + <img width={80} style={{ marginRight: '10px' }} height={80} src={img} key={idx} alt="example" /> ))} </Descriptions.Item> <Descriptions.Item label={'上传视频'}> - {item.videoUrl && (item.videoUrl || '').split(',').map((item, index) => ( - <video width={280} style={{ marginRight: '10px' }} src={item} key={index} controls></video> + {item.video && item.video.split(',').map((video, idx) => ( + <video width={280} style={{ marginRight: '10px' }} src={video} key={idx} controls></video> ))} </Descriptions.Item> </Descriptions> + <div style={{ position: 'absolute', right: 24, bottom: 16 }}> + <Button size="small" type="primary" style={{ marginRight: 8 }} onClick={() => { + setEditProgressData(item); + setEditModalVisible(true); + editForm.setFieldsValue({ + describe: item.describe, + imgUrl: item.imgUrl ? item.imgUrl.split(',') : [], + videoUrl: item.video ? item.video.split(',') : [], + }); + }}>编辑</Button> + <Popconfirm title="确定要删除该办理进度吗?" onConfirm={() => handleDeleteProgress(item.id)} okText="确定" cancelText="取消"> + <Button size="small" danger>删除</Button> + </Popconfirm> + </div> </Card> </Descriptions.Item> )) : <Descriptions.Item span={4} >暂无办理进度</Descriptions.Item>} @@ -252,7 +392,80 @@ </div> </Card> + <Modal + title="编辑办理进度" + open={editModalVisible} + onCancel={() => setEditModalVisible(false)} + onOk={handleEditProgress} + confirmLoading={loading} + destroyOnClose + width={600} + > + <Form form={editForm} layout="vertical" initialValues={{ describe: '', imgUrl: [], videoUrl: [] }}> + <Form.Item name="describe" label="办理进度描述" rules={[{ required: true, message: '请输入办理进度描述' }]}> + <Input.TextArea rows={4} maxLength={500} showCount /> + </Form.Item> + <Form.Item name="imgUrl" label="图片:"> + <Upload + {...uploadConfig} + listType="picture-card" + maxCount={9} + beforeUpload={beforeUpload} + onChange={handleImgChange} + onRemove={() => { + setImgFileList([]); + editForm.setFieldValue('imgUrl', []); + }} + showUploadList={{ showPreviewIcon: true }} + accept="image/*" + fileList={imgFileList} + onPreview={file => { + setPreviewType('image'); + setPreviewImage(file.url || file.thumbUrl); + setPreviewVisible(true); + }} + > + {imgFileList.length < 9 && <PlusOutlined />} + </Upload> + </Form.Item> + <Form.Item name="videoUrl" label="视频:"> + <Upload + {...uploadConfig} + listType="picture-card" + maxCount={9} + beforeUpload={beforeUpload} + onChange={handleVideoChange} + onRemove={() => { + setVideoFileList([]); + editForm.setFieldValue('videoUrl', []); + }} + showUploadList={{ showPreviewIcon: true }} + accept="video/*" + fileList={videoFileList} + onPreview={file => { + setPreviewType('video'); + setPreviewImage(file.url || file.thumbUrl); + setPreviewVisible(true); + }} + > + {videoFileList.length < 9 && <PlusOutlined />} + </Upload> + </Form.Item> + </Form> + </Modal> + <Modal + open={previewVisible} + footer={null} + onCancel={() => setPreviewVisible(false)} + width={previewType === 'video' ? 800 : 600} + > + {previewType === 'image' ? ( + <img alt="预览" style={{ width: '100%' }} src={previewImage} /> + ) : ( + <video style={{ width: '100%' }} src={previewImage} controls autoPlay /> + )} + </Modal> </PageContainer> </div> ); diff --git a/management/src/pages/appeal-management/service.js b/management/src/pages/appeal-management/service.js index efdd37d..832c5b8 100644 --- a/management/src/pages/appeal-management/service.js +++ b/management/src/pages/appeal-management/service.js @@ -15,3 +15,19 @@ data }); } + +//编辑办理进度 +export const editProgress = async (data) => { + return request(`/api/huacheng-sangeshenbian/complaint/update-progress`, { + method: 'POST', + data + }); +} + +//删除办理进度 +export const deleteProgress = async (data) => { + return request(`/api/huacheng-sangeshenbian/complaint/del-progress/${data.id}`, { + method: 'DELETE', + data + }); +} diff --git a/management/src/pages/appeal-management/statistics/index.jsx b/management/src/pages/appeal-management/statistics/index.jsx index 85e061f..cc99e94 100644 --- a/management/src/pages/appeal-management/statistics/index.jsx +++ b/management/src/pages/appeal-management/statistics/index.jsx @@ -7,14 +7,12 @@ import { getList } from './service'; const Account = () => { - const actionRef = useRef(); const access = useAccess(); - const formRef = useRef(); const columns = [ { title: '述求号', - dataIndex: 'reportUserName', + dataIndex: 'serialNumber', order: 8, }, { @@ -29,30 +27,30 @@ }, { title: '申请人', - dataIndex: 'name', + dataIndex: 'applyUserName', order: 5, }, { title: '申请时间', - dataIndex: 'name', + dataIndex: 'applyTime', hideInTable: true, valueType: 'dateRange', order: 3, }, { title: '审批时间', - dataIndex: 'name', + dataIndex: 'examineTime', valueType: 'dateRange', order: 2, }, { title: '审批人', - dataIndex: 'name', + dataIndex: 'examineUserName', order: 4, }, { title: '驳回理由', - dataIndex: 'contactNumber', + dataIndex: 'remark', hideInSearch: true, }, { @@ -64,11 +62,13 @@ 1: '延期办理', 2: '超时办理', 3: '已办结', - 4: '上报待审核', + 4: '群众撤销', + 5: '上报待审核', + 6: '上级驳回', + 7: '延期待审核', + 8: '已评价', + 9: '延期驳回', }, - render: (text, record) => { - return Number(record.status) == 0 ? '正在办理' : record.status == 1 ? '延期办理' : record.status == 2 ? '超时办理' : record.status == 3 ? '已办结' : record.status == 4 ? '上报待审核' : '已办结'; - } }, { title: '操作', @@ -102,20 +102,16 @@ > <ProTable rowKey="id" - actionRef={actionRef} columns={columns} - formRef={formRef} request={async (params) => { - - if (params.time && params.time.length > 0) { - params.startTime = moment(params.time[0]).format('YYYY-MM-DD HH:mm:ss'); - params.endTime = moment(params.time[1]).format('YYYY-MM-DD 23:59:59'); - delete params.time - } else { - delete params.startTime - delete params.endTime + if (params.applyTime && params.applyTime.length > 0) { + params.applyTime = moment(params.applyTime[0]).format('YYYY-MM-DD') + + ' - ' + moment(params.applyTime[1]).format('YYYY-MM-DD') } - + if (params.examineTime && params.examineTime.length > 0) { + params.examineTime = moment(params.examineTime[0]).format('YYYY-MM-DD') + + ' - ' + moment(params.examineTime[1]).format('YYYY-MM-DD') + } return buildProTableDataSource(getList, params); }} diff --git a/management/src/pages/appeal-management/statistics/service.js b/management/src/pages/appeal-management/statistics/service.js index efdd37d..9d4db84 100644 --- a/management/src/pages/appeal-management/statistics/service.js +++ b/management/src/pages/appeal-management/statistics/service.js @@ -1,17 +1,8 @@ import { request } from '@umijs/max'; -// 获取诉求列表 -export const getList = async (data) => { - return request(`/api/huacheng-sangeshenbian/complaint/page`, { - method: 'POST', - data - }); -} - -// 获取述求详情 -export const getDetail = async (data) => { - return request(`/api/huacheng-sangeshenbian/complaint/detail/${data.id}`, { +export const getList = async (params) => { + return request(`/api/huacheng-sangeshenbian/complaint-reject/list`, { method: 'GET', - data + params }); } diff --git a/management/src/pages/logManagement/index.jsx b/management/src/pages/logManagement/index.jsx index 9df7999..3a05a6b 100644 --- a/management/src/pages/logManagement/index.jsx +++ b/management/src/pages/logManagement/index.jsx @@ -14,24 +14,24 @@ const columns = [ { title: '操作时间', - dataIndex: 'name', + dataIndex: 'createTime', hideInTable: true, valueType: 'dateRange', order: 1, }, { title: '操作用户', - dataIndex: 'reportUserName', + dataIndex: 'operatorName', order: 5, }, { title: '联系电话', - dataIndex: 'reportUserName', + dataIndex: 'operatorPhone', order: 4, }, { title: '操作类型', - dataIndex: 'reportUserPhone', + dataIndex: 'operatorCategory', order: 3, valueEnum: { 1: '登录', @@ -48,45 +48,41 @@ }, { title: '对象名称', - dataIndex: 'name', + dataIndex: 'targetName', hideInSearch: true, }, { title: '所在IP', - dataIndex: 'name', + dataIndex: 'ip', order: 2, }, ]; return ( - <div> - <PageContainer header={{ - breadcrumb: {}, - }} - title={'日志记录'} - > - <ProTable - rowKey="id" - actionRef={actionRef} - columns={columns} - formRef={formRef} - request={async (params) => { - if (params.time && params.time.length > 0) { - params.startTime = moment(params.time[0]).format('YYYY-MM-DD HH:mm:ss'); - params.endTime = moment(params.time[1]).format('YYYY-MM-DD 23:59:59'); - delete params.time - } else { - delete params.startTime - delete params.endTime - } - - - return buildProTableDataSource(getList, params); - }} - search={{ labelWidth: 'auto', defaultCollapsed: false }} - /> - </PageContainer> - </div> + <PageContainer header={{ + breadcrumb: {}, + }} + title={'日志记录'} + > + <ProTable + rowKey="id" + actionRef={actionRef} + columns={columns} + formRef={formRef} + request={async (params) => { + if (params.time && params.time.length > 0) { + params.startTime = moment(params.time[0]).format('YYYY-MM-DD HH:mm:ss'); + params.endTime = moment(params.time[1]).format('YYYY-MM-DD 23:59:59'); + delete params.time + } else { + delete params.startTime + delete params.endTime + } + return buildProTableDataSource(getList, params); + }} + search={{ labelWidth: 'auto', defaultCollapsed: false }} + /> + </PageContainer> ); }; -export default Account; +export default Account; \ No newline at end of file diff --git a/management/src/pages/logManagement/service.js b/management/src/pages/logManagement/service.js index efdd37d..73f1726 100644 --- a/management/src/pages/logManagement/service.js +++ b/management/src/pages/logManagement/service.js @@ -1,17 +1,8 @@ import { request } from '@umijs/max'; -// 获取诉求列表 -export const getList = async (data) => { - return request(`/api/huacheng-sangeshenbian/complaint/page`, { - method: 'POST', - data - }); -} - -// 获取述求详情 -export const getDetail = async (data) => { - return request(`/api/huacheng-sangeshenbian/complaint/detail/${data.id}`, { +export const getList = async (params) => { + return request(`/api/huacheng-sangeshenbian/system-log/list`, { method: 'GET', - data + params }); } diff --git a/management/src/pages/party/manage/index.jsx b/management/src/pages/party/manage/index.jsx index 7edb3d1..f5a39dd 100644 --- a/management/src/pages/party/manage/index.jsx +++ b/management/src/pages/party/manage/index.jsx @@ -66,6 +66,18 @@ } }, { + title: '创建时间', + dataIndex: 'createTime', + sorter: true, + hideInSearch: true, + }, + { + title: '更新时间', + dataIndex: 'updateTime', + sorter: true, + hideInSearch: true, + }, + { title: '状态', dataIndex: 'freezeStatus', valueEnum: { @@ -75,80 +87,84 @@ } }, { + title: '身份证号', + dataIndex: 'idNumber', + }, + { title: '操作', hideInSearch: true, render: (text, record) => { return ( <Space> <Access accessible={access['/party_member/edit']}> - <Button - type="link" - onClick={() => { - history.push(`/party/manage/add?type=edit&id=${record.id}`) - }} - > - 编辑 - </Button> - </Access> - <Access accessible={access['/party_member/del']}> - <Button - type="link" - onClick={() => { - showDelConfirm(async () => { - let status = await sendRequest(deleteBanner, {id:record.id}) - if (status) { - actionRef.current.reload(); - } - }, '确认删除所选信息吗?'); - }} - > - 删除 - </Button> - </Access> - <Access accessible={access['/party_member/detail']}> - <Button - type="link" - onClick={() => { - history.push(`/party/manage/add?detail=true&id=${record.id}`) - }} - > - 查看详情 - </Button> - </Access> - <Access accessible={access['/party_member/freeze']}> - {record.freezeStatus == 0 && ( <Button type="link" onClick={() => { - showDelConfirm1(async () => { - let status = await sendRequest(freeze, { id: record.id }) + history.push(`/party/manage/add?type=edit&id=${record.id}`) + }} + > + 编辑 + </Button> + </Access> + <Access accessible={access['/party_member/del']}> + <Button + type="link" + onClick={() => { + showDelConfirm(async () => { + let status = await sendRequest(deleteBanner, { id: record.id }) if (status) { actionRef.current.reload(); } - }, '确认冻结该党员信息吗?', '冻结', '', '确认冻结该党员信息吗?'); + }, '确认删除所选信息吗?'); }} > - 冻结 + 删除 </Button> - )} + </Access> + <Access accessible={access['/party_member/detail']}> + <Button + type="link" + onClick={() => { + history.push(`/party/manage/add?detail=true&id=${record.id}`) + }} + > + 查看详情 + </Button> + </Access> + <Access accessible={access['/party_member/freeze']}> + {record.freezeStatus == 0 && ( + <Button + type="link" + onClick={() => { + showDelConfirm1(async () => { + let status = await sendRequest(freeze, { id: record.id }) + if (status) { + actionRef.current.reload(); + } + }, '确认冻结该党员信息吗?', '冻结', '', '确认冻结该党员信息吗?'); + }} + > + 冻结 + </Button> + )} </Access> <Access accessible={access['/party_member/freeze']}> - {record.freezeStatus == 1 && ( - <Button - type="link" - onClick={() => { - showDelConfirm1(async () => { - let status = await sendRequest(freeze, { id: record.id }) - if (status) { - actionRef.current.reload(); - } - }, '确认解冻该党员信息吗?', '解冻', '', '确认解冻该党员信息吗?'); - }} - > - 解冻 - </Button> - )} + {record.freezeStatus == 1 && ( + <Button + type="link" + onClick={() => { + showDelConfirm1(async () => { + let status = await sendRequest(freeze, { id: record.id }) + if (status) { + actionRef.current.reload(); + } + }, '确认解冻该党员信息吗?', '解冻', '', '确认解冻该党员信息吗?'); + }} + > + 解冻 + </Button> + )} </Access> </Space > @@ -175,7 +191,15 @@ showQuickJumper: true, defaultPageSize: 10, }} - request={(params) => { + request={(params, sorter) => { + console.log('weqweqeqwe', sorter); + if (sorter.createTime) { + params.createTimeSort = sorter.createTime === 'ascend' ? 2 : 1; + } + if (sorter.updateTime) { + params.updateTimeSort = sorter.updateTime === 'ascend' ? 2 : 1; + } + params.communityId = params.community ? params.community[params.community.length - 1] : '' params.auditStatus = 1 setExcelParams(() => params) @@ -184,35 +208,35 @@ toolBarRender={(action, selectRows) => [ <Space> <Access accessible={access['/party_member/add']}> - <Button - type="primary" - onClick={() => { - history.push('/party/manage/add?type=add') - }} - > - 添加 - </Button> + <Button + type="primary" + onClick={() => { + history.push('/party/manage/add?type=add') + }} + > + 添加 + </Button> </Access> <Access accessible={access['/party_member/export']}> - <Button - type="primary" - onClick={() => { - exportExcell('党员列表', excelParams, '/api/huacheng-sangeshenbian/party-member/export') - }} - > - 导出 - </Button> + <Button + type="primary" + onClick={() => { + exportExcell('党员列表', excelParams, '/api/huacheng-sangeshenbian/party-member/export') + }} + > + 导出 + </Button> </Access> <Access accessible={access['/party_member/import']}> - <Button - type="primary" - onClick={() => { - handleModalExport(true) - modalExportRef.current.clean() - }} - > - 导入 - </Button> + <Button + type="primary" + onClick={() => { + handleModalExport(true) + modalExportRef.current.clean() + }} + > + 导入 + </Button> </Access> </Space> ]} diff --git a/management/src/pages/setting/role/components/addAndEdit.jsx b/management/src/pages/setting/role/components/addAndEdit.jsx index ad2a7a4..d8b2284 100644 --- a/management/src/pages/setting/role/components/addAndEdit.jsx +++ b/management/src/pages/setting/role/components/addAndEdit.jsx @@ -128,7 +128,7 @@ getContainer={false} width="20%" destroyOnClose - title={detailType ? '角色详情' : data.id ? '编辑角色' : '添加角色'} + title={detailType ? '权限详情' : data.id ? '编辑权限' : '添加权限'} open={visible} onCancel={() => onCancel(false)} afterClose={() => { @@ -150,10 +150,10 @@ <Form layout="horizontal" {...formItemLayout} form={form} scrollToFirstError> <Form.Item name="name" - label="角色名称" - rules={[{ required: true, message: '请输入角色名称' }]} + label="权限名称" + rules={[{ required: true, message: '请输入权限名称' }]} > - <Input disabled={detailType} placeholder='请输入角色名称' /> + <Input disabled={detailType} placeholder='请输入权限名称' /> </Form.Item> <Form.Item name="tree" diff --git a/management/src/pages/setting/role/index.jsx b/management/src/pages/setting/role/index.jsx index 630cbc7..46a4693 100644 --- a/management/src/pages/setting/role/index.jsx +++ b/management/src/pages/setting/role/index.jsx @@ -14,7 +14,7 @@ const columns = [ { - title: '角色名称', + title: '权限名称', dataIndex: 'name', }, { diff --git a/management/src/pages/setting/user/components/addAndEdit.jsx b/management/src/pages/setting/user/components/addAndEdit.jsx index fb5e4ef..9bc2cb8 100644 --- a/management/src/pages/setting/user/components/addAndEdit.jsx +++ b/management/src/pages/setting/user/components/addAndEdit.jsx @@ -1,7 +1,9 @@ -import { Form, Input, Modal, Tree, Button, Spin, Row, Col, Select, Radio, Cascader } from 'antd'; +import { Form, Input, Modal, Tree, Button, Spin, Row, Col, Select, Radio, Cascader, Card } from 'antd'; import { forwardRef, useImperativeHandle, useState } from 'react'; import { useEffect } from 'react'; -import { getDepartmentList, systemPostList, systemRoleList, getSystemUserInfo, getCityList, addSystemUserInfo, editSystemUserInfo } from '../service'; +import { DeleteOutlined } from '@ant-design/icons'; +import '../index.less' +import { getDepartmentList, systemPostList, systemRoleList, getSystemUserInfo, getCityList, addSystemUserInfo, editSystemUserInfo, getCascaderData } from '../service'; import CryptoJS from 'crypto-js'; const formItemLayout = { labelCol: { span: 8 }, @@ -27,7 +29,10 @@ //所属角色 const [roleList, setRoleList] = useState([]) //账号层级 - const [levelList, setLevelList] = useState([{ name: '市级账号', value: 1 }, { name: '区县账号', value: 2 }, { name: '街道账号', value: 3 }, { name: '社区账号', value: 4 }, { name: '党员账号', value: 5 }]) + const [levelList, setLevelList] = useState([]) + + //级联数据 + const [cascaderData, setCascaderData] = useState([]) //选择的层级 const [activeLevel, setActiveLevel] = useState('') //所属区县 @@ -37,11 +42,11 @@ const [streetList, setStreetList] = useState([]) const [activeStreet, setActiveStreet] = useState({}) //所属社区 - const [communityList, setCommunityList] = useState([]) const [activeCommunity, setActiveCommunity] = useState({}) - - + const [accountLevels, setAccountLevels] = useState([ + { area: [], isDiscipline: 0, options: [] } + ]); useEffect(() => { // 获取单位 @@ -51,63 +56,14 @@ useImperativeHandle(ref, () => { return { refreshData: (data, companyList) => { - getCountyList() + getCascaderDatas() setOneCompanyList(() => companyList) - console.log('companyList', companyList) - systemPostList({ pageNum: 1, pageSize: 10000 }).then(res => { setPositionList(() => res.data.records) }) systemRoleList({ pageNum: 1, pageSize: 10000 }).then(res => { setRoleList(() => res.data.records) }) - // 权限判断 - let adminInfo = JSON.parse(localStorage.getItem('userInfo')) - setAdminLevel(() => adminInfo.accountLevel) - switch (adminInfo.accountLevel) { - case 1: - setLevelList(() => [{ name: '市级账号', value: 1 }, { name: '区县账号', value: 2 }, { name: '街道账号', value: 3 }, { name: '社区账号', value: 4 }, { name: '党员账号', value: 5 }]) - break; - case 2: - getStreetList(adminInfo.districtsCode) - setLevelList(() => [{ name: '区县账号', value: 2 }, { name: '街道账号', value: 3 }, { name: '社区账号', value: 4 }, { name: '党员账号', value: 5 }]) - this.$nextTick(() => { - 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) @@ -128,9 +84,51 @@ // 保存 const okHandle = () => { form.validateFields().then((values) => { - if(values.password){ + // 校验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{ + } else { delete values.password } if (values.DepartmentId) { @@ -144,54 +142,73 @@ onUpdate(values) } else { onSave(values); + setAccountLevels([{ area: [], isDiscipline: 0, options: [] }]); // 清空 } }); }; - const getCountyList = (id) => { - getCityList({ id: '510400', tier: 2 }).then(res => { - setCountyList(() => res.data) - }) - } - const getStreetList = (id) => { - getCityList({ id: id, tier: 3 }).then(res => { - setStreetList(() => res.data) - }) - } - const getcommunityList = (id) => { - getCityList({ id: id, tier: 4 }).then(res => { - setCommunityList(() => res.data) - }) - } - const changeCountry = (value, label) => { - setActiveCounty(label) - getStreetList(value) - form.setFieldsValue({ streetId: '', communityId: '' }) - setActiveCommunity({ name: '', id: '' }) - setActiveStreet({ name: '', id: '' }) + // 获取级联数据 + const getCascaderDatas = () => { + getCascaderData().then(res => { + // 处理级联数据 + const processedData = res.data.map(item => { + // 根据第一级类型处理数据 + if (item.children) { + // 区县级别 + if (item.id == 2) { // 假设type=2表示区县 + // 只保留第一级children + item.children = item.children.map(child => { + const { children, ...childWithoutChildren } = child; + return childWithoutChildren; + }); + } + // 街道级别 + else if (item.id == 3) { // 假设type=3表示街道 + // 保留区县和街道两级 + item.children = item.children.map(child => { + if (child.children) { + child.children = child.children.map(street => { + const { children, ...streetWithoutChildren } = street; + return streetWithoutChildren; + }); + } + return child; + }); + } + // 社区级别 + else if (item.id == 4) { // 假设type=4表示社区 + // 保留所有层级数据 + return item; + } + } + return item; + }); + + let arr = JSON.parse(JSON.stringify(processedData)) + let accountLevels = localStorage.getItem('userInfo') + let accountLevel = JSON.parse(accountLevels).accountLevel + if (accountLevel == 2) { + arr = arr.filter(item => item.id != 1) + } else if (accountLevel == 3) { + arr = arr.filter(item => item.id != 1 && item.id != 2) + } else if (accountLevel == 4) { + arr = arr.filter(item => item.id != 1 && item.id != 2 && item.id != 3) + } + setCascaderData(() => arr); + }); } - const changeStreet = (value, label) => { - setActiveStreet(label) - getcommunityList(value) - form.setFieldsValue({ communityId: '' }) - setActiveCommunity({ name: '', id: '' }) - } - const changeCommunity = (value, label) => { - setActiveCommunity(label) - } + + + + + + + + + + const getInfo = (id) => { getSystemUserInfo(id).then(res => { - if (res.data.districtsCode) { - setActiveStreet({ name: res.data.districts, id: res.data.districtsCode }) - getStreetList(res.data.districtsCode) - } - if (res.data.streetId) { - setActiveCounty({ name: res.data.street, id: res.data.streetId }) - getcommunityList(res.data.streetId) - } - if (res.data.communityId) { - setActiveCommunity({ name: res.data.community, id: res.data.communityId }) - } // delete res.data.password let departmentId = [] @@ -208,8 +225,89 @@ departmentId.push(res.data.fourDepartmentId) } res.data.DepartmentId = departmentId - console.log('departmentId', departmentId) setActiveLevel(() => res.data.accountLevel) + + // 先获取级联数据再回显 + getCascaderData().then(cascadeRes => { + // 处理级联数据 + const processedData = cascadeRes.data.map(item => { + // 根据第一级类型处理数据 + if (item.children) { + // 区县级别 + if (item.id == 2) { // 假设type=2表示区县 + // 只保留第一级children + item.children = item.children.map(child => { + const { children, ...childWithoutChildren } = child; + return childWithoutChildren; + }); + } + // 街道级别 + else if (item.id == 3) { // 假设type=3表示街道 + // 保留区县和街道两级 + item.children = item.children.map(child => { + if (child.children) { + child.children = child.children.map(street => { + const { children, ...streetWithoutChildren } = street; + return streetWithoutChildren; + }); + } + return child; + }); + } + // 社区级别 + else if (item.id == 4) { // 假设type=4表示社区 + // 保留所有层级数据 + return item; + } + } + return item; + }); + + let arr = JSON.parse(JSON.stringify(processedData)) + let accountLevels = localStorage.getItem('userInfo') + let accountLevel = JSON.parse(accountLevels).accountLevel + if (accountLevel == 2) { + arr = arr.filter(item => item.id != 1) + } else if (accountLevel == 3) { + arr = arr.filter(item => item.id != 1 && item.id != 2) + } else if (accountLevel == 4) { + arr = arr.filter(item => item.id != 1 && item.id != 2 && item.id != 3) + } + setCascaderData(() => arr); + + // 回显accountLevels,area始终为4级,id类型统一 + if (res.data.systemUserLevels && Array.isArray(res.data.systemUserLevels)) { + const newAccountLevels = res.data.systemUserLevels.map(level => { + const toId = v => (v === undefined || v === null) ? undefined : String(v); + const area = [ + toId(level.level), + toId(level.twoLevelId || level.districtsCode), + toId(level.threeLevelId || level.streetId), + toId(level.fourLevelId || level.communityId) + ].filter(v => v !== undefined && v !== null && v !== ''); + return { + area, + isDiscipline: Number(level.superviseFlag) === 1 ? 1 : 0, + options: [], + selectedOptions: [ + level.level ? { id: toId(level.level), name: '' } : undefined, + level.twoLevelName ? { id: toId(level.twoLevelId), name: level.twoLevelName } : undefined, + level.threeLevelName ? { id: toId(level.threeLevelId), name: level.threeLevelName } : undefined, + level.fourLevelName ? { id: toId(level.fourLevelId), name: level.fourLevelName } : undefined, + ].filter(Boolean), + level: toId(level.level) || '', + community: level.community || '', + communityId: level.communityId || '', + districts: level.districts || '', + districtsCode: level.districtsCode || '', + id: level.id || '', + street: level.street || '', + streetId: level.streetId || '', + }; + }); + setAccountLevels(newAccountLevels.length ? newAccountLevels : [{ area: [], isDiscipline: 0, options: [], level: '', community: '', communityId: '', districts: '', districtsCode: '', id: '', street: '', streetId: '' }]); + } + }); form.setFieldsValue({ ...res.data, password: '' }) }) @@ -235,13 +333,32 @@ return false } + // 动态添加账号层级项 + const addAccountLevel = () => { + setAccountLevels([...accountLevels, { area: [], isDiscipline: 0, options: [] }]); + }; + // 删除账号层级项 + const removeAccountLevel = (idx) => { + if (accountLevels.length === 1) return; + setAccountLevels(accountLevels.filter((_, i) => i !== idx)); + }; + // 纪检委单选变更 + const handleDisciplineChange = (e, idx) => { + setAccountLevels(levels => { + const newLevels = [...levels]; + newLevels[idx].isDiscipline = e.target.value; + return newLevels; + }); + }; + return ( <Modal getContainer={false} - width="65%" + width="75%" destroyOnClose title={data.type == 'detail' ? '人员详情' : data.type == 'edit' ? '编辑人员' : '添加人员'} open={visible} + className='addAndEditModal' onCancel={() => onCancel(false)} afterClose={() => { form.resetFields() @@ -258,8 +375,8 @@ <Button key="back" onClick={() => onCancel(false)}>关闭</Button> } > - <Form layout="horizontal" {...formItemLayout} form={form} scrollToFirstError> - <Row> + <Form layout="horizontal" form={form} scrollToFirstError labelCol={{ span: 6 }} wrapperCol={{ span: 20 }}> + <Row gutter={16}> <Col span={8}> <Form.Item name="name" @@ -269,90 +386,6 @@ <Input disabled={data.type == 'detail'} placeholder='请输入人员姓名' /> </Form.Item> </Col> - </Row> - <Row> - <Col span={16}> - <Form.Item - name="DepartmentId" - label="所属单位" - labelCol={{ span: 4 }} - rules={[{ required: true, message: '请选择所属单位' }]} - > - {/* <Select - key="searchSelect" - allowClear - disabled={data.type == 'detail'} - placeholder="请选择" - options={oneCompanyList} - fieldNames={{ label: 'name', value: 'id' }} - filterOption={false} - > - </Select > */} - <Cascader - changeSelect - options={oneCompanyList} - fieldNames={{ value: 'key', label: 'name' }} - placeholder="请选择" - // displayRender={(label) => label[label.length - 1]} - changeOnSelect={true} - /> - </Form.Item> - </Col> - {/* <Col span={8}> - <Form.Item - name="twoDepartmentId" - label="所属二级单位" - > - <Select - key="searchSelect" - allowClear - disabled={data.type == 'detail'} - placeholder="请选择" - options={twoCompanyList} - fieldNames={{ label: 'name', value: 'id' }} - filterOption={false} - > - </Select > - </Form.Item> - </Col> - <Col span={8}> - <Form.Item - name="threeDepartmentId" - label="所属三级单位" - > - <Select - key="searchSelect" - allowClear - disabled={data.type == 'detail'} - placeholder="请选择" - options={threeCompanyList} - fieldNames={{ label: 'name', value: 'id' }} - filterOption={false} - > - </Select > - </Form.Item> - </Col> */} - </Row> - {/* <Row> - <Col span={8}> - <Form.Item - name="fourDepartmentId" - label="所属四级单位" - > - <Select - key="searchSelect" - allowClear - disabled={data.type == 'detail'} - placeholder="请选择" - options={fourCompanyList} - fieldNames={{ label: 'name', value: 'id' }} - filterOption={false} - > - </Select > - </Form.Item> - </Col> - </Row> */} - <Row> <Col span={8}> <Form.Item name="systemPostId" @@ -360,197 +393,120 @@ rules={[{ required: true, message: '请选择所属职位' }]} > <Select - key="searchSelect" allowClear disabled={data.type == 'detail'} placeholder="请选择" options={positionList} fieldNames={{ label: 'name', value: 'id' }} filterOption={false} - > - </Select > + /> </Form.Item> </Col> <Col span={8}> <Form.Item name="systemRoleId" - label="所属角色" - rules={[{ required: true, message: '请选择所属角色' }]} + label="后台权限" + rules={[{ required: true, message: '请选择后台权限' }]} > <Select - key="searchSelect" allowClear disabled={data.type == 'detail'} placeholder="请选择" options={roleList} fieldNames={{ label: 'name', value: 'id' }} filterOption={false} - > - </Select > + /> </Form.Item> </Col> </Row> - <Row> + <Row gutter={16}> + <Col span={24}> + <Form.Item + label="账号所属层级" + colon={true} + required + labelCol={{ span: 2 }} + wrapperCol={{ span: 12 }} + style={{ marginBottom: 0 }} + > + <Button type={'primary'} onClick={addAccountLevel} style={{ marginBottom: 8 }}>添加</Button> + {accountLevels.map((item, idx) => ( + <Card key={idx} style={{ marginBottom: 8 }} size='small' > + <div style={{ display: 'flex', alignItems: 'center' }}> + <Cascader + style={{ width: 320, marginRight: 16 }} + placeholder="请选择区域" + options={cascaderData} + fieldNames={{ label: 'name', value: 'id' }} + value={item.area} + onChange={(value, selectedOptions) => { + setAccountLevels(levels => { + const newLevels = [...levels]; + newLevels[idx] = { + ...newLevels[idx], + area: value, + selectedOptions: selectedOptions, + // 清空原有数据,确保新选择的数据生效 + community: '', + communityId: '', + districts: '', + districtsCode: '', + street: '', + streetId: '', + level: selectedOptions?.[0]?.id || '' + }; + return newLevels; + }); + }} + /> + + <div style={{ marginLeft: 16, marginTop: 22 }}> + <span style={{ marginRight: 8 }}>是否为纪检委账号:</span> + <Radio.Group + style={{ marginRight: 16 }} + value={item.isDiscipline ?? 0} + onChange={e => handleDisciplineChange(e, idx)} + options={[ + { label: '否', value: 0 }, + { label: '是', value: 1 } + ]} + /> + </div> + + {idx > 0 && <DeleteOutlined style={{ marginRight: 16 }} onClick={() => removeAccountLevel(idx)} disabled={accountLevels.length === 1} />} + + </div> + </Card> + ))} + </Form.Item> + </Col> + </Row> + <Row gutter={16}> <Col span={8}> <Form.Item - name="accountLevel" - label="账号层级" - rules={[{ required: true, message: '请选择账号层级' }]} - > - <Select - key="searchSelect" - allowClear - onChange={(e) => setActiveLevel(e)} - disabled={data.type == 'detail'} - placeholder="请选择" - options={levelList} - fieldNames={{ label: 'name', value: 'value' }} - // filterOption={false} - > - </Select > - </Form.Item> - </Col> - <Col span={16}> - <Form.Item - label="是否管理员" - labelCol={{ span: 4 }} - wrapperCol={{ span: 20 }} - style={{ marginBottom: 0 }} - > - <div style={{ display: "flex", flex: 1 }}> - <Form.Item - name="isAdmin" - rules={[{ required: true, message: '是否管理员' }]} - > - <Radio.Group - disabled={data.type == 'detail'} - // style={style} - // onChange={onChange} - // value={value} - options={[{ value: 0, label: '否', }, { value: 1, label: '是', }, - - ]} - /> - </Form.Item> - <div style={{ fontSize: '12px', color: "rgba(0,0,0,0.5)", marginLeft: "10px" }}>管理员主要用于接收实现临期提醒,以及上级端登录</div> - </div> - </Form.Item> - </Col> - </Row> - {activeLevel != 1 && ( - <Row> - {[2, 3, 4, 5].includes(activeLevel) && ( - <Col span={8}> - <Form.Item - name="districtsCode" - label="所属区县" - rules={[{ required: true, message: '请选择所属区县' }]} - > - {/* <Select - // onChange={changeCountry} - // value={activeCounty} - placeholder="请选择" - options={levelList} - fieldNames={{ label: 'name', value: 'id' }} - > - </Select > */} - <Select - key="searchSelect" - allowClear - disabled={data.type == 'detail' || [2, 3, 4, 5].includes(adminLevel)} - onChange={changeCountry} - value={activeCounty.id} - placeholder="请选择" - options={countyList} - fieldNames={{ label: 'name', value: 'id' }} - // filterOption={false} - > - </Select > - </Form.Item> - </Col> - )} - {[3, 4, 5].includes(activeLevel) && ( - <Col span={8}> - <Form.Item - name="streetId" - label="所属街道" - rules={[{ required: true, message: '请选择所属街道' }]} - > - <Select - onChange={changeStreet} - disabled={!activeCounty || data.type == 'detail' || [3, 4, 5].includes(adminLevel)} - key="searchSelect" - allowClear - value={activeStreet.id} - placeholder="请选择" - options={streetList} - fieldNames={{ label: 'name', value: 'id' }} - > - </Select > - </Form.Item> - </Col> - )} - - {[4, 5].includes(activeLevel) && ( - <Col span={8}> - <Form.Item - name="communityId" - label="所属社区" - rules={[{ required: true, message: '请选择所属社区' }]} - > - <Select - onChange={changeCommunity} - disabled={!activeStreet || data.type == 'detail' || [4, 5].includes(adminLevel)} - key="searchSelect" - allowClear - value={activeCommunity.id} - placeholder="请选择" - options={communityList} - fieldNames={{ label: 'name', value: 'id' }} - > - </Select > - </Form.Item> - </Col> - )} - </Row> - )} - - <Row> - <Col span={16}> - <Form.Item label="联系方式" - required - labelCol={{ span: 4 }} - wrapperCol={{ span: 20 }} - style={{ marginBottom: 0 }} + name="phone" + extra={'联系方式将作为登录账号使用'} + rules={[{ + validator: (rule, value) => { + return new Promise((resolve, reject) => { + if (!value) { + reject('请输入联系方式'); + } + const phoneRegex = /^((\+86)?(13|14|15|16|17|18|19)[0-9]{9})|((\+86)?(0[0-9]{2,3})?([2-9][0-9]{6,7}))$/; + if (!phoneRegex.test(value)) { + reject('请输入正确的电话号码'); + } + resolve(''); + }); + }, + }]} > - <div style={{ display: "flex", flex: 1 }}> - <Form.Item - name="phone" - - rules={[{ - validator: (rule, value) => { - return new Promise((resolve, reject) => { - if (!value) { - reject('请输入联系方式'); - } - const phoneRegex = /^((\+86)?(13|14|15|16|17|18|19)[0-9]{9})|((\+86)?(0[0-9]{2,3})?([2-9][0-9]{6,7}))$/; - if (!phoneRegex.test(value)) { - reject('请输入正确的电话号码'); - } - resolve(''); - }); - }, - }]} - > - <Input disabled={data.type == 'detail'} placeholder='请输入联系方式' /> - </Form.Item> - <div style={{ fontSize: '12px', color: "rgba(0,0,0,0.5)", marginLeft: "10px" }}>联系方式将作为登录账号使用</div> - </div> + <Input disabled={data.type == 'detail'} placeholder='请输入联系方式' /> </Form.Item> </Col> </Row> - <Row> + <Row gutter={16}> <Col span={8}> <Form.Item required={data.type == 'add'} @@ -574,39 +530,32 @@ <Input.Password disabled={data.type == 'detail'} placeholder='请输入' /> </Form.Item> </Col> - <Col span={16}> + </Row> + <Row gutter={16}> + <Col span={8}> <Form.Item label="确认密码" + name="surePassword" + extra={'密码需要包含大小写字母,数字和特殊符号,且长度为8位以上'} required={data.type == 'add'} - labelCol={{ span: 4 }} - wrapperCol={{ span: 20 }} - style={{ marginBottom: 0 }} + rules={[{ + validator: (rule, value) => { + return new Promise((resolve, reject) => { + if (!value && data.type == 'add') { + reject('请再次输入新密码'); + } + if (value != form.getFieldValue('password') && data.type == 'add') { + reject('两次密码请保持一致'); + } + resolve(''); + }); + }, + }]} > - <div style={{ display: "flex", flex: 1 }}> - <Form.Item - name="surePassword" - rules={[{ - validator: (rule, value) => { - return new Promise((resolve, reject) => { - if (!value && data.type == 'add') { - reject('请再次输入新密码'); - } - if (value != form.getFieldValue('password') && data.type == 'add') { - reject('两次密码请保持一致'); - } - resolve(''); - }); - }, - },]} - > - <Input.Password disabled={data.type == 'detail'} placeholder='请输入' /> - </Form.Item> - <div style={{ fontSize: '12px', color: "rgba(0,0,0,0.5)", marginLeft: "10px" }}>密码需要包含大小写字母,数字和特殊符号,且长度为8位以上</div> - </div> + <Input.Password disabled={data.type == 'detail'} placeholder='请输入' /> </Form.Item> </Col> </Row> - </Form> <Spin spinning={spinning} fullscreen /> diff --git a/management/src/pages/setting/user/index.jsx b/management/src/pages/setting/user/index.jsx index d159be5..c0ab038 100644 --- a/management/src/pages/setting/user/index.jsx +++ b/management/src/pages/setting/user/index.jsx @@ -1,7 +1,7 @@ import { buildProTableDataSource, sendRequest, showDelConfirm, showConfirm } from '@/utils/antdUtils'; import { DeleteOutlined, EditOutlined, PlusOutlined } from '@ant-design/icons'; -import { PageContainer, ProFormText, ProTable, QueryFilter } from '@ant-design/pro-components'; -import { Button, Cascader, Col, Menu, Row, Select, Space } from 'antd'; +import { PageContainer, ProFormText, ProTable, QueryFilter, ProFormSelect } from '@ant-design/pro-components'; +import { Button, Cascader, Col, Menu, Row, Select, Space, Form } from 'antd'; import { useEffect, useRef, useState } from 'react'; import { Access, useAccess } from 'umi'; import AddAndEdit from './components/addAndEdit'; @@ -21,6 +21,7 @@ } from './service'; const Role = () => { + const [form] = Form.useForm(); const actionRef = useRef(); const addViewRef = useRef(); const addViewRef1 = useRef(); @@ -48,7 +49,7 @@ }} > <span>{item.name}</span> - <div> + {/* <div> {item.tier < 4 && ( <Access accessible={access['/system_setting/unit_management/add']}> <PlusOutlined @@ -82,31 +83,40 @@ }} /> </Access> - </div> + </div> */} </div > ); }; - const renderMenuItems = (items) => { + const renderMenuItems = (items, level = 0) => { return items.map((item) => { if (item.children && item.children.length > 0) { return ( <SubMenu key={item.key} title={node(item)} - onTitleClick={(item) => { - setUnitId(item.key); - actionRef.current.reload(); + onTitleClick={(e) => { + if (e && e.domEvent) { + e.domEvent.stopPropagation(); + } + setUnitId(item.id); + const values = form.getFieldsValue(); + actionRef.current.reload(values); }} > - {renderMenuItems(item.children)} + {renderMenuItems(item.children, level + 1)} </SubMenu> ); } return ( <Menu.Item - onClick={(item) => { - setUnitId(item.key); - actionRef.current.reload(); + onClick={(e) => { + if (e && e.domEvent) { + e.domEvent.stopPropagation(); + } + const itemId = item.id || item.key; + setUnitId(itemId); + const values = form.getFieldsValue(); + actionRef.current.reload(values); }} key={item.key} > @@ -120,6 +130,8 @@ if (res.code == 200 && res.data) { const traverseItems = (items) => { return items.map((item) => { + if (!item.id) { + } item.key = item.id; item.title = '1'; if (item.child && item.child.length > 0) { @@ -128,7 +140,8 @@ return item; }); }; - setItems(traverseItems(res.data)); + const processedItems = traverseItems(res.data); + setItems(processedItems); } }); }; @@ -141,50 +154,50 @@ title: '联系方式', dataIndex: 'phone', }, - { - title: '所在单位', - dataIndex: 'departmentName', - hideInSearch: true, - renderFormItem: () => { - return ( - <Cascader - options={items} - fieldNames={{ value: 'key', label: 'name' }} - placeholder="请选择" - displayRender={(label) => label[label.length - 1]} - changeOnSelect={true} - /> - ); - }, - }, - { - hideInTable: true, - title: '所在单位', - dataIndex: 'departmentId', - renderFormItem: () => { - return ( - <Cascader - options={items} - fieldNames={{ value: 'key', label: 'name' }} - placeholder="请选择" - displayRender={(label) => label[label.length - 1]} - changeOnSelect={true} - /> - ); - }, - }, + // { + // title: '所在单位', + // dataIndex: 'departmentName', + // hideInSearch: true, + // renderFormItem: () => { + // return ( + // <Cascader + // options={items} + // fieldNames={{ value: 'key', label: 'name' }} + // placeholder="请选择" + // displayRender={(label) => label[label.length - 1]} + // changeOnSelect={true} + // /> + // ); + // }, + // }, + // { + // hideInTable: true, + // title: '所属权限', + // dataIndex: 'departmentId', + // renderFormItem: () => { + // return ( + // <Cascader + // options={items} + // fieldNames={{ value: 'key', label: 'name' }} + // placeholder="请选择" + // displayRender={(label) => label[label.length - 1]} + // changeOnSelect={true} + // /> + // ); + // }, + // }, { title: '所属职位', dataIndex: 'systemPostName', hideInSearch: true, }, { - title: '所属角色', + title: '后台权限', dataIndex: 'systemRoleName', hideInSearch: true, }, { - title: '所属角色', + title: '所属权限', dataIndex: 'systemRoleId', hideInTable: true, renderFormItem: () => { @@ -201,31 +214,24 @@ }, }, { - title: '账号层级', + title: '账号所属层级', dataIndex: 'accountLevel', // (1=市级账号,2=区县账号,3=街道账号,4=社区账号) render: (text, record) => { - let role = ''; - switch (record.accountLevel) { - case 1: - role = '市'; - break; - case 2: - role = '区县'; - break; - case 3: - role = '街道'; - break; - case 4: - role = '社区'; - break; - case 5: - role = '党员'; - break; - default: - role = ''; - } - return role; + record.list.length>0&&record.list.map(item=>{ + item.str = '' + if(item.level==1){ + item.str = '市' + }else if(item.level==2){ + item.str = '区县' + '/' + item.districts + }else if(item.level==3){ + item.str = '街道' + '/' + item.districts + '/' + item.street + }else{ + item.str = '社区' + '/' + item.districts + '/' + item.street + '/' + item.community + } + return item.str; + }) + return record.list.map(item=>item.str).join('、'); }, valueEnum: { 1: '市', @@ -265,7 +271,7 @@ <Access accessible={access['/system_setting/people_management/edit']}> <a onClick={() => { - addViewRef.current.refreshData({ ...record, type: 'edit' },items); + addViewRef.current.refreshData({ ...record, type: 'edit' }, items); handleModalVisibles(true); }} > @@ -292,7 +298,7 @@ <Access accessible={access['/system_setting/people_management/detail']}> <a onClick={() => { - addViewRef.current.refreshData({ ...record, type: 'detail' },items); + addViewRef.current.refreshData({ ...record, type: 'detail' }, items); handleModalVisibles(true); }} > @@ -302,7 +308,7 @@ <Access accessible={access['/system_setting/people_management/freeze']}> <a onClick={() => { - showConfirm(`确认${record.status === 1 ? '冻结' : '解冻'}该人员吗?`,'', async () => { + showConfirm(`确认${record.status === 1 ? '冻结' : '解冻'}该人员吗?`, '', async () => { let status = await sendRequest( record.status === 1 ? freezeApi : unfreezeApi, record.id, @@ -331,6 +337,8 @@ > <div style={{ background: '#fff' }}> <QueryFilter + form={form} + labelWidth={100} onReset={(values) => { fetchUnit(values); setUnitId(''); @@ -342,14 +350,21 @@ actionRef.current.reload(); }} > - <ProFormText name="name" label="单位名称" /> + <ProFormText name="name" label="组织结构名称" /> + <ProFormSelect name="type" label="筛选维度" options={[{ + label: '当前组织结构', + value: 1, + }, { + label: '当前及下级组织结构', + value: 2, + }]} /> </QueryFilter> </div> <Row style={{ marginTop: 20, background: '#fff' }}> <Col span={4}> <Space style={{ margin: '10px 0' }}> - <span style={{ margin: '0 27px' }}>单位管理</span> - <Button + <span style={{ margin: '0 27px' }}>组织结构</span> + {/* <Button type="primary" onClick={() => { addViewRef1.current.refreshData({}); @@ -357,10 +372,17 @@ }} > 添加 - </Button> + </Button> */} </Space> - <Menu mode="inline">{renderMenuItems(items)}</Menu> + <Menu + mode="inline" + onClick={({ key, domEvent }) => { + domEvent.stopPropagation(); + }} + > + {renderMenuItems(items)} + </Menu> </Col> <Col span={20} style={{ minHeight: 650 }}> <ProTable @@ -385,6 +407,9 @@ if (params.departmentId) { obj.departmentId = params.departmentId[params.departmentId.length - 1]; } + if (form.getFieldValue('type')) { + obj.type = form.getFieldValue('type'); + } return buildProTableDataSource(getList, obj); }} toolBarRender={(action, selectRows) => [ @@ -393,7 +418,7 @@ <Button type="primary" onClick={() => { - addViewRef.current.refreshData({ type: 'add', unitId : unitId },items); + addViewRef.current.refreshData({ type: 'add', unitId: unitId }, items); handleModalVisibles(true); }} > diff --git a/management/src/pages/setting/user/index.less b/management/src/pages/setting/user/index.less new file mode 100644 index 0000000..17eba53 --- /dev/null +++ b/management/src/pages/setting/user/index.less @@ -0,0 +1,5 @@ +.addAndEditModal{ + .ant-modal-body{ + padding: 0 !important; + } +} \ No newline at end of file diff --git a/management/src/pages/setting/user/service.js b/management/src/pages/setting/user/service.js index d300fa1..ae7691e 100644 --- a/management/src/pages/setting/user/service.js +++ b/management/src/pages/setting/user/service.js @@ -134,4 +134,12 @@ return request(`/api/huacheng-sangeshenbian/systemUser/unfreeze/${id}`, { method: 'PUT', }); -} \ No newline at end of file +} + + +//获取级联数据 +export const getCascaderData = async (data) => { + return request(`/api/huacheng-sangeshenbian/systemUser/getAdministrativeDivisionTwo`, { + method: 'GET', + }); +} diff --git a/management/src/requestErrorConfig.ts b/management/src/requestErrorConfig.ts index 10d9de4..cf32d48 100644 --- a/management/src/requestErrorConfig.ts +++ b/management/src/requestErrorConfig.ts @@ -26,7 +26,7 @@ * @doc https://umijs.org/docs/max/request#配置 */ export const errorConfig: RequestConfig = { - baseURL: BASE_URL, + // baseURL: BASE_URL, // 请求拦截器 requestInterceptors: [ -- Gitblit v1.7.1