Преглед на файлове

fix: ip分组/域名分组接口联调结束

jcq преди 2 седмици
родител
ревизия
c78e034f60

+ 11 - 26
src/api/marketing/config.ts

@@ -1,5 +1,5 @@
 import request from '/@/utils/request';
-
+//获取域名分组
 export const pageListDomain = () => {
 	return request({
 		url: '/marketing/config/getDomainGroup',
@@ -7,6 +7,7 @@ export const pageListDomain = () => {
 		method: 'get',
 	});
 };
+//获取ip分组
 export const pageListIp = (params?: Object) => {
 	return request({
 		url: '/marketing/config/getIpGroup',
@@ -15,21 +16,8 @@ export const pageListIp = (params?: Object) => {
 		params,
 	});
 };
-export const update = (data: Object) => {
-	return request({
-		// url: `/admin/marketing/config/${id}`,
-		url: 'https://m1.apifoxmock.com/m2/6687089-6396408-default/323895237',
-		method: 'post',
-		data: data,
-	});
-};
-export const info = (id: String) => {
-	return request({
-		url: `/admin/marketing/config/${id}`,
-		method: 'get',
-	});
-};
 
+//修改域名分组
 export const saveDomains = (data: Object) => {
 	return request({
 		url: '/marketing/config/modDomainGroup',
@@ -37,14 +25,16 @@ export const saveDomains = (data: Object) => {
 		data: data,
 	});
 };
-
-export const delObj = (ids: Array<String>) => {
+//修改IP分组
+export const saveIps = (data: Object) => {
 	return request({
-		url: '/admin/marketing/config/remove',
+		url: '/marketing/config/modIpGroup',
 		method: 'post',
-		data: ids
+		data: data,
 	});
 };
+
+//删除分组
 export const delGroup = (data: object) => {
 	return request({
 		url: '/marketing/config/delGroup',
@@ -52,6 +42,7 @@ export const delGroup = (data: object) => {
 		data: data
 	});
 };
+//添加分组
 export const addGroup = (data: object) => {
 	return request({
 		url: '/marketing/config/addGroup',
@@ -59,13 +50,7 @@ export const addGroup = (data: object) => {
 		data: data
 	});
 };
-export const getAppList = (params?: Object) => {
-	return request({
-		url: '/admin/marketing/config/apps',
-		method: 'get',
-		params
-	});
-};
+
 
 /**
  * 后端控制路由,isRequestRoutes 为 true,则开启后端控制路由

+ 27 - 2
src/utils/ipUpdate.ts

@@ -1,7 +1,32 @@
 // src/utils/ipUpdate.ts
 
+
+
+/**
+ * 将 "startIp/number" 格式拆分为 start 和 end IP
+ * @param ipWithRange 例如 "192.168.1.1/100"
+ * @returns { start: string, end: string }
+ */
+ const parseIpRange = (ipWithRange: string): { start: string; end: string } => {
+  if (!ipWithRange.includes('/')) {
+    // 如果没有 /,说明是普通 IP
+    return { start: ipWithRange, end: ipWithRange };
+  }
+
+  const [startIp, numberStr] = ipWithRange.split('/');
+  const baseIpParts = startIp.split('.');
+  const prefix = baseIpParts.slice(0, 3).join('.'); // 192.168.1
+  const startLastOctet = parseInt(baseIpParts[3], 10); // 1
+  const endLastOctet = parseInt(numberStr, 10); // 100
+
+  return {
+    start: startIp,
+    end: `${prefix}.${endLastOctet}`,
+  };
+};
+
 const ipSplicing = (startIp: string, endIp: string | null | undefined): string => {
-  if (!endIp || endIp.trim() === '') {
+  if (!endIp || endIp.trim() === '' || startIp === endIp) {
     return startIp;
   }
 
@@ -12,4 +37,4 @@ const ipSplicing = (startIp: string, endIp: string | null | undefined): string =
   return `${startIp}/${lastPartOfEndIp}`;
 };
 
-export { ipSplicing };
+export { ipSplicing,parseIpRange };

+ 39 - 1
src/utils/validate.ts

@@ -175,8 +175,46 @@ export const rule = {
 			callback();
 		}
 	},
+	ipRange(rule: any, value: any, callback: any) {
+		if (validateNull(value) || value.length <= 0) {
+			callback();
+			return;
+		}
+
+		// 校验普通 IP 格式(如 192.168.1.1)
+		const ipOnlyRegex = /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
+
+		// 校验 IP 范围格式(如 192.168.1.1/100)
+		const ipRangeRegex = /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\/([1-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/;
+
+		// 如果是普通 IP,直接通过
+		if (ipOnlyRegex.test(value)) {
+			callback();
+			return;
+		}
+
+		// 如果是 IP 范围格式,进一步校验
+		if (ipRangeRegex.test(value)) {
+			const [ip, rangeStr] = value.split('/');
+			const range = parseInt(rangeStr, 10);
+			const ipParts = ip.split('.');
+			const lastOctet = parseInt(ipParts[3], 10);
+
+			if (lastOctet > range) {
+			callback(new Error('/ 前的数字不能大于 / 后的数字'));
+			} else {
+			callback();
+			}
+			return;
+		}
+
+		// 格式都不匹配
+		callback(new Error('IP 格式不正确'));
+	},
 };
 
+
+
 /**
  * @desc  [自定义校验规则]
  * @example
@@ -197,7 +235,7 @@ export const getRegExp = function (validatorName) {
 		chinese: '^[\u4e00-\u9fa5]+$',
 		email: '^([-_A-Za-z0-9.]+)@([_A-Za-z0-9]+\\.)+[A-Za-z0-9]{2,3}$',
 		url: '(https?|ftp|file|http)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]',
-		ip:'^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$',
+		ip:'^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\/([1-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))?$',
 	};
 	return commonRegExp[validatorName];
 };

+ 1 - 1
src/views/marketing/config/components/domainEdit.vue

@@ -30,7 +30,7 @@
 
 <script setup name="systemMenuDialog">
 import { useI18n } from 'vue-i18n';
-import { info, getAppList } from '/@/api/marketing/config';
+// import { info, getAppList } from '/@/api/marketing/config';
 import { useMessage } from '/@/hooks/message';
 
 // 定义子组件向父组件传值/事件

+ 10 - 0
src/views/marketing/config/components/ipGroupingEdit.vue

@@ -76,6 +76,16 @@ const onSubmit = async () => {
 		loading.value = false;
 	}
 };
+watch(() => props.open, (val) => {
+	if(val){
+		state.ruleForm = {
+			groupName: '',
+			groupType: '',
+			groupDesc: '',
+			groupStatus: '',
+		};
+	} 
+})
 </script>
 <style>
 .apps-loadmore.el-select-dropdown .el-scrollbar__wrap {

+ 26 - 16
src/views/marketing/config/components/ipListEdit.vue

@@ -11,12 +11,7 @@
 			<el-form-item label="名称" prop="groupName">
 				<el-input class="!w-[300px]" v-model="state.ruleForm.groupName" placeholder="请输入名称"></el-input>
 			</el-form-item>
-			<el-form-item
-				v-for="(item, index) in state.ruleForm.list"
-				:key="item.id || item.val"
-				:prop="'list.' + index + '.val'"
-				:rules="dataRules.configs"
-			>
+			<el-form-item v-for="(item, index) in state.ruleForm.list" :key="item.id || index" :prop="'list.' + index + '.val'" :rules="dataRules.configs">
 				<template #label>
 					<div v-if="index === 0" class="flex items-center">
 						<el-tooltip v-if="sign === 'ip'" effect="light" content="输入127.0.0.1/24格式代表网段" placement="top">
@@ -78,15 +73,15 @@
 
 <script setup name="systemMenuDialog">
 import { useI18n } from 'vue-i18n';
-import { saveDomains } from '/@/api/marketing/config';
+import { saveDomains ,saveIps } from '/@/api/marketing/config';
 import { useMessage } from '/@/hooks/message';
 import { rule } from '/@/utils/validate';
+import { parseIpRange } from '/@/utils/ipUpdate';
 
 // 定义子组件向父组件传值/事件
 const emit = defineEmits(['onsuccess']);
 const { t } = useI18n();
 
-
 const inputTip = computed(() => t('marketingConfig.inputIPTip'));
 // 定义变量内容
 const visible = ref(false);
@@ -99,7 +94,6 @@ const state = reactive({
 		list: [],
 		delList: [], //删除的ip或者域名的id
 	},
-	appList: [], // 应用下拉框列表
 });
 
 //删除的id存入delList中
@@ -117,7 +111,7 @@ const onAddItem = () => {
 const dataRules = computed(() => ({
 	configs: [
 		{ required: false, message: `${sign.value === 'domain' ? '域名' : 'IP'}不能为空`, trigger: 'blur' },
-		{ validator: sign.value === 'domain' ? rule.url : rule.ip, trigger: 'blur' },
+		{ validator: sign.value === 'domain' ? rule.url : rule.ipRange, trigger: 'blur' },
 	],
 }));
 
@@ -143,18 +137,34 @@ const resetForm = () => {
 const onSubmit = async () => {
 	const valid = await menuDialogFormRef.value.validate().catch(() => {});
 	if (!valid) return false;
+	if (sign.value === 'ip') {
+		state.ruleForm.ips = state.ruleForm.list.map((item) => {
+			const { start, end } = parseIpRange(item.val);
+			
+			return {
+				...item,
+				ipMode:start === end ? 1 : 2,
+				startIp: start,
+				endIp: end,
+			};
+		});
+	} else {
+		state.ruleForm.domains = state.ruleForm.list.map((item) => ({
+			...item,
+			domain: item.val,
+		}));
+	}
 	console.log(state.ruleForm);
 
-	sign.value === 'domain'
-		? (state.ruleForm.domains = state.ruleForm.list.map((item) => {
-				return { ...item, domain: item.val };
-		  }))
-		: (state.ruleForm.ips = state.ruleForm.list.map((e) => e.val));
 	// delete state.ruleForm.list;
 	// return;
 	try {
 		loading.value = true;
-		await saveDomains(state.ruleForm);
+		if (sign.value === 'ip') {
+			await saveIps(state.ruleForm);
+		} else {
+			await saveDomains(state.ruleForm);
+		}
 		useMessage().success(t(state.ruleForm.id ? 'common.editSuccessText' : 'common.addSuccessText'));
 		visible.value = false;
 		emit('onsuccess');

+ 1 - 1
src/views/marketing/config/components/listEdit.vue

@@ -36,7 +36,7 @@
 
 <script setup name="systemMenuDialog">
 import { useI18n } from 'vue-i18n';
-import { info, getAppList } from '/@/api/marketing/config';
+// import { info, getAppList } from '/@/api/marketing/config';
 import { useMessage } from '/@/hooks/message';
 
 // 定义子组件向父组件传值/事件

+ 13 - 16
src/views/marketing/config/index.vue

@@ -150,10 +150,10 @@
 		</el-tabs>
 		<DomainEdit v-model:open="domainEditOpen" />
 		<ListEdit v-model:open="listEditOpen" />
-		<GroupingEdit v-model:open="groupingEditOpen" :type="openType" />
-		<IpListEdit :type="openType" ref="menuDialogRef" @onsuccess="getDomainData" />
+		<GroupingEdit v-model:open="groupingEditOpen" :type="openType" @onsuccess="getData" />
+		<IpListEdit :type="openType" ref="menuDialogRef" @onsuccess="getData" />
 		<el-dialog v-model="delOpen" title="提示" width="500" @close="delOpen = false">
-			<span>确认删除{{ delObj?.title }} 分组吗?</span>
+			<span>确认删除{{ delObj?.title }}分组吗?</span>
 			<template #footer>
 				<div class="dialog-footer">
 					<el-button @click="delOpen = false">取消</el-button>
@@ -193,11 +193,9 @@ const delOpen = ref(false);
 const loading = ref(false);
 const menuDialogRef = ref();
 
-
 const domainActiveId = ref([]);
 const ipActiveId = ref([]);
 
-
 // const ipListEditOpen = ref(false);
 const domainData = ref([]);
 const ipData = ref([]);
@@ -245,6 +243,10 @@ const onOpenDelete = (item: any, type: any) => {
 
 	delOpen.value = true;
 };
+const getData = () => {
+	openType.value === 'ip' ? getIpData() : getDomainData();
+	
+};
 
 // // 表单校验规则
 const dataRules = reactive({
@@ -273,17 +275,15 @@ const whiteList = ref([{ label: '站大' }, { label: '站而' }, { label: '站
 const onSubmit = () => {
 	useMessage().success(t(formData.value ? 'common.editSuccessText' : 'common.addSuccessText'));
 };
-const handleClick = (data:any) => { 
-	if(data.props.label === 'IP分组'){
+const handleClick = (data: any) => {
+	if (data.props.label === 'IP分组') {
 		getIpData();
-	}else if(data.props.label === '域名分组'){
+	} else if (data.props.label === '域名分组') {
 		getDomainData();
-	}else{
-
+	} else {
 	}
-	
 };
-const onDel = async (data:any) => {
+const onDel = async (data: any) => {
 	try {
 		loading.value = true;
 		await delGroup({
@@ -302,8 +302,6 @@ const onDel = async (data:any) => {
 
 const onClickAdd = (type: string) => {
 	openType.value = type;
-	console.log(type);
-	
 	groupingEditOpen.value = true;
 };
 const onClickEdit = (item: any, type: string) => {
@@ -349,7 +347,7 @@ const getIpData = async () => {
 					return {
 						...items,
 						id: items.id,
-						value: ipSplicing(items.startIp,items.endIp),
+						value: ipSplicing(items.startIp, items.endIp),
 					};
 				}),
 			};
@@ -360,7 +358,6 @@ const getIpData = async () => {
 onMounted(() => {
 	//获取IP列表
 	getIpData();
-
 });
 </script>
 <style  lang="scss">