Kaynağa Gözat

为新增投诉,留言和企业自主上报增加滑动框验证

nahida 1 yıl önce
ebeveyn
işleme
54bb2bdafe

+ 1 - 0
components.d.ts

@@ -34,6 +34,7 @@ declare module 'vue' {
     NewDynamics: typeof import('./src/components/NewDynamics.vue')['default']
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']
+    SliderVerify: typeof import('./src/components/SliderVerify.vue')['default']
     SmallSearch: typeof import('./src/components/SmallSearch.vue')['default']
   }
   export interface ComponentCustomProperties {

+ 5 - 0
env.d.ts

@@ -33,4 +33,9 @@ declare interface PageResponse{
   searchCount: boolean,
   size:number,
   total: number
+}
+
+declare interface SliderVerify{
+  imageKey?:string,
+  imageCode?:number
 }

+ 132 - 0
src/components/SliderVerify.vue

@@ -0,0 +1,132 @@
+<template>
+  <div class="slider-verify-container w-320px my-3" v-loading="isResetting">
+    <div class="relative">
+      <img :src="captcha.canvasSrc" alt="验证图" draggable="false"/>
+      <img
+        ref="blockImage"
+        :src="captcha.blockSrc"
+        class="absolute left-0"
+        :style="{top: captcha.blockY + 'px', transform: `translateX(${blockX}px)`}"
+        alt="缺失的块"
+        draggable="false"
+      />
+    </div>
+    <div class="relative bg-white text-center line-height-50px h-50px shadow-inset shadow-black shadow-sm bg-opacity-50">
+      <span v-show="!isDragging">向右拖动滑块来验证</span>
+      <div
+        ref="sliderBlock"
+        class="absolute top-0 left-0 mt-5px ml-8px w-49px h-40px bg-blue cursor-pointer rounded-5px"
+        :class="{'shadow-inset shadow-black shadow-sm': isDragging,'cursor-not-allowed':isResetting}"
+        :style="{transform: `translateX(${currentX}px)`}"
+        @mousedown="startDrag"
+        @mousemove="onDrag"
+        @mouseup="endDrag"
+      ></div>
+    </div>
+    <div class="text-black text-5 rounded-50%">
+      <el-icon class="cursor-pointer hover:text-cyan" @click="reset">
+        <RefreshRight />
+      </el-icon>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import request from '@/utils/request'
+import { ElMessage } from 'element-plus'
+import { ref } from 'vue'
+import { RefreshRight } from '@element-plus/icons-vue'
+
+interface Captcha {
+  nonceStr: string,
+  value: string,
+  canvasSrc: string,
+  canvasWidth: number,
+  canvasHeight: number,
+  blockSrc: string,
+  blockWidth: number,
+  blockHeight: number,
+  blockRadius: number,
+  blockX: number,
+  blockY: number,
+  place: number,
+}
+interface SliderVerifyResponse extends AxiosResponse{
+  data: Captcha
+}
+const captcha = ref<Captcha>({
+  blockHeight: 0,
+  blockRadius: 0,
+  blockSrc: '',
+  blockWidth: 0,
+  blockX: 0,
+  blockY: 0,
+  canvasHeight: 0,
+  canvasSrc: '',
+  canvasWidth: 0,
+  nonceStr: '',
+  place: 0,
+  value: ''
+})
+const sliderBlock = ref<HTMLElement | null>(null)
+const blockImage = ref<HTMLElement | null>(null)
+const isDragging = ref(false);
+const startX = ref(0)
+const currentX = ref(0)
+const blockX = ref(0)
+const isResetting = ref(false);
+const startTime = ref(0);
+const emits = defineEmits(['verify','resetSlider']);
+const getImg = async () => {
+  const res = await request.post<Captcha, SliderVerifyResponse>("/captchaSlider/getCaptcha", {})
+  if (res.code !== 200) {
+    ElMessage.error("当次获取验证图失败")
+  }
+  captcha.value = res.data
+  blockX.value = captcha.value.blockX
+}
+
+const init = () => {
+  getImg()
+}
+
+const startDrag = (event: MouseEvent) => {
+  startTime.value = Date.now()
+  if (sliderBlock.value && !isResetting.value) {
+    isDragging.value = true
+    startX.value = event.clientX - sliderBlock.value.offsetLeft
+  }
+}
+
+const onDrag = (event: MouseEvent) => {
+  event.preventDefault()
+  if (isDragging.value && sliderBlock.value) {
+    currentX.value = event.clientX - startX.value
+    if (currentX.value < 0) currentX.value = 0
+    if (currentX.value > 252) currentX.value = 252
+    blockX.value = currentX.value
+  }
+}
+
+const endDrag = () => {
+  if(Date.now() - startTime.value > 10000){
+    emits('resetSlider');
+    return
+  }
+  if (isDragging.value) {
+    emits('verify', captcha.value.nonceStr, currentX.value);
+    isDragging.value = false
+    isResetting.value = true
+  }
+}
+
+const reset =async ()=>{
+  await getImg();
+  isResetting.value = false
+  isDragging.value = false
+  blockX.value = 0;
+  currentX.value = 0;
+}
+defineExpose({reset});
+init()
+</script>

+ 9 - 9
src/components/base/BaseContainer.vue

@@ -26,40 +26,40 @@ watch(() => props.showData, (newValue) => {
   if(newValue.records){
     // console.log('这是records类型的详情')
     // console.log(newValue.records)
-    newValue.records.forEach(item=>{
+    newValue.records.forEach((item:any)=>{
       const map = new Map()
       for (let itemKey in item) {
         if(itemKey == 'id') continue;
         if(dialogMap.get(itemKey) == '录入时间'){
-          map.set(dialogMap.get(itemKey),item[itemKey].split('T')[0])
+          map.set(dialogMap.get(itemKey),item[itemKey]?.split('T')[0])
           continue;
         }
         if(dialogMap.get(itemKey) == '统计时间'){
-          map.set(dialogMap.get(itemKey),item[itemKey].split('T')[0])
+          map.set(dialogMap.get(itemKey),item[itemKey]?.split('T')[0])
           continue;
         }
         if(dialogMap.get(itemKey) == '许可决定日期'){
-          map.set(dialogMap.get(itemKey),item[itemKey].split('T')[0])
+          map.set(dialogMap.get(itemKey),item[itemKey]?.split('T')[0])
           continue;
         }
         if(dialogMap.get(itemKey) == '有效起始日期'){
-          map.set(dialogMap.get(itemKey),item[itemKey].split('T')[0])
+          map.set(dialogMap.get(itemKey),item[itemKey]?.split('T')[0])
           continue;
         }
         if(dialogMap.get(itemKey) == '有效截至日期'){
-          map.set(dialogMap.get(itemKey),item[itemKey].split('T')[0])
+          map.set(dialogMap.get(itemKey),item[itemKey]?.split('T')[0])
           continue;
         }
         if(dialogMap.get(itemKey) == '立案时间'){
-          map.set(dialogMap.get(itemKey),item[itemKey].split('T')[0])
+          map.set(dialogMap.get(itemKey),item[itemKey]?.split('T')[0])
           continue;
         }
         if(dialogMap.get(itemKey) == '处罚决定日期'){
-          map.set(dialogMap.get(itemKey),item[itemKey].split('T')[0])
+          map.set(dialogMap.get(itemKey),item[itemKey]?.split('T')[0])
           continue;
         }
         if(dialogMap.get(itemKey) == '处罚有效期'){
-          map.set(dialogMap.get(itemKey),item[itemKey].split('T')[0])
+          map.set(dialogMap.get(itemKey),item[itemKey]?.split('T')[0])
           continue;
         }
         if(dialogMap.get(itemKey) == '法定代表人证件号码'){

+ 8 - 5
src/layout/index.vue

@@ -4,11 +4,14 @@ import FooterBlock from '@/layout/component/FooterBlock.vue'
 </script>
 
 <template>
-  <head-block />
-  <transition enter-active-class="animate__animated animate__fadeIn" mode="out-in">
-    <RouterView />
-  </transition>
-   <FooterBlock />
+  <div>
+
+    <head-block />
+    <transition enter-active-class="animate__animated animate__fadeIn" mode="out-in">
+      <RouterView />
+    </transition>
+    <FooterBlock />
+  </div>
 </template>
 
 <style scoped>

+ 6 - 6
src/utils/request.ts

@@ -113,10 +113,10 @@ service.interceptors.response.use(
       })
       return Promise.reject(new Error(msg))
     } else if (code !== 200) {
-      ElNotification.error({
-        title: msg
-      })
-      return Promise.reject('error')
+      // ElNotification.error({
+      //   title: msg
+      // })
+      return Promise.resolve(res.data)
     } else {
       return Promise.resolve(res.data)
     }
@@ -142,8 +142,8 @@ service.interceptors.response.use(
 
 export async function downloadFile(url:string, fileName:string) {
   try {
-    const response = await axios.get(url, { responseType: 'blob' }); // 设置响应类型为blob
-    const urlForDownload = window.URL.createObjectURL(new Blob([response.data]));
+    const response:any = await service.get(url, { responseType: 'blob' }); // 设置响应类型为blob
+    const urlForDownload = window.URL.createObjectURL(new Blob([response]));
     const link = document.createElement('a');
     link.href = urlForDownload;
     link.download = fileName; // 指定下载的文件名

+ 34 - 3
src/views/complaint/index.vue

@@ -3,6 +3,7 @@ import { reactive, ref, toRaw } from 'vue'
 import { ElMessage, type FormProps, type UploadFile, type FormRules, type FormInstance } from 'element-plus'
 import { type complaintData, delFileById, getDict, saveComplaint } from '@/views/complaint/type'
 import { useRouter } from 'vue-router'
+import SliderVerify from '@/components/SliderVerify.vue'
 
 const labelPosition = ref<FormProps['labelPosition']>('right')
 const fileList = ref([])
@@ -10,6 +11,8 @@ const complaintFormRef = ref<FormInstance>();
 const dickArr = ref<any>([]);
 const dickMap = new Map();
 const router = useRouter()
+const sliderVerifyRef = ref();
+const verifyVisible = ref(false)
 const complaintForm = reactive<complaintData>({
   name: '',
   phone: '',
@@ -50,13 +53,21 @@ const uploadComplaint = (formEl: FormInstance | undefined)=>{
   formEl.validate(async (valid) => {
     if (valid) {
       const res = await saveComplaint(toRaw(complaintForm));
+      if (res.code == 999) {
+        ElMessage.warning(res.msg);
+        sliderVerifyRef.value.reset();
+        return;
+      }
       if(res.code == 200){
         ElMessage.success(res.msg)
+        sliderVerifyRef.value.reset();
+        verifyVisible.value = false
         complaintFormRef.value?.resetFields()
         setTimeout(() => {
           router.push('/home')
         }, 1000)
       }
+      sliderVerifyRef.value.reset();
     }
   })
 }
@@ -111,6 +122,23 @@ const init =async ()=>{
     dickMap.set(k,v)
   })
 }
+const verify = (k: string, c: number) => {
+  complaintForm.imageKey = k
+  complaintForm.imageCode = c
+  uploadComplaint(complaintFormRef.value)
+}
+const resetSlider = ()=>{
+  ElMessage.warning('验证时长太长,请重新验证')
+  sliderVerifyRef.value.reset();
+}
+const verifyCode = (formEl: FormInstance | undefined) => {
+  if (!formEl) return
+  formEl.validate(async (valid) => {
+    if (valid) {
+      verifyVisible.value = true
+    }
+  })
+}
 init();
 
 </script>
@@ -183,13 +211,16 @@ init();
           >
             <el-icon><Plus /></el-icon>
           </el-upload>
-
-
         </el-form-item>
-        <el-button type="primary" @click="uploadComplaint(complaintFormRef)">提交投诉</el-button>
+        <el-button type="primary" @click="verifyCode(complaintFormRef)">提交投诉</el-button>
         <el-button @click="resetForm(complaintFormRef)">重置</el-button>
       </el-form>
     </div>
+    <el-dialog v-model="verifyVisible" title="滑动验证" draggable width="355">
+      <template #default>
+        <SliderVerify ref="sliderVerifyRef" @verify="verify" @resetSlider="resetSlider" />
+      </template>
+    </el-dialog>
   </div>
 </template>
 

+ 1 - 1
src/views/complaint/type/index.ts

@@ -5,7 +5,7 @@ enum API {
   DELETE_BY_ID_FILE = '/commonFile/deleteFileById'
 }
 
-export interface complaintData {
+export interface complaintData extends SliderVerify{
   "name": string,
   "phone": string,
   "remark": string,

+ 38 - 3
src/views/creditServices/index.vue

@@ -7,19 +7,24 @@ import SmallSearch from '@/components/SmallSearch.vue'
 import NewDynamics from '@/components/NewDynamics.vue'
 import { downloadFile } from '@/utils/request'
 import { useRouter } from 'vue-router'
+import SliderVerify from '@/components/SliderVerify.vue'
 
 const router = useRouter()
 
 const labelPosition = ref<FormProps['labelPosition']>('left')
 const selfReportRef = ref<FormInstance>();
 const uploadFilesList = ref<any>([]);
+const sliderVerifyRef = ref();
+const verifyVisible = ref(false)
 const selfReportForm = ref({
   contactInformation: '',
   contacts: '',
   enterpriseName: '',
   unifiedSocialCreditCode:'',
   complainantIdNumber:'',
-  files:uploadFilesList.value
+  files:uploadFilesList.value,
+  imageKey:'',
+  imageCode:0,
 })
 const rules = reactive<FormRules<typeof  selfReportForm.value>>({
   contactInformation: [
@@ -60,14 +65,22 @@ const submitReportForm = ()=>{
   selfReportRef.value?.validate( async (valid) => {
     if (valid) {
       const res = await saveSelfReport(selfReportForm.value);
+      if (res.code == 999) {
+        ElMessage.warning(res.msg);
+        sliderVerifyRef.value.reset();
+        return;
+      }
       if(res.code == 200){
         ElMessage.success(res.msg+"即将返回首页");
+        sliderVerifyRef.value.reset();
+        verifyVisible.value = false
         setTimeout(()=>{
           router.push('/home')
         },2000)
         canSelfReport.value = false;
         reset()
       }
+      sliderVerifyRef.value.reset();
     }
   })
 }
@@ -112,7 +125,7 @@ const beforeUpload = (q:UploadRawFile)=>{
   if(q.size > 1024*1024*30){
     ElMessage.error('文件大于了30M')
     return false;
-  };
+  }
   isloading.value = true
 }
 const checkInformation = async (unicode:string)=>{
@@ -129,6 +142,23 @@ const checkInformation = async (unicode:string)=>{
     dialogVisible.value = true;
   }
 }
+const resetSlider = ()=>{
+  ElMessage.warning('验证时长太长,请重新验证')
+  sliderVerifyRef.value.reset();
+}
+const verifyCode = (formEl: FormInstance | undefined) => {
+  if (!formEl) return
+  formEl.validate(async (valid) => {
+    if (valid) {
+      verifyVisible.value = true
+    }
+  })
+}
+const verify = (k: string, c: number) => {
+  selfReportForm.value.imageKey = k
+  selfReportForm.value.imageCode = c
+  submitReportForm()
+}
 const dialogVisible = ref(false);
 const companyUploadInformationList = ref<any>([]);
 </script>
@@ -197,7 +227,7 @@ const companyUploadInformationList = ref<any>([]);
                   </div>
                 </template>
               </el-upload>
-              <el-button @click="submitReportForm" type="primary" :disabled="!canSelfReport">上报</el-button>
+              <el-button @click="verifyCode(selfReportRef)" type="primary" :disabled="!canSelfReport">上报</el-button>
               <el-button @click="reset">重置</el-button>
             </el-form>
           </div>
@@ -226,6 +256,11 @@ const companyUploadInformationList = ref<any>([]);
         </div>
       </template>
     </el-dialog>
+    <el-dialog v-model="verifyVisible" title="滑动验证" draggable width="355">
+      <template #default>
+        <SliderVerify ref="sliderVerifyRef" @verify="verify" @resetSlider="resetSlider" />
+      </template>
+    </el-dialog>
   </div>
 </template>
 

+ 4 - 2
src/views/creditServices/type/index.ts

@@ -4,7 +4,7 @@ import request from '@/utils/request'
 enum API {
   SAVE_SELF_REPORT = '/owSelfReporting/save',
 }
-export interface SelfReportResponseData{
+export interface SelfReportResponseData extends SliderVerify{
   contactInformation:string,
   contacts:string,
   enterpriseName:string,
@@ -24,7 +24,9 @@ export const saveSelfReport = (selfReportData:SelfReportResponseData)=>{
     complainantIdNumber,
     enterpriseName,
     unifiedSocialCreditCode,
-    files
+    files,
+    imageKey:selfReportData.imageKey,
+    imageCode:selfReportData.imageCode
   })
 }
 

+ 41 - 10
src/views/message/index.vue

@@ -2,21 +2,25 @@
 import { getDict, type messageData, saveMessage } from '@/views/message/type'
 import { reactive, ref, toRaw, watch } from 'vue'
 import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
-import { saveComplaint } from '@/views/complaint/type'
 import { useRouter } from 'vue-router'
+import SliderVerify from '@/components/SliderVerify.vue'
 
 const dickArr = ref<{ dictValue: string, dictLabel: string }[]>([{ dictLabel: '', dictValue: '' }])
 const dickMap = ref()
-const messageFormRef = ref()
+const messageFormRef = ref<FormInstance>()
+const verifyVisible = ref(false)
 const router = useRouter()
+const sliderVerifyRef = ref();
 const messageForm = reactive<messageData>({
-  card: '',
-  content: '',
+  card: '430111199911111111',
+  content: '123',
   enterpriseName: '',
-  name: '',
-  phone: '',
+  name: '123',
+  phone: '13245678910',
   type: 'personal_message',
-  unifiedSocialCreditCode: ''
+  unifiedSocialCreditCode: '',
+  imageCode: 0,
+  imageKey: ''
 })
 const unifiedSocialCreditCodeRuleValid = (rule: any, value: any, callback: any) => {
   if (messageForm.type == 'personal_message') callback()
@@ -67,12 +71,15 @@ const rules = reactive<FormRules<typeof messageForm>>({
   ]
 })
 const uploadMessage = (formEl: FormInstance | undefined) => {
-  // console.log(messageForm)
-  // console.log(formEl)
   if (!formEl) return
   formEl.validate(async (valid) => {
     if (valid) {
       const res = await saveMessage(toRaw(messageForm))
+      if (res.code == 999) {
+        ElMessage.warning(res.msg);
+        sliderVerifyRef.value.reset();
+        return;
+      }
       if (res.code == 200) {
         ElMessage({
           message: res.msg,
@@ -88,13 +95,32 @@ const uploadMessage = (formEl: FormInstance | undefined) => {
           type: 'error'
         })
       }
+      sliderVerifyRef.value.reset();
+      verifyVisible.value = false
     }
   })
 }
+const verifyCode = (formEl: FormInstance | undefined) => {
+  if (!formEl) return
+  formEl.validate(async (valid) => {
+    if (valid) {
+      verifyVisible.value = true
+    }
+  })
+}
+const verify = (k: string, c: number) => {
+  messageForm.imageKey = k
+  messageForm.imageCode = c
+  uploadMessage(messageFormRef.value)
+}
 const resetForm = (formEl: FormInstance | undefined) => {
   if (!formEl) return
   formEl.resetFields()
 }
+const resetSlider = ()=>{
+  ElMessage.warning('验证时长太长,请重新验证')
+  sliderVerifyRef.value.reset();
+}
 const init = async () => {
   let res = await getDict('message_type')
   dickArr.value = res.data
@@ -148,10 +174,15 @@ init()
         <el-form-item label="内容" prop="content">
           <el-input v-model="messageForm.content" type="textarea" rows="8" placeholder="请输入留言内容" />
         </el-form-item>
-        <el-button type="primary" @click="uploadMessage(messageFormRef)">提交投诉</el-button>
+        <el-button type="primary" @click="verifyCode(messageFormRef)">提交投诉</el-button>
         <el-button @click="resetForm(messageFormRef)">重置</el-button>
       </el-form>
     </div>
+    <el-dialog v-model="verifyVisible" title="滑动验证" draggable width="355">
+      <template #default>
+        <SliderVerify ref="sliderVerifyRef" @verify="verify" @resetSlider="resetSlider" />
+      </template>
+    </el-dialog>
   </div>
 </template>
 

+ 4 - 3
src/views/message/type/index.ts

@@ -4,15 +4,16 @@ import request from '@/utils/request'
 enum API{
   SAVE_MESSAGE = '/owLeavingMessage/save'
 }
-export interface messageData {
+export interface messageData extends SliderVerify{
   "card": string,
   "content": string,
-  "enterpriseName": string,
+  "enterpriseName"?: string,
   "name": string,
   "phone": string,
   "type": string,
-  "unifiedSocialCreditCode": string,
+  "unifiedSocialCreditCode"?: string,
 }
+
 export const saveMessage = (data:messageData)=> {
   return request<any,any>({
     url:API.SAVE_MESSAGE,

+ 1 - 1
vite.config.ts

@@ -32,7 +32,7 @@ export default defineConfig({
   server:{
     proxy:{
       '/api':{
-        target:'http://192.168.110.235:20002',
+        target:'http://192.168.110.13:20002',
         changeOrigin:true,
         rewrite:path=>path.replace(/^\/api/,'')
       }