Explorar el Código

修改卡清单卡资费资费名称

吴sir hace 2 meses
padre
commit
c7d5a7c611

+ 1 - 1
src/App.vue

@@ -29,7 +29,7 @@ provide('reloadRoutePage', () => {
   })
 })
 
-
+// 获取用户ip地址 
 async function getIpClient() {
     try {
         const response = await axios.get('https://ipinfo.io/json');

+ 73 - 0
src/components/Steps/StepButton.vue

@@ -0,0 +1,73 @@
+<template>
+    <div class="step-footer">
+        <a-space size="large">
+            <template v-if="isShowArray">
+                <a-button type="secondary" :disabled="current <= 1" @click="onPrev">
+                    <IconLeft /> 上一步
+                </a-button>
+                <a-button type="primary" :disabled="current >= maxStep" @click="onNext" v-if="maxStep !== current">
+                    下一步
+                    <IconRight />
+                </a-button>
+
+                <a-button type="primary" @click="onFinish" v-else>
+                    确认
+                </a-button>
+            </template>
+
+            <template v-if="!isShowArray">
+                <a-button type="primary" @click="onCancel">
+                    取消
+                </a-button>
+                <a-button type="primary" @click="onFinish">
+                    确认
+                </a-button>
+            </template>
+        </a-space>
+    </div>
+</template>
+
+<script setup>
+import { ref, defineEmits ,defineProps, toRefs } from 'vue';
+
+const emit = defineEmits(['onCancel', 'onSubmit']);
+
+const props = defineProps({
+    current: {
+        type: Number,
+        default: undefined,
+    },
+    maxStep: {
+        type: Number,
+        default: 1,
+    },
+    isShowArray: {
+        type: Boolean,
+        default: true,
+    },
+    onPrev: {
+        type: Function,
+        default: () => {},
+    },
+    onNext: {
+        type: Function,
+    }
+})
+
+const { current, maxStep, isShowArray, onPrev, onNext, onFinish } = toRefs(props);
+
+
+
+</script>
+
+
+<style lang="less" scoped>
+.step-footer {
+    width: 100%;
+    height: 200px;
+    text-align: center;
+    background: var(--color-bg-2);
+    color: #C2C7CC;
+    padding-top: 20px;
+}
+</style>

+ 126 - 0
src/components/Steps/StepForm.vue

@@ -0,0 +1,126 @@
+<template>
+    <!-- 动态渲染表单数据 -->
+    <template v-if="isShowArray">
+        <keep-alive>
+            <div class="steps-conter">
+                <div>
+                    <div v-for="(item, index) in StepsData[current - 1]" :key="item.id || item.field">
+                        <div v-show="visibleShowMap[item.field]">
+                            <a-form-item :label="item.label" :rules="item.rules" :field="item.field"
+                                :validate-trigger="rulesTagse(item?.rules)">
+                                <!-- 使用 key 触发重新渲染 -->
+                                <component :is="'a-' + item.type" v-model="formState[item.field]"
+                                    :placeholder="item.type === 'input' ? '请输入' : '请选择' + item.label" allow-clear
+                                    :style="{ width: item.width ? item.width + 'px' : '' }"
+                                    :key="formState[item.field]">
+                                    <template v-if="item.type === 'select'">
+                                        <a-option v-for="option in item.options" :key="option.value"
+                                            :value="option.value">
+                                            {{ option.label }}
+                                        </a-option>
+                                    </template>
+                                    <!-- 渲染插槽 必须先渲染一次 在传递给父组件 不然为空 -->
+                                    <template v-if="item.slot" #[item.slot]>
+                                        <slot :name="item.slot" :item="item"></slot>
+                                    </template>
+                                </component>
+                            </a-form-item>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </keep-alive>
+    </template>
+
+    <!-- 如果 StepsData 不是数组的格式,渲染单个表单项 -->
+    <template v-if="!isShowArray">
+        <component :is="'a-' + StepsData.type" v-model="StepsData.value"
+            :placeholder="StepsData.type === 'input' ? '请输入' : '请选择' + StepsData.label" allow-clear
+            :style="{ width: StepsData.width ? StepsData.width + 'px' : '' }">
+            <template v-if="StepsData.type === 'select'">
+                <a-option v-for="option in StepsData.options" :key="option.value" :value="option.value">
+                    {{ option.label }}
+                </a-option>
+            </template>
+            <!-- 渲染插槽 必须先渲染一次 在传递给父组件 不然为空 -->
+            <template v-if="item.slot" #[item.slot]>
+                <slot :name="item.slot" :item="item"></slot>
+            </template>
+        </component>
+    </template>
+</template>
+
+<script setup>
+import { ref, toRefs, defineProps, computed } from 'vue';
+const formState = ref({});
+const props = defineProps({
+    current: {
+        type: Number,
+        default: undefined,
+    },
+    StepsData: {
+        type: Number,
+        default: 1,
+    },
+    isShowArray: {
+        type: Boolean,
+        default: true,
+    },
+});
+
+const { current, StepsData, isShowArray } = toRefs(props)
+
+// 初始化 formState
+if (isShowArray.value) {
+    StepsData.value.forEach(step => {
+        step.forEach(item => {
+            if (item.field) {
+                formState.value[item.field] = item.value;
+            }
+        });
+    });
+} else if (StepsData.value && StepsData.value.value) {
+    formState.value = StepsData.value.value;
+}
+
+// 使用 computed 来动态计算每个表单项的可见性
+const visibleShowMap = computed(() => {
+    const visibilityMap = {};
+    // 遍历每个步骤,计算每个表单项的可见性
+    StepsData.value.forEach((step) => {
+        step.forEach((item) => {
+            if (item.field) {
+                if (item.isVisible) {
+                    if (typeof item.isVisible === 'function') {
+                        // 如果 isVisible 是函数,执行并设置结果
+                        visibilityMap[item.field] = item.isVisible(item, formState.value);
+                    } else {
+                        // 否则直接使用 isVisible 的值
+                        visibilityMap[item.field] = item.isVisible;
+                    }
+                } else {
+                    visibilityMap[item.field] = true;  // 默认可见
+                }
+            }
+        });
+    });
+
+    return visibilityMap;
+});
+
+
+
+// 表单验证方式
+const rulesTagse = (item) => {
+    if (!item) return ''
+    return item[0]?.trigger
+}
+
+</script>
+
+<style lang="less" scoped>
+.steps-conter {
+    width: 100%;
+    margin-top: 20px;
+}
+</style>

+ 36 - 146
src/components/Steps/index.vue

@@ -19,100 +19,43 @@
             <a-step>结果</a-step>
         </a-steps>
 
-        <!-- 动态渲染表单数据 -->
-        <template v-if="isShowArray">
-            <keep-alive>
-                <div class="steps-conter">
-                    <div>
-                        <div v-for="(item, index) in StepsData[current - 1]" :key="item.id || item.field">
-                            <div :style="{ width: width }" v-show="visibleShowMap[item.field]">
-                                <a-form-item :label="item.label" :rules="item.rules" :field="item.field"
-                                    :validate-trigger="rulesTagse(item?.rules)">
-                                    <!-- 使用 key 触发重新渲染 -->
-                                    <component :is="'a-' + item.type" v-model="formState[item.field]"
-                                        :placeholder="item.type === 'input' ? '请输入' : '请选择' + item.label" allow-clear
-                                        :style="{ width: item.width ? item.width + 'px' : '' }"
-                                        :key="formState[item.field]">
-                                        <template v-if="item.type === 'select'">
-                                            <a-option v-for="option in item.options" :key="option.value"
-                                                :value="option.value">
-                                                {{ option.label }}
-                                            </a-option>
-                                        </template>
-                                    </component>
-                                </a-form-item>
-                            </div>
-                        </div>
-                    </div>
-                </div>
-            </keep-alive>
-        </template>
-
-        <!-- 如果 StepsData 不是数组的格式,渲染单个表单项 -->
-        <template v-if="!isShowArray">
-            <component :is="'a-' + StepsData.type" v-model="StepsData.value"
-                :placeholder="StepsData.type === 'input' ? '请输入' : '请选择' + StepsData.label" allow-clear
-                :style="{ width: StepsData.width ? StepsData.width + 'px' : '' }">
-                <template v-if="StepsData.type === 'select'">
-                    <a-option v-for="option in StepsData.options" :key="option.value" :value="option.value">
-                        {{ option.label }}
-                    </a-option>
-                </template>
-            </component>
-        </template>
-
-        <!-- 底部按钮 -->
-        <div class="step-footer">
-            <a-space size="large">
-                <template v-if="isShowArray">
-                    <a-button type="secondary" :disabled="current <= 1" @click="onPrev">
-                        <IconLeft /> 上一步
-                    </a-button>
-                    <a-button type="primary" :disabled="current >= maxStep" @click="onNext" v-if="maxStep !== current">
-                        下一步
-                        <IconRight />
-                    </a-button>
-
-                    <a-button type="primary" @click="onFinish" v-else>
-                        确认
-                    </a-button>
-                </template>
-
-                <template v-if="!isShowArray">
-                    <a-button type="primary" @click="onCancel">
-                        取消
-                    </a-button>
-                    <a-button type="primary" @click="onFinish">
-                        确认
-                    </a-button>
-                </template>
-            </a-space>
+        <div class="form-container">
+            <div :style="{ width: width }">
+                <StepForm :StepsData="StepsData" :current="current" :isShowArray="isShowArray" />
+            </div>
         </div>
+
+
+        <StepButton :current="current" :maxStep="maxStep" :isShowArray="isShowArray" :onPrev="onPrev" :onNext="onNext"
+            :onFinish="onFinish" />
     </div>
 </template>
 
 <script setup>
 import { ref, defineProps, toRefs, computed, defineEmits } from 'vue';
-
-// const StepsData = [
-//   [
-//     { type: 'select', label: '姓名', field: 'name', value: '',options:[], isVisible:  (item, e) => {
-//       if(e.gender=='male'){
-//         item.options = [{ value: 'male', label: '男' }, { value: 'female', label: '女' }]
-//       }
-//       return e.gender=='male'
-//     } },
-//     { type: 'select', label: '性别', field: 'gender',value:"", options: [{ value: 'male', label: '男' }, { value: 'female', label: '女' }] },
-//   ],
-//   [
-//     { type: 'input', label: '年龄', field: 'age', value: '' },
-//     { type: 'input', label: '电话', field: 'phone', value: '' },
-//   ],
-]
+import StepButton from './StepButton.vue'
+import StepForm from './StepForm.vue'
+// const StepsData =
 const props = defineProps({
     StepsData: {
         type: Array,
-        default: () => []  // 默认值设置为数组套数组
+        default: () => [
+            [
+                {
+                    type: 'select', label: '姓名', field: 'name', value: '', options: [], isVisible: (item, e) => {
+                        if (e.gender == 'male') {
+                            item.options = [{ value: 'male', label: '男' }, { value: 'female', label: '女' }]
+                        }
+                        return e.gender == 'male'
+                    }
+                },
+                { type: 'select', label: '性别', field: 'gender', value: "", options: [{ value: 'male', label: '男' }, { value: 'female', label: '女' }] },
+            ],
+            [
+                { type: 'input', label: '年龄', field: 'age', value: '' },
+                { type: 'input', label: '电话', field: 'phone', value: '' },
+            ],
+        ]  // 默认值设置为数组套数组
     }, // 步骤条数据层
     width: {
         type: [String, Number],
@@ -128,56 +71,12 @@ const { StepsData, width } = toRefs(props);
 const isShowArray = computed(() => Array.isArray(StepsData.value));
 
 // 初始化表单状态
-const formState = ref({});
+
 const current = ref(1);
 const submitShow = ref(false)
 // 计算最大步骤数
 const maxStep = computed(() => (isShowArray.value ? StepsData.value.length : 1));
 
-// 初始化 formState
-if (isShowArray.value) {
-    StepsData.value.forEach(step => {
-        step.forEach(item => {
-            if (item.field) {
-                formState.value[item.field] = item.value;
-            }
-        });
-    });
-} else if (StepsData.value && StepsData.value.value) {
-    formState.value = StepsData.value.value;
-}
-
-// 使用 computed 来动态计算每个表单项的可见性
-const visibleShowMap = computed(() => {
-    const visibilityMap = {};
-    // 遍历每个步骤,计算每个表单项的可见性
-    StepsData.value.forEach((step) => {
-        step.forEach((item) => {
-            if (item.field) {
-                if (item.isVisible) {
-                    if (typeof item.isVisible === 'function') {
-                        // 如果 isVisible 是函数,执行并设置结果
-                        visibilityMap[item.field] = item.isVisible(item, formState.value);
-                    } else {
-                        // 否则直接使用 isVisible 的值
-                        visibilityMap[item.field] = item.isVisible;
-                    }
-                } else {
-                    visibilityMap[item.field] = true;  // 默认可见
-                }
-            }
-        });
-    });
-
-    return visibilityMap;
-});
-
-// 表单验证方式
-const rulesTagse = (item) => {
-    if (!item) return ''
-    return item[0]?.trigger
-}
-
 // 上一步
 const onPrev = () => {
     current.value = Math.max(1, current.value - 1);
@@ -202,30 +101,21 @@ const onCancel = () => {
 // 确认
 const onFinish = () => {
     emit('onSubmit', formState.value)
-    current.value = Math.min(maxStep.value+1, current.value + 1);
+    current.value = Math.min(maxStep.value + 1, current.value + 1);
     submitShow.value = true
 }
 </script>
 
 <style scoped>
-.step-footer {
-    width: 100%;
-    height: 200px;
-    text-align: center;
-    background: var(--color-bg-2);
-    color: #C2C7CC;
-    padding-top: 20px;
-}
-
-.steps-conter {
-    display: flex;
-    justify-content: center;
-    margin-top: 20px;
-}
-
 .header {
     margin-bottom: 50px;
     display: flex;
     justify-content: end;
 }
+
+.form-container{
+    width: 100%;
+    display: flex;
+    justify-content: center;
+}
 </style>

+ 1 - 1
src/components/upload/index.vue

@@ -20,7 +20,7 @@ const customRequest = async (option) => {
   const fileName = `thumbnail_${file.name}`;
   const key = `test/${fileName}`;
 
-  // 获取上传客户端(假设 systemStore 已经定义了上传接口)
+
   const client = await systemStore.getSTSClient();
   const resClient = await client.putObject({
     key: key,

+ 2 - 1
src/hooks/AcquireLocation.js

@@ -1,6 +1,7 @@
-// 获取当前用户ip
 import { EnvTypeNum } from '@/settings/designSetting'
 
+// 根据当前启动的项目地址动态更换浏览器图标
+
 export function AcquisitionArea() {
     let favicon = document.querySelector('link[rel="icon"]')
     if (EnvTypeNum === 0) {

+ 72 - 41
src/utils/axios.js

@@ -1,47 +1,72 @@
 import axios from "axios";
 import Router from "@/router";
-import { Message, Notification } from "@arco-design/web-vue";
+import { Notification } from "@arco-design/web-vue";
 import { useSystemStore } from "@/store/modules/systemStore";
 import moment from 'moment-timezone';
 import { fn_logout } from "@/utils";
-import error from "@/i18n/zh/error";
+
+// 用于存储请求和对应的 AbortController
+const requestMap = new Map();
+const requestCountMap = new Map(); // 用于记录请求的次数
 
 const axiosInstance = axios.create({
-  // baseURL: `${import.meta.env.PROD? import.meta.env.VITE_PRO_PATH : ''}/api`,
-  baseURL: import.meta.env.BASE_URL + "api",
-  timeout: 300000,
+  baseURL: import.meta.env.BASE_URL + "api", // 设置 API 基础 URL
+  timeout: 300000, // 设置请求超时时间
 });
 
 const requestState = {
-  // 得到正确响应
   success: [200],
-  // token 跳转
   beOverdue: [886],
-  // 没有访问权限
   NotAccessRight: [500],
-  // 异常 code
   exception: [400],
 };
 
 const pathArr = ["/api/admin/system/login", "/api/logout"];
+
 axiosInstance.interceptors.request.use(
   (config) => {
     const systemStore = useSystemStore();
     systemStore.localLoading(true);
 
-    // 在发送请求之前做些什么
     if (!pathArr.includes(config.url)) {
       const token = localStorage.getItem("token");
       if (token && config.headers) {
         config.headers["Authorization"] = token;
       }
     }
+
+    const requestKey = `${config.url}?${JSON.stringify(config.params)}`;
+
+    // 判断同一个接口请求次数
+    const currentCount = requestCountMap.get(requestKey) || 0;
+    if (currentCount >= 5) {
+      // 如果请求次数大于等于5次,取消当前请求
+      const controller = requestMap.get(requestKey);
+      controller?.abort();
+      console.log(`请求 ${requestKey} 已被取消,超过请求次数限制`);
+      Notification.warning({
+        title: "请求频繁",
+        content: `请求 ${config.url} 过于频繁,已取消当前请求。`,
+      });
+      return Promise.reject("请求过于频繁,已取消");
+    }
+
+    // 更新请求次数
+    requestCountMap.set(requestKey, currentCount + 1);
+
+    // 创建新的 AbortController
+    const controller = new AbortController();
+    config.signal = controller.signal;
+
+    // 存储当前请求的 AbortController
+    requestMap.set(requestKey, controller);
+
     return config;
   },
   (err) => {
     const systemStore = useSystemStore();
     systemStore.localLoading();
-    Promise.reject(err);
+    return Promise.reject(err);
   }
 );
 
@@ -51,17 +76,29 @@ axiosInstance.interceptors.response.use(
     const systemStore = useSystemStore();
     systemStore.localLoading();
     const { code, data, message: msg } = res.data;
-    // 成功
+
+    // 获取当前请求的 key
+    const requestKey = `${res.config.url}?${JSON.stringify(res.config.params)}`;
+
+    // 请求完成后移除 AbortController
+    if (requestMap.has(requestKey)) {
+      requestMap.delete(requestKey);
+    }
+
+    // 清理请求次数
+    if (requestCountMap.has(requestKey)) {
+      requestCountMap.delete(requestKey);
+    }
+
     if (code === 200) {
       const dataList = {
         code: code,
         message: res.data.msg,
-        data: DataViewsProvider(data)
+        data: DataViewsProvider(data),
       };
       return Promise.resolve(dataList);
     }
 
-    // 服务端错误信息
     if (requestState.NotAccessRight.includes(code)) {
       Notification.warning({
         title: "系统信息",
@@ -70,7 +107,6 @@ axiosInstance.interceptors.response.use(
       return Promise.reject(msg);
     }
 
-    // 异常 code
     if (requestState.exception.includes(code)) {
       Notification.warning({
         title: "系统提示",
@@ -84,56 +120,56 @@ axiosInstance.interceptors.response.use(
   (err) => {
     const systemStore = useSystemStore();
     systemStore.localLoading();
-    const res = err["response"];
-    if (err.code === "ERR_CANCELED") {
-      console.log("请求中断");
-      return;
+
+    const requestKey = `${err.config.url}?${JSON.stringify(err.config.params)}`;
+
+    // 请求完成后移除 AbortController
+    if (requestMap.has(requestKey)) {
+      requestMap.delete(requestKey);
+    }
+
+    // 清理请求次数
+    if (requestCountMap.has(requestKey)) {
+      requestCountMap.delete(requestKey);
     }
+
     const msg = err.response?.data ? err.response.data.message : "";
     Notification.warning({
       title: "系统信息",
-      content: msg
+      content: msg,
     });
-    // token 失效
+
     if (requestState.beOverdue.includes(err.status)) {
       fn_logout(Router);
-      // 直接终止请求
+      Router.push("/");
       return undefined;
     }
-    Promise.reject(err);
+
+    return Promise.reject(err);
   }
 );
 
-
 function timeLoadTemeYear(value) {
   if (!value) return;
   const systemStore = useSystemStore();
-  const timezone = systemStore.getUserIp?.timezone || JSON.parse(localStorage.getItem('IPCONFIG'))?.timezone
+  const timezone = systemStore.getUserIp?.timezone || JSON.parse(localStorage.getItem('IPCONFIG'))?.timezone;
   if (timezone) {
-    // 转换 UTC 时间为用户的本地时间
     let localDate = moment.utc(value).tz(timezone);
 
     if (!localDate.isValid()) {
-      return null;  // 如果时间无效,返回 null
+      return null;
     }
 
-    // 格式化并返回本地时间
     const localTime = localDate.format('YYYY-MM-DD HH:mm:ss');
     return localTime;
   }
 
-  return value
-
+  return value;
 }
 
-
-
-
-// 转换数据格式 
 function DataViewsProvider(data) {
   if (!data) return data;
 
-  // 如果数据是对象并且包含 records 字段
   if (data.records && Array.isArray(data.records)) {
     return {
       ...data,
@@ -150,7 +186,6 @@ function DataViewsProvider(data) {
     };
   }
 
-  // 如果数据本身是数组
   if (Array.isArray(data)) {
     return data.map(item => {
       const transformedItem = { ...item };
@@ -164,11 +199,7 @@ function DataViewsProvider(data) {
     });
   }
 
-  // 如果数据不是数组也不是包含 records 的对象,直接返回数据
   return data;
 }
 
-
-
-
-export default axiosInstance;
+export default axiosInstance;

+ 0 - 0
src/utils/components.js


+ 1 - 2
src/utils/index.js

@@ -1,5 +1,4 @@
 export * from '@/utils/crypto'
 export * from '@/utils/utils'
 export * from '@/utils/storage'
-export * from '@/utils/router'
-export * from '@/utils/components'
+export * from '@/utils/router'

+ 1 - 1
src/views/financialManagement/appPuy/index.vue

@@ -24,6 +24,7 @@
                 </a-image>
             </template>
         </a-table>
+
     </div>
 </template>
 <script setup>
@@ -32,7 +33,6 @@ import { getAdiroePuyList } from "@/api/path/finance.js";
 import { columns, handleStatusTxt, statusOptions, UserAdiroColumns, AdminSearchForm, ClientSearchForm } from '../config'
 import Search from '@/components/Search/index.vue'
 import { Getdictionary, tableFunction, filterDict } from '@/mixins/index.js'
-
 const pagination = ref({
     showTotal: true,
     showJumper: true,

+ 2 - 1
src/views/lotCard/cardList/trafficUseDialog.vue

@@ -235,12 +235,13 @@ const open = async (data) => {
   dataSource.value = []
   Card_info.value = data
   dataPackage.value = data.dataPackage[0]
+
   // 卡套餐信息
   dataCard.value = (data.dataPackage || []).map(val => {
     return {
       ...val,
       dataTotal: val.dataTotal == -1 ? t('lotCard.UnlimitedFlow') : val.dataTotal,
-      tariffName: data.tariffName,
+      tariffName: val.tariffName,
       dataUsage: val.dataUsage + '/MB',
       dataTotal: val.dataTotal + '/MB',
       dataToday: val.dataToday + '/MB',