浏览代码

Merge branch 'dev-cmn' into dev-ly

cmy 1 周之前
父节点
当前提交
b455e0e142

+ 46 - 0
src/api/marketing/apps.ts

@@ -97,4 +97,50 @@ export const appUpdate = (data: Object) => {
 		method: 'post',
 		method: 'post',
 		data: data,
 		data: data,
 	});
 	});
+};
+
+/**
+ * 修改应用IP集合
+ * @param id 
+ * @param delIps 
+ * @param delIps 
+ * @returns ips
+ */
+export const updateModIp = (data: Object) => {
+	return request({
+		url: '/marketing/app/modIp',
+		method: 'post',
+		data: data,
+	});
+};
+
+/**
+ * 修改应用域名集合
+ * @param id 
+ * @param delDomains 
+ * @param delIps 
+ * @returns domains
+ */
+export const updateModDomains = (data: Object) => {
+	return request({
+		url: '/marketing/app/modDomain',
+		method: 'post',
+		data: data,
+	});
+};
+
+/**
+ * 设置应用信息
+ * @param id	id
+ * @param domainType	应用类型,0-Android,1-IOS
+ * @param domainLimit	域名限制
+ * @param launch	营销投放
+ * @param triggerRule	触发规则,1-仅一次,2-多次
+ */
+export const setAppInfo = (data: Object) => {
+	return request({
+		url: '/marketing/app/setInfo',
+		method: 'post',
+		data: data,
+	});
 };
 };

+ 8 - 8
src/views/marketing/apps/components/domainCell.vue

@@ -1,7 +1,7 @@
 <template>
 <template>
   <div class="cell">
   <div class="cell">
     <div class="ellipsis" style="text-align: left;">
     <div class="ellipsis" style="text-align: left;">
-      <span class="item" v-for="item in list.slice(0, 4)" :key="item">{{item}}</span>
+      <span class="item" v-for="item in list" :key="item">{{item}}</span>
     </div>
     </div>
     <div class="action" :style="{ textAlign: list.length > 0 ? 'left' : 'center'}">
     <div class="action" :style="{ textAlign: list.length > 0 ? 'left' : 'center'}">
       <el-link type="info" @click="handleEdit" style="color: #167AF0; line-height: 18px;">
       <el-link type="info" @click="handleEdit" style="color: #167AF0; line-height: 18px;">
@@ -9,13 +9,14 @@
       </el-link>
       </el-link>
     </div>
     </div>
   </div>
   </div>
-  <DomainForm ref="DomainFormRef" />
+  <DomainForm ref="DomainFormRef" @refresh="handleUpdate()" />
 </template>
 </template>
 <script setup>
 <script setup>
 import { ref, watch } from 'vue';
 import { ref, watch } from 'vue';
 const DomainForm = defineAsyncComponent(() => import('./domainForm.vue'));
 const DomainForm = defineAsyncComponent(() => import('./domainForm.vue'));
 
 
 const props = defineProps(['domainList', 'rowData']);
 const props = defineProps(['domainList', 'rowData']);
+const emit = defineEmits(['refresh']);
 const list = ref([]);
 const list = ref([]);
 const DomainFormRef = ref();
 const DomainFormRef = ref();
 
 
@@ -39,17 +40,16 @@ watch(
 
 
 // 修改域名弹窗
 // 修改域名弹窗
 const handleEdit = () => {
 const handleEdit = () => {
-  DomainFormRef.value.openDialog(props.domainList);
+  DomainFormRef.value.openDialog(props.domainList, props.rowData);
 };
 };
+
+const handleUpdate = () => {
+  emit('refresh');
+}
 </script>
 </script>
 <style scoped lang="scss">
 <style scoped lang="scss">
 .cell {
 .cell {
   padding: 10px 0;
   padding: 10px 0;
-  // .flex {
-  //   display: flex;
-  //   flex-wrap: wrap;
-  //   margin-bottom: 5px;
-  // }
   .item {
   .item {
     box-sizing: border-box;
     box-sizing: border-box;
     padding: 4px 0;
     padding: 4px 0;

+ 26 - 14
src/views/marketing/apps/components/domainCollapse.vue

@@ -15,7 +15,7 @@
           <!-- 分组域名 -->
           <!-- 分组域名 -->
           <el-popover v-else width="200" trigger="hover" placement="top">
           <el-popover v-else width="200" trigger="hover" placement="top">
             <div class="flex flex-wrap">
             <div class="flex flex-wrap">
-              {{ item.groupName }}
+              {{ item.domains && item.domains.length ? item.domains.map(i=>i.domain).join(',') : '暂无数据' }}
             </div>
             </div>
             <template #reference>
             <template #reference>
               <el-tag @click="getDomainList(item)" effect="light" :closable="domainDeletable"
               <el-tag @click="getDomainList(item)" effect="light" :closable="domainDeletable"
@@ -42,12 +42,13 @@ const JCollapse = defineAsyncComponent(() => import('/@/components/JCollapse/ind
 const DomainEdit = defineAsyncComponent(() => import('./domainEdit.vue'));
 const DomainEdit = defineAsyncComponent(() => import('./domainEdit.vue'));
 
 
 interface DomianItem {
 interface DomianItem {
-  id: String;
+  id?: String;
   domain: String;
   domain: String;
   sourceType: Number; // 1:分组 2:具体域名
   sourceType: Number; // 1:分组 2:具体域名
   groupId: String;
   groupId: String;
   groupName: String;
   groupName: String;
   modify: boolean; // 是否被修改/新增
   modify: boolean; // 是否被修改/新增
+  domains: string[];
 }
 }
 
 
 const props = defineProps(['data']);
 const props = defineProps(['data']);
@@ -88,28 +89,38 @@ const getDomainList = (detail: DomianItem) => {
 // 删除域名
 // 删除域名
 const handleDeleteDomain = (deleteItem: DomianItem, type: 'group' | 'domain') => {
 const handleDeleteDomain = (deleteItem: DomianItem, type: 'group' | 'domain') => {
   console.log(deleteItem)
   console.log(deleteItem)
+  console.log('domains.value', domains.value);
+  
   domains.value = domains.value.filter(i=>{
   domains.value = domains.value.filter(i=>{
     if(!i.modify && i.id == deleteItem.id && !delDomains.value.includes(i.id)) {
     if(!i.modify && i.id == deleteItem.id && !delDomains.value.includes(i.id)) {
       delDomains.value = [...delDomains.value, i.id];
       delDomains.value = [...delDomains.value, i.id];
     }
     }
-    return i.id != deleteItem.id
+    if(type == 'group') {
+      return i.groupId != deleteItem.groupId
+    }else{
+      return i.domain != deleteItem.domain
+    }
   });
   });
 }
 }
 
 
 // 分组去重
 // 分组去重
 function addGroupsUnique(newGroups: DomianItem[]) {
 function addGroupsUnique(newGroups: DomianItem[]) {
   const existIds = new Set([
   const existIds = new Set([
-    ...domains.value.map(item => item.id),
+    // ...domains.value.map(item => item.id),
+    ...domains.value.map(item => item.groupId),
   ]);
   ]);
-  const filtered = newGroups.filter(item => !existIds.has(item.id));
-  if(filtered.length) {
-    filtered.forEach(item => {
-      Object.assign(item, {
+  const filtered = newGroups.filter(item => !existIds.has(item.id))
+    .map(item=>{
+      return {
         "sourceType": 1,
         "sourceType": 1,
         "modify": true,
         "modify": true,
-        // "domain": "",
-      })
-    })
+        "groupId": item.id,
+        "groupName": item.groupName,
+        "domains": item.domains,
+        "domain": "",
+      }
+    });
+  if(filtered.length) {
     domains.value = [...domains.value, ...filtered];
     domains.value = [...domains.value, ...filtered];
     console.log(filtered);
     console.log(filtered);
 		useMessage().success(t('common.addSuccessText'));
 		useMessage().success(t('common.addSuccessText'));
@@ -127,11 +138,12 @@ function addSinglesUnique(newSingles: DomianItem[]) {
   if(filtered.length) {
   if(filtered.length) {
     filtered.forEach(item => {
     filtered.forEach(item => {
       Object.assign(item, {
       Object.assign(item, {
+        "groupId": "",
+        "groupName": "",
+        "domains": [],
+        "domain": item.domain,
         "sourceType": 2,
         "sourceType": 2,
         "modify": true,
         "modify": true,
-        // "groupId": 0,
-        // "groupName": "",
-        // "domain": "",
       })
       })
     })
     })
     domains.value = [...domains.value, ...filtered];
     domains.value = [...domains.value, ...filtered];

+ 6 - 2
src/views/marketing/apps/components/domainEdit.vue

@@ -25,7 +25,7 @@
 		<template #footer>
 		<template #footer>
 			<span class="dialog-footer">
 			<span class="dialog-footer">
 				<el-button @click="onCancel">{{ t('common.cancelButtonText') }}</el-button>
 				<el-button @click="onCancel">{{ t('common.cancelButtonText') }}</el-button>
-				<el-button type="primary" @click="onSubmit" :disabled="loading">{{ t('common.confirmButtonText')
+				<el-button type="primary" @click="onSubmit()" :disabled="loading">{{ t('common.confirmButtonText')
 					}}</el-button>
 					}}</el-button>
 			</span>
 			</span>
 		</template>
 		</template>
@@ -36,6 +36,7 @@
 import { useI18n } from 'vue-i18n';
 import { useI18n } from 'vue-i18n';
 import { pageListDomain } from '/@/api/marketing/config';
 import { pageListDomain } from '/@/api/marketing/config';
 import { useMessage } from '/@/hooks/message';
 import { useMessage } from '/@/hooks/message';
+import { rule } from '/@/utils/validate';
 
 
 // 定义子组件向父组件传值/事件
 // 定义子组件向父组件传值/事件
 const emit = defineEmits(['update:open', 'onsuccess']);
 const emit = defineEmits(['update:open', 'onsuccess']);
@@ -72,6 +73,8 @@ const props = defineProps({
 
 
 watch(() => props.open, async (val) => {
 watch(() => props.open, async (val) => {
 	if (val) {
 	if (val) {
+		state.ruleForm.selectedIds = [];
+		state.ruleForm.domain = [];
 	    getDomainList();
 	    getDomainList();
 	}
 	}
 })
 })
@@ -79,7 +82,8 @@ watch(() => props.open, async (val) => {
 // 表单校验规则
 // 表单校验规则
 const dataRules = reactive({
 const dataRules = reactive({
 	domain: [
 	domain: [
-		{ required: true, message: '域名不能为空', trigger: 'blur' }
+		{ required: true, message: '域名不能为空', trigger: 'blur' },
+		{ validator: rule.url, trigger: 'blur' },
 	],
 	],
 });
 });
 const onCancel = () => {
 const onCancel = () => {

+ 7 - 1
src/views/marketing/apps/components/domainForm.vue

@@ -19,6 +19,7 @@
 <script setup lang="ts" name="systemMenuDialog">
 <script setup lang="ts" name="systemMenuDialog">
 import { ElMessage } from 'element-plus';
 import { ElMessage } from 'element-plus';
 import { useI18n } from 'vue-i18n';
 import { useI18n } from 'vue-i18n';
+import { updateModDomains } from '/@/api/marketing/apps';
 const DomainCollapse = defineAsyncComponent(() => import('./domainCollapse.vue'));
 const DomainCollapse = defineAsyncComponent(() => import('./domainCollapse.vue'));
 
 
 interface DomianItem {
 interface DomianItem {
@@ -39,13 +40,15 @@ const loading = ref(false);
 const domains = ref<DomianItem[]>([]);
 const domains = ref<DomianItem[]>([]);
 const childDomains = ref();
 const childDomains = ref();
 const delDomains = ref<String[]>([]);
 const delDomains = ref<String[]>([]);
+const rowData = ref();
 
 
 // 打开弹窗
 // 打开弹窗
-const openDialog = async (data: any) => {
+const openDialog = async (data: any, row) => {
   visible.value = true;
   visible.value = true;
   domains.value = data;
   domains.value = data;
   childDomains.value = [];
   childDomains.value = [];
   delDomains.value = [];
   delDomains.value = [];
+  rowData.value = row;
 };
 };
 
 
 const updateDomains = (data: DomianItem[])=>{
 const updateDomains = (data: DomianItem[])=>{
@@ -61,6 +64,9 @@ const onSubmit = async () => {
   visible.value = false;
   visible.value = false;
   console.log(childDomains.value);
   console.log(childDomains.value);
   console.log(delDomains.value);
   console.log(delDomains.value);
+  updateModDomains({ domains: childDomains.value, delDomains: delDomains.value, id: rowData.value.id}).then((res) => {
+    emit('refresh');
+  })
 };
 };
 
 
 // 暴露变量 只有暴漏出来的变量 父组件才能使用
 // 暴露变量 只有暴漏出来的变量 父组件才能使用

+ 129 - 37
src/views/marketing/apps/components/form.vue

@@ -1,16 +1,17 @@
 <template>
 <template>
   <div class="apps-form">
   <div class="apps-form">
-    <el-dialog :title="(!state.ruleForm.id ? '批量' : '')+'修改营销配置'" width="880" v-model="visible" :close-on-click-modal="false" :destroy-on-close="true"
-      draggable>
-      <el-form ref="appDialogFormRef" :rules="dataRules" :model="state.ruleForm" class="demo-form-inline" v-loading="loading">
+    <el-dialog :title="(!state.ruleForm.id ? '批量' : '') + '修改营销配置'" width="880" v-model="visible"
+      :close-on-click-modal="false" :destroy-on-close="true" draggable>
+      <el-form ref="appDialogFormRef" :rules="dataRules" :model="state.ruleForm" class="demo-form-inline"
+        v-loading="loading">
         <el-form-item v-if="state.ruleForm.appName" :label="'应用名称'" prop="appName">
         <el-form-item v-if="state.ruleForm.appName" :label="'应用名称'" prop="appName">
           <el-input style="width: 200px;" v-model="state.ruleForm.appName" disabled />
           <el-input style="width: 200px;" v-model="state.ruleForm.appName" disabled />
         </el-form-item>
         </el-form-item>
         <el-row :gutter="20" style="margin-bottom: 15px;">
         <el-row :gutter="20" style="margin-bottom: 15px;">
           <el-col :span="12">
           <el-col :span="12">
             <el-form-item :label="'营销开关'" prop="launch">
             <el-form-item :label="'营销开关'" prop="launch">
-              <el-switch v-model="state.ruleForm.launch" style="--el-switch-on-color: rgb(48, 185, 113);"
-                inline-prompt :active-value="true" :inactive-value="false" />
+              <el-switch v-model="state.ruleForm.launch" style="--el-switch-on-color: rgb(48, 185, 113);" inline-prompt
+                :active-value="true" :inactive-value="false" />
             </el-form-item>
             </el-form-item>
           </el-col>
           </el-col>
           <el-col :span="12">
           <el-col :span="12">
@@ -35,10 +36,11 @@
           </el-col>
           </el-col>
         </el-row>
         </el-row>
         <div class="mt-7">
         <div class="mt-7">
-          <IpCollapse :data=state.ruleForm.ips></IpCollapse>
+          <IpCollapse :data=state.ruleForm.ips @ips="updateIps" @delIps="updateDelIps"></IpCollapse>
         </div>
         </div>
         <div class="mt-7">
         <div class="mt-7">
-          <DomainCollapse :data=state.ruleForm.domains @domains="updateDomains" @delDomains="updateDelDomains"></DomainCollapse>
+          <DomainCollapse :data=state.ruleForm.domains @domains="updateDomains" @delDomains="updateDelDomains">
+          </DomainCollapse>
         </div>
         </div>
         <div class="title">备注</div>
         <div class="title">备注</div>
         <el-form-item>
         <el-form-item>
@@ -59,6 +61,8 @@
 <script setup lang="ts" name="systemMenuDialog">
 <script setup lang="ts" name="systemMenuDialog">
 import { ElMessage } from 'element-plus';
 import { ElMessage } from 'element-plus';
 import { useI18n } from 'vue-i18n';
 import { useI18n } from 'vue-i18n';
+import { appUpdate } from '/@/api/marketing/apps';
+import { useMessage } from '/@/hooks/message';
 const DomainCollapse = defineAsyncComponent(() => import('./domainCollapse.vue'));
 const DomainCollapse = defineAsyncComponent(() => import('./domainCollapse.vue'));
 const IpCollapse = defineAsyncComponent(() => import('./ipCollapse.vue'));
 const IpCollapse = defineAsyncComponent(() => import('./ipCollapse.vue'));
 
 
@@ -82,20 +86,21 @@ interface DomianItem {
 }
 }
 
 
 interface Form {
 interface Form {
-  id: String;
-  appId: String;
-  appName: String;
-  appImg: String;
-  appUrl: String;
-  backUpUrl: String;
-  domainType: String;
+  id?: String;
+  appId?: String;
+  appName?: String;
+  appImg?: String;
+  appUrl?: String;
+  backUpUrl?: String;
+  domainType?: String;
   domainLimit: Boolean;
   domainLimit: Boolean;
   launch: Boolean;
   launch: Boolean;
   triggerRule: Number;
   triggerRule: Number;
-  triggerNum: Number;
+  triggerNum: String;
   remark: String;
   remark: String;
-  ips: IpItem[];
-  domains: DomianItem[];
+  ips?: IpItem[];
+  domains?: DomianItem[];
+  delDomains?: string[];
 }
 }
 
 
 // 定义子组件向父组件传值/事件
 // 定义子组件向父组件传值/事件
@@ -108,6 +113,10 @@ const loading = ref(false);
 const appDialogFormRef = ref();
 const appDialogFormRef = ref();
 const domains = ref();
 const domains = ref();
 const delDomains = ref<String[]>([]);
 const delDomains = ref<String[]>([]);
+const childIps = ref();
+const delIps = ref<String[]>([]);
+// 批量操作
+const rows = ref<Form[]>([]);
 
 
 const triggerRules = [
 const triggerRules = [
   {
   {
@@ -122,14 +131,27 @@ const triggerRules = [
 
 
 // 定义需要的数据
 // 定义需要的数据
 const state = reactive({
 const state = reactive({
-  ruleForm: {} as Form,
+  ruleForm: {
+    remark: "",
+    domainLimit: true,
+    launch: true,
+    triggerNum: "",
+    triggerRule: 1,
+  } as Form,
 });
 });
 
 
 watch(() => state.ruleForm, async (val) => {
 watch(() => state.ruleForm, async (val) => {
   console.log('val', val);
   console.log('val', val);
 })
 })
 
 
-const updateDomains = (data: DomianItem[])=>{
+const updateIps = (data: IpItem[]) => {
+  childIps.value = data;
+}
+const updateDelIps = (data: String[]) => {
+  delIps.value = data;
+}
+
+const updateDomains = (data: DomianItem[]) => {
   domains.value = data;
   domains.value = data;
 }
 }
 const updateDelDomains = (data: String[]) => {
 const updateDelDomains = (data: String[]) => {
@@ -137,9 +159,21 @@ const updateDelDomains = (data: String[]) => {
 }
 }
 
 
 // 打开弹窗
 // 打开弹窗
-const openDialog = async (type: string, row: any, str: string = 'domain') => {
+const openDialog = async (type: string, row: any) => {
   visible.value = true;
   visible.value = true;
-  state.ruleForm = row;
+  if(row instanceof Array){
+    state.ruleForm = {
+      remark: "",
+      domainLimit: true,
+      launch: true,
+      triggerNum: "",
+      triggerRule: 1,
+    } as Form;
+    rows.value = row;
+  }else if(row instanceof Object) {
+    state.ruleForm = row;
+    rows.value = [row]
+  }
   domains.value = [];
   domains.value = [];
   delDomains.value = [];
   delDomains.value = [];
   console.log(row);
   console.log(row);
@@ -147,7 +181,7 @@ const openDialog = async (type: string, row: any, str: string = 'domain') => {
 
 
 // 表单校验规则
 // 表单校验规则
 const dataRules = reactive({
 const dataRules = reactive({
-	triggerNum: [
+  triggerNum: [
     { required: true, message: '触发频率不能为空', trigger: 'blur' },
     { required: true, message: '触发频率不能为空', trigger: 'blur' },
     {
     {
       pattern: /^(10000|[1-9]\d{0,3}|0)$|^(100%|[1-9]?\d%|0%)$/,
       pattern: /^(10000|[1-9]\d{0,3}|0)$|^(100%|[1-9]?\d%|0%)$/,
@@ -157,27 +191,85 @@ const dataRules = reactive({
   ],
   ],
 });
 });
 
 
+/**
+ * 处理数字字符串的方法
+ * @param input - 输入值(字符串或空值)
+ * @param defaultValue - 默认值(默认为0)
+ * @returns 数值类型结果
+ */
+function processNumberString(
+  input: string | null | undefined, 
+  defaultValue: number = 0
+): number {
+  // 处理空值情况
+  if (input === null || input === undefined || input.trim() === '') {
+    return defaultValue;
+  }
+
+  // 去除前后空格
+  const trimmedInput = input.trim();
+
+  // 检查是否为百分比格式
+  if (trimmedInput.endsWith('%')) {
+    const percentStr = trimmedInput.slice(0, -1);
+    const percentValue = parseFloat(percentStr);
+    
+    // 验证是有效的0-100%数字
+    if (!isNaN(percentValue) && percentValue >= 0 && percentValue <= 100) {
+      return percentValue / 100;
+    }
+    return defaultValue; // 无效百分比返回默认值
+  }
+  
+  // 处理普通数字字符串
+  const numValue = parseFloat(trimmedInput);
+  
+  // 验证是0-10000的有效数字
+  if (!isNaN(numValue) && numValue >= 0 && numValue <= 10000) {
+    return numValue;
+  }
+  
+  return defaultValue; // 无效数字返回默认值
+}
+
 // 保存数据
 // 保存数据
 const onSubmit = async () => {
 const onSubmit = async () => {
   console.log({
   console.log({
     ...state.ruleForm,
     ...state.ruleForm,
     domains: domains.value,
     domains: domains.value,
-    delDomains: delDomains.value
+    delDomains: delDomains.value,
+    ips: childIps.value,
+    delIps: delIps.value,
   });
   });
-  ElMessage.success('提交成功!');
-
-  visible.value = false;
-  // try {
-  //   loading.value = true;
-  //   await save(state.ruleForm);
-  //   useMessage().success(t(state.ruleForm.id ? 'common.editSuccessText' : 'common.addSuccessText'));
-  //   visible.value = false;
-  //   emit('refresh');
-  // } catch (err: any) {
-  //   useMessage().error(err.msg);
-  // } finally {
-  //   loading.value = false;
-  // }
+  try {
+    loading.value = true;
+    const data:Form[] = [];
+    rows.value.forEach(async (item) => {
+      data.push({
+        ...item,
+        domains: domains.value,
+        delDomains: state.ruleForm.id ? delDomains.value : item.domains.map((item: DomianItem) => item.id),
+        ips: childIps.value,
+        delIps: state.ruleForm.id ? delIps.value : item.ips.map((item: IpItem) => item.id),
+        remark: state.ruleForm.remark,
+        domainLimit: state.ruleForm.domainLimit,
+        launch: state.ruleForm.launch,
+        triggerNum: processNumberString(state.ruleForm.triggerNum) + '',
+        triggerRule: state.ruleForm.triggerRule,
+      })
+    })
+    console.log('rows.value', data);
+    
+    await appUpdate(data);
+    ElMessage.success('提交成功!');
+    
+    visible.value = false;
+    emit('refresh');
+  } catch (err: any) {
+    useMessage().error(err.msg);
+  } finally {
+    loading.value = false;
+  }
 };
 };
 
 
 // 暴露变量 只有暴漏出来的变量 父组件才能使用
 // 暴露变量 只有暴漏出来的变量 父组件才能使用

+ 9 - 4
src/views/marketing/apps/components/ipCell.vue

@@ -11,13 +11,14 @@
       </el-link>
       </el-link>
     </div>
     </div>
   </div>
   </div>
-  <IpForm ref="IpFormRef" />
+  <IpForm ref="IpFormRef" @refresh="handleUpdate()" />
 </template>
 </template>
 <script setup>
 <script setup>
 import { ref, watch } from 'vue';
 import { ref, watch } from 'vue';
 const IpForm = defineAsyncComponent(() => import('./ipForm.vue'));
 const IpForm = defineAsyncComponent(() => import('./ipForm.vue'));
 
 
-const props = defineProps(['ipList']);
+const props = defineProps(['ipList', 'rowData']);
+const emit = defineEmits(['refresh']);
 
 
 const blacklist = ref([]);
 const blacklist = ref([]);
 const whitelist = ref([]);
 const whitelist = ref([]);
@@ -35,7 +36,7 @@ watch(
       } else {
       } else {
         temp = item.groupName;
         temp = item.groupName;
       }
       }
-      item.ipType == 1 ? blacklist.value.push(temp) : whitelist.value.push(temp);
+      item.ipType == 1 ? whitelist.value.push(temp) : blacklist.value.push(temp);
     });
     });
   },
   },
   { immediate: true }
   { immediate: true }
@@ -43,8 +44,12 @@ watch(
 
 
 // 修改域名弹窗
 // 修改域名弹窗
 const handleEdit = () => {
 const handleEdit = () => {
-  IpFormRef.value.openDialog(props.ipList);
+  IpFormRef.value.openDialog(props.ipList, props.rowData);
 };
 };
+
+const handleUpdate = () => {
+  emit('refresh');
+}
 </script>
 </script>
 <style scoped lang="scss">
 <style scoped lang="scss">
 .cell {
 .cell {

+ 142 - 59
src/views/marketing/apps/components/ipCollapse.vue

@@ -1,99 +1,182 @@
 <template>
 <template>
-  <JCollapse :data="[
-    { title: 'IP集合', id: '1' },
-  ]" @update="(item: any) => listEditOpen = true" @delete="(item: any) => ipDeletable = !ipDeletable"
-    :deleteText="ipDeletable ? '取消' : t('marketingConfig.deleteText')" :updateText="'新增'"
-    :activeNames="['1']">
+  <JCollapse :data="[{ title: 'IP集合', id: '1' }]" @update="(item: any) => listEditOpen = true"
+    @delete="(item: any) => ipDeletable = !ipDeletable"
+    :deleteText="ipDeletable ? '取消' : t('marketingConfig.deleteText')" :updateText="'新增'" :activeNames="['1']">
     <template #default>
     <template #default>
-      <div class="border-b p-2 items-center flex flex-wrap collapse-group min-h-[40px]" v-for="(list, index) in [ips.whitelist, ips.blacklist]" :key="index">
-        <div class="collapse-group-name">{{ index == 0 ? '白名单' : '黑名单'}}:</div>
-        <div class="tag-content" v-if="list.length > 0">
-          <template v-for="item in list" :key="item.id">
-            <!-- 具体IP(段) -->
+      <div class="border-b p-2 items-center flex flex-wrap collapse-group min-h-[40px]" v-for="type in [1, 2]"
+        :key="type">
+        <div class="collapse-group-name">{{ type === 1 ? '白名单' : '黑名单' }}:</div>
+        <div class="tag-content" v-if="getTargetList(type).length > 0">
+          <template v-for="item in getTargetList(type)" :key="item.id">
             <el-tag v-if="item.sourceType == 2" effect="light" :closable="ipDeletable" @close="handleDeleteIp(item)"
             <el-tag v-if="item.sourceType == 2" effect="light" :closable="ipDeletable" @close="handleDeleteIp(item)"
               color="#f4f4f4" round class="ml-1 cursor-pointer" style="margin-bottom: 4px;">
               color="#f4f4f4" round class="ml-1 cursor-pointer" style="margin-bottom: 4px;">
-              {{ item.startIp }}{{ item.ipMode == 2 ? (' / ' + item.endIp.split('.').pop()) : '' }}
+              {{ item.startIp }}{{ item.ipMode == 2 && item.endIp ? (' / ' + item.endIp.split('.').pop()) : '' }}
             </el-tag>
             </el-tag>
-            <!-- 分组 -->
-            <el-popover v-else width="200" trigger="hover" placement="top">
-              <div class="flex flex-wrap break-all">
-                {{ item.groupName }}
+            <el-popover v-else width="200" trigger="hover" placement="top" @show="onLoadDetail(item)">
+              <div v-if="item.list.length > 0" class="flex flex-wrap break-all">
+                <span v-for="ip in item.list" :key="String(ip)" class="mr-2">{{ ip }}</span>
               </div>
               </div>
+              <div v-else>暂无数据</div>
               <template #reference>
               <template #reference>
-                <el-tag @click="getIpList(item)" effect="light" :closable="ipDeletable" @close="handleDeleteIp(item)"
-                  color="#f4f4f4" round class="ml-1 cursor-pointer" style="margin-bottom: 4px;">
-                  {{ item.groupName.length > 30 ? item.groupName.substring(0, 30) + '...' : item.groupName}}
+                <el-tag effect="light" :closable="ipDeletable" @close="handleDeleteIp(item)" color="#f4f4f4" round
+                  class="ml-1 cursor-pointer" style="margin-bottom: 4px;">
+                  {{ item.groupName.length > 30 ? item.groupName.substring(0, 30) + '...' : item.groupName }}
                 </el-tag>
                 </el-tag>
               </template>
               </template>
             </el-popover>
             </el-popover>
           </template>
           </template>
         </div>
         </div>
-        <div class="text-gray-400" v-else>
-          --
-        </div>
+        <div class="text-gray-400" v-else>--</div>
       </div>
       </div>
-
     </template>
     </template>
   </JCollapse>
   </JCollapse>
-  <IpEdit v-model:open="listEditOpen" />
+  <IpEdit v-model:open="listEditOpen" @onsuccess="addIp" />
 </template>
 </template>
-
 <script setup lang="ts" name="ipCollapse">
 <script setup lang="ts" name="ipCollapse">
-import { useI18n } from 'vue-i18n';
 const JCollapse = defineAsyncComponent(() => import('/@/components/JCollapse/index.vue'));
 const JCollapse = defineAsyncComponent(() => import('/@/components/JCollapse/index.vue'));
 const IpEdit = defineAsyncComponent(() => import('./ipEdit.vue'));
 const IpEdit = defineAsyncComponent(() => import('./ipEdit.vue'));
+import { useI18n } from 'vue-i18n';
+import { getGroupDetail } from '/@/api/marketing/config';
+import { ipSplicing } from '/@/utils/ipUpdate';
+import { useMessage } from '/@/hooks/message';
 
 
 interface IpItem {
 interface IpItem {
-  id: String;
-  ipMode: Number;
-  ipType: Number;
-  sourceType: Number; // 1:分组 2:具体Ip(段)
-  startIp: String;
-  endIp: String;
-  groupId: String;
-  groupName: String;
-}
-
-interface Ips {
-  blacklist: IpItem[];
-  whitelist: IpItem[];
+  id: string;
+  ipMode: number;
+  ipType: number; // 1: 白名单, 2: 黑名单
+  sourceType: number;
+  startIp: string;
+  endIp: string;
+  groupId: string;
+  groupName: string;
+  list: string[];
+  modify?: boolean;
 }
 }
 
 
 const props = defineProps(['data']);
 const props = defineProps(['data']);
-
-// 定义子组件向父组件传值/事件
-const emit = defineEmits(['refresh']);
+const emit = defineEmits(['refresh', 'ips', 'delIps']);
 const { t } = useI18n();
 const { t } = useI18n();
 
 
-// 定义变量内容
-const ips = ref<Ips>({
-  blacklist: [],
-  whitelist: []
-});
-const ipDeletable = ref(false);// 控制域名列表项是否可删除
+const ips = ref<IpItem[]>([]);
+const ipDeletable = ref(false);
 const listEditOpen = ref(false);
 const listEditOpen = ref(false);
+const delIps = ref<String[]>([]);
+
+watch(ips, (newVal) => emit('ips', newVal), { deep: true, immediate: true });
+watch(delIps, (newVal) => emit('delIps', newVal), { deep: true, immediate: true });
+
+const getTargetList = (ipType: number) => ips.value.filter(i => i.ipType === ipType);
+
+const setTargetList = (ipType: number, newList: IpItem[]) => {
+  ips.value = [
+    ...ips.value.filter(i => i.ipType !== ipType),
+    ...newList
+  ];
+};
 
 
 watchEffect(() => {
 watchEffect(() => {
   if (props.data) {
   if (props.data) {
-    ips.value.blacklist = props.data.filter((item: any) => item.ipType == '1');
-    ips.value.whitelist = props.data.filter((item: any) => item.ipType == '2');
+    ips.value = props.data.map((item: any) => ({ ...item, list: [] }));
   }
   }
-  console.log('ips', ips.value);
-})
+});
 
 
-const getIpList = (detail: IpItem) => {
-  if (detail.sourceType == 1) {
-    // 获取ip组
-    console.log(detail.groupId);
+const handleDeleteIp = (deleteItem: IpItem) => {
+  if (!deleteItem.modify && !delIps.value.includes(deleteItem.id)) {
+    delIps.value.push(deleteItem.id);
   }
   }
+  ips.value = ips.value.filter(i => {
+    if (i.id !== deleteItem.id) return true;
+    if (deleteItem.sourceType === 1) return i.groupId !== deleteItem.groupId;
+    return i.startIp !== deleteItem.startIp || i.endIp !== deleteItem.endIp;
+  });
+};
+
+const onLoadDetail = async (item: any) => {
+  if (item.list.length || item.modify) return;
+  const val = await getGroupDetail({ id: item.groupId });
+  item.list = val.data.ips.map((item: any) => ipSplicing(item.startIp, item.endIp));
+};
+
+function addGroupsUnique(newGroups: IpItem[], ipType: number) {
+  const targetList = getTargetList(ipType);
+  const conflictType = ipType === 1 ? 2 : 1;
+  const conflictList = getTargetList(conflictType);
+  const existIds = new Set(targetList.filter(i => i.sourceType === 1).map(i => i.groupId));
+  const conflictIds = new Set(conflictList.filter(i => i.sourceType === 1).map(i => i.groupId));
+
+  const conflict = newGroups.find(i => conflictIds.has(i.id));
+  if (conflict) {
+    useMessage().warning(`分组 ${ conflict.groupName } 已存在于 ${ ipType === 1 ? '黑名单' : '白名单'} 中`);
+  return;
 }
 }
 
 
-// 删除ip
-const handleDeleteIp = (deleteItem: IpItem) => {
-  ips.value.whitelist = ips.value.whitelist.filter((item: any) => item.id != deleteItem.id);
-  ips.value.blacklist = ips.value.blacklist.filter((item: any) => item.id != deleteItem.id);
+const filtered = newGroups.filter(i => !existIds.has(i.id));
+
+if (filtered.length) {
+  const processedGroups = filtered.map(item => ({
+    id: item.id,
+    ipType,
+    sourceType: 1,
+    groupId: item.id,
+    groupName: item.groupName,
+    isMode: 1,
+    startIp: '',
+    endIp: '',
+    modify: true,
+    list: item.list || []
+  }));
+  setTargetList(ipType, [...targetList, ...processedGroups]);
+  useMessage().success(t('common.addSuccessText'));
+} else {
+  useMessage().warning('分组重复');
+}
+}
+
+function addSinglesUnique(newSingles: IpItem[], ipType: number) {
+  const targetList = getTargetList(ipType);
+  const conflictType = ipType === 1 ? 2 : 1;
+  const conflictList = getTargetList(conflictType);
+
+  const existIps = new Set(targetList.filter(i => i.sourceType === 2).map(i => i.startIp));
+  const conflictIps = new Set(conflictList.filter(i => i.sourceType === 2).map(i => i.startIp));
+
+  const conflict = newSingles.find(i => conflictIps.has(i.startIp));
+  if (conflict) {
+    console.log(conflict);
+    useMessage().warning(`IP ${ conflict.startIp } 已存在于 ${ ipType === 1 ? '黑名单' : '白名单'} 中`);
+  return;
 }
 }
 
 
+const filtered = newSingles.filter(i => !existIps.has(i.startIp));
+
+if (filtered.length) {
+  const processedSingles = filtered.map(i => ({
+    // id: i.id,
+    endIp: i.endIp,
+    groupId: '0',
+    groupName: '',
+    ipMode: i.endIp ? 2 : 1,
+    startIp: i.startIp,
+    sourceType: 2,
+    modify: true,
+    ipType,
+    list: []
+  }));
+  setTargetList(ipType, [...targetList, ...processedSingles]);
+  useMessage().success(t('common.addSuccessText'));
+} else {
+  useMessage().warning('IP重复');
+}
+}
+
+const addIp = (data: IpItem[], sourceType: 1 | 2, ipType: '1' | '2') => {
+  const ipTypeNum = Number(ipType);
+  if (sourceType === 2) {
+    data.length && addSinglesUnique(data, ipTypeNum);
+  } else {
+    data.length && addGroupsUnique(data, ipTypeNum);
+  }
+};
 </script>
 </script>
 <style lang="scss">
 <style lang="scss">
 .el-collapse-item__content {
 .el-collapse-item__content {

+ 100 - 41
src/views/marketing/apps/components/ipEdit.vue

@@ -2,14 +2,14 @@
 	<el-dialog :title="type === 'Edit' ? '修改IP' : '添加IP'" width="600" v-model="props.open" :close-on-click-modal="false"
 	<el-dialog :title="type === 'Edit' ? '修改IP' : '添加IP'" width="600" v-model="props.open" :close-on-click-modal="false"
 		:destroy-on-close="true" @close="onCancel" draggable>
 		:destroy-on-close="true" @close="onCancel" draggable>
 		<el-form style="height: 128px;" ref="menuDialogFormRef" :rules="dataRules" :model="state.ruleForm" v-loading="loading">
 		<el-form style="height: 128px;" ref="menuDialogFormRef" :rules="dataRules" :model="state.ruleForm" v-loading="loading">
-			<el-form-item label="" prop="listType">
-				<el-radio-group v-model="state.ruleForm.listType">
-					<el-radio value="1">白名单</el-radio>
-					<el-radio value="2">黑名单</el-radio>
+			<el-form-item label="" prop="ipType">
+				<el-radio-group v-model="state.ruleForm.ipType">
+					<el-radio :value="1">白名单</el-radio>
+					<el-radio :value="2">黑名单</el-radio>
 				</el-radio-group>
 				</el-radio-group>
 			</el-form-item>
 			</el-form-item>
 			<div class="custom-style">
 			<div class="custom-style">
-				<el-segmented v-model="activeName" :options="[t('marketingConfig.grouping'), t('marketingConfig.ipSegment')]"
+				<el-segmented v-model="state.ruleForm.sourceTypeText" :options="[t('marketingConfig.grouping'), t('marketingConfig.ipSegment')]"
 					size="default">
 					size="default">
 					<template #default="scope">
 					<template #default="scope">
 						<div style="min-width: 50px; line-height: 32px;">
 						<div style="min-width: 50px; line-height: 32px;">
@@ -30,10 +30,10 @@
 					</template>
 					</template>
 				</el-segmented>
 				</el-segmented>
 			</div>
 			</div>
-			<el-form-item label="" prop="grouping" v-if="activeName === t('marketingConfig.grouping')">
-				<el-select v-model="state.ruleForm.groupingTip" :placeholder="t('marketingConfig.groupingTip')"
-					clearable>
-					<el-option v-for="item in listSelect" :key="item.value" :label="item.label" :value="item.value" />
+			<el-form-item label="" prop="groupId" v-if="state.ruleForm.sourceTypeText === t('marketingConfig.grouping')">
+				<el-select v-model="state.ruleForm.groupId" :placeholder="t('marketingConfig.groupingTip')"
+					multiple clearable collapse-tags collapse-tags-tooltip :max-collapse-tags="5">
+					<el-option v-for="item in selectData" :key="item.id" :label="item.groupName" :value="item.id" />
 				</el-select>
 				</el-select>
 			</el-form-item>
 			</el-form-item>
 			<el-form-item label="" prop="ip" v-else>
 			<el-form-item label="" prop="ip" v-else>
@@ -50,9 +50,30 @@
 	</el-dialog>
 	</el-dialog>
 </template>
 </template>
 
 
-<script setup name="systemMenuDialog">
+<script setup name="systemMenuDialog" lang="ts">
 import { useI18n } from 'vue-i18n';
 import { useI18n } from 'vue-i18n';
 import { useMessage } from '/@/hooks/message';
 import { useMessage } from '/@/hooks/message';
+import {
+	pageListIp,
+} from '/@/api/marketing/config';
+import { ipSplicing } from '/@/utils/ipUpdate';
+import { rule } from '/@/utils/validate';
+import { parseIpRange } from '/@/utils/ipUpdate';
+
+interface IpItem {
+	id: Number;
+	groupId: Number;
+	groupName: String;
+	ipMode: Number;
+	ip: String;
+	startIp: String;
+	endIp: String;
+	sourceType: Number;
+	list: {
+		id: Number;
+		value: String;
+	}[];
+}
 
 
 // 定义子组件向父组件传值/事件
 // 定义子组件向父组件传值/事件
 const emit = defineEmits(['update:open', 'onsuccess']);
 const emit = defineEmits(['update:open', 'onsuccess']);
@@ -61,38 +82,23 @@ const { t } = useI18n();
 // 定义变量内容
 // 定义变量内容
 const loading = ref(false);
 const loading = ref(false);
 const type = ref('add'); // 'add' or 'edit'
 const type = ref('add'); // 'add' or 'edit'
-const activeName = ref(t('marketingConfig.grouping'));
 const menuDialogFormRef = ref();
 const menuDialogFormRef = ref();
-const listSelect = ref([
-	{
-		value: 'group1',
-		label: '分组1',
-	},
-	{
-		value: 'group2',
-		label: '分组2',
-	},
-	{
-		value: 'group3',
-		label: '分组3',
-	},
-]);
+const selectData = ref<IpItem[]>([]);
+const selectedObjects = ref<IpItem[]>([]);
+
 // 定义需要的数据
 // 定义需要的数据
 const state = reactive({
 const state = reactive({
 	ruleForm: {
 	ruleForm: {
-		listType: '1', // 1: 白名单, 2: 黑名单
-		list: [''],
-		https: [''], // 域名集合
-		ips: [''], // ip集合
-		// {
-		//   id: '1',
-		//   ip: '美国',
-		//   appId: '1',
-		//   appUrl: 'www.baidu.com',
-		//   appName: 'adsa'
-		// },
+		ipType: 1, // 1: 白名单, 2: 黑名单
+		sourceType: 1,
+		sourceTypeText: t('marketingConfig.grouping'),
+		groupId: [] as IpItem[],
+		ipMode: 1,
+		groupName:'',
+		startIp: '',
+		endIp: '',
+		ip: '',
 	},
 	},
-	appList: [], // 应用下拉框列表
 });
 });
 
 
 const props = defineProps({
 const props = defineProps({
@@ -104,27 +110,80 @@ const props = defineProps({
 
 
 // 表单校验规则
 // 表单校验规则
 const dataRules = reactive({
 const dataRules = reactive({
-	domain: [{ required: true, message: '域名不能为空', trigger: 'blur' }],
+	groupId: [{ required: true, message: '分组不能为空', trigger: 'blur' }],
+	ip: [
+		{ required: rule.ip, message: 'IP不能为空', trigger: 'blur' },
+		{ validator: rule.ip, trigger: 'blur' },
+	],
 });
 });
 
 
+// 获取ip列表
+const getIpData = async () => {
+	await pageListIp().then((val) => {
+		selectData.value = val.data.map((item) => {
+			return {
+				...item,
+				title: item.groupName,
+				id: item.id,
+				list: item.ips.map((items) => {
+					return {
+						...items,
+						id: items.id,
+						value: ipSplicing(items.startIp, items.endIp),
+					};
+				}),
+			};
+		});
+	});
+};
+
+watchEffect(() => {
+  if (props.open) {
+	state.ruleForm.groupId = [];
+	state.ruleForm.ip = '';
+	state.ruleForm.ipType = 1;
+	getIpData();
+  }
+})
+
 const onCancel = () => {
 const onCancel = () => {
 	emit('update:open', false);
 	emit('update:open', false);
 };
 };
 
 
 // 保存数据
 // 保存数据
 const onSubmit = async () => {
 const onSubmit = async () => {
+	const lis = parseIpRange(state.ruleForm.ip);
+	state.ruleForm.startIp = lis.start;
+	state.ruleForm.endIp = lis.end;
+	if (lis.end !== '') {
+		state.ruleForm.ipMode = 2;
+	}
+	
+	state.ruleForm.sourceType = state.ruleForm.sourceTypeText === t('marketingConfig.grouping') ? 1 : 2;
+	if (state.ruleForm.sourceType == 1 && state.ruleForm.groupId.length == 0) {
+		return useMessage().error('请选择分组');
+	} else if (state.ruleForm.sourceType == 2 && state.ruleForm.ip == '') {
+		return useMessage().error('ip不能为空');
+	}
+	
 	try {
 	try {
 		loading.value = true;
 		loading.value = true;
-		// await save(state.ruleForm);
-		useMessage().success(t(state.ruleForm.id ? 'common.editSuccessText' : 'common.addSuccessText'));
-		emit('onsuccess');
+		if (state.ruleForm.sourceTypeText == t('marketingConfig.grouping')) {
+			selectedObjects.value = [];
+			selectedObjects.value = selectData.value.filter(item => state.ruleForm.groupId.includes(item.id));
+			emit('onsuccess', selectedObjects.value, state.ruleForm.sourceType, state.ruleForm.ipType + '');
+		}else if(state.ruleForm.startIp.length > 0) {
+			emit('onsuccess', [state.ruleForm], state.ruleForm.sourceType, state.ruleForm.ipType + '');
+		}
 		onCancel();
 		onCancel();
 	} catch (err) {
 	} catch (err) {
+		console.log(err);
 		useMessage().error(err.msg);
 		useMessage().error(err.msg);
 	} finally {
 	} finally {
 		loading.value = false;
 		loading.value = false;
 	}
 	}
 };
 };
+
 </script>
 </script>
 <style>
 <style>
 .apps-loadmore.el-select-dropdown .el-scrollbar__wrap {
 .apps-loadmore.el-select-dropdown .el-scrollbar__wrap {

+ 17 - 2
src/views/marketing/apps/components/ipForm.vue

@@ -3,7 +3,7 @@
     <el-dialog
     <el-dialog
       append-to-body :title="'修改IP'" width="880" v-model="visible" :close-on-click-modal="false" :destroy-on-close="true"
       append-to-body :title="'修改IP'" width="880" v-model="visible" :close-on-click-modal="false" :destroy-on-close="true"
       draggable>
       draggable>
-      <IpCollapse :data=ips></IpCollapse>
+      <IpCollapse :data=ips  @ips="updateIps" @delIps="updateDelIps"></IpCollapse>
       <template #footer>
       <template #footer>
         <span class="dialog-footer">
         <span class="dialog-footer">
           <el-button @click="visible = false">{{ t('common.cancelButtonText') }}</el-button>
           <el-button @click="visible = false">{{ t('common.cancelButtonText') }}</el-button>
@@ -18,6 +18,7 @@
 <script setup lang="ts" name="systemMenuDialog">
 <script setup lang="ts" name="systemMenuDialog">
 import { ElMessage } from 'element-plus';
 import { ElMessage } from 'element-plus';
 import { useI18n } from 'vue-i18n';
 import { useI18n } from 'vue-i18n';
+import { updateModIp } from '/@/api/marketing/apps';
 const IpCollapse = defineAsyncComponent(() => import('./ipCollapse.vue'));
 const IpCollapse = defineAsyncComponent(() => import('./ipCollapse.vue'));
 
 
 interface IpItem {
 interface IpItem {
@@ -36,17 +37,31 @@ const { t } = useI18n();
 const visible = ref(false);
 const visible = ref(false);
 const loading = ref(false);
 const loading = ref(false);
 const ips = ref<IpItem[]>([]);
 const ips = ref<IpItem[]>([]);
+const childIps = ref();
+const delIps = ref<String[]>([]);
+const rowData = ref();
 
 
 // 打开弹窗
 // 打开弹窗
-const openDialog = async (data: any) => {
+const openDialog = async (data: any, row) => {
   visible.value = true;
   visible.value = true;
   ips.value = data;
   ips.value = data;
+  rowData.value = row;
 };
 };
 
 
+const updateIps = (data: IpItem[])=>{
+  childIps.value = data;
+}
+const updateDelIps = (data: String[]) => {
+  delIps.value = data;
+}
+
 // 保存数据
 // 保存数据
 const onSubmit = async () => {
 const onSubmit = async () => {
   ElMessage.success('提交成功!');
   ElMessage.success('提交成功!');
   visible.value = false;
   visible.value = false;
+  updateModIp({ ips: childIps.value, delIps: delIps.value, id: rowData.value.id}).then((res) => {
+    emit('refresh');
+  })
 };
 };
 
 
 // 暴露变量 只有暴漏出来的变量 父组件才能使用
 // 暴露变量 只有暴漏出来的变量 父组件才能使用

+ 4 - 2
src/views/marketing/apps/components/statistical.vue

@@ -7,7 +7,7 @@
     :close-on-click-modal="false" 
     :close-on-click-modal="false" 
     draggable>
     draggable>
     <div class="statistics-table-wrapper">
     <div class="statistics-table-wrapper">
-      <StatisticsIndex />
+      <StatisticsIndex :row="row" />
     </div>
     </div>
   </el-dialog>
   </el-dialog>
 </template>
 </template>
@@ -17,10 +17,12 @@ import { ref } from 'vue';
 const StatisticsIndex = defineAsyncComponent(() => import('/@/views/marketing/statistics/index.vue'));
 const StatisticsIndex = defineAsyncComponent(() => import('/@/views/marketing/statistics/index.vue'));
 
 
 const visible = ref(false);
 const visible = ref(false);
+const row = ref({});
 
 
 // 打开弹窗
 // 打开弹窗
-const openDialog = async (row: any) => {
+const openDialog = async (_row: any) => {
   visible.value = true;
   visible.value = true;
+  row.value = _row;
 };
 };
 
 
 // 暴露变量 只有暴漏出来的变量 父组件才能使用
 // 暴露变量 只有暴漏出来的变量 父组件才能使用

+ 22 - 24
src/views/marketing/apps/index.vue

@@ -80,15 +80,15 @@
         </el-table-column>
         </el-table-column>
         <el-table-column :label="'域名集合'" prop="domains" width="280" show-overflow-tooltip>
         <el-table-column :label="'域名集合'" prop="domains" width="280" show-overflow-tooltip>
           <template #default="{ row }">
           <template #default="{ row }">
-            <DomainCell :domainList="row.domains" :rowData="row" />
+            <DomainCell :domainList="row.domains" :rowData="row" @refresh="getDataList()" />
           </template>
           </template>
         </el-table-column>
         </el-table-column>
         <el-table-column :label="'IP集合'" prop="ips" width="320">
         <el-table-column :label="'IP集合'" prop="ips" width="320">
           <template #default="{ row }">
           <template #default="{ row }">
-            <IpCell :ipList="row.ips" />
+            <IpCell :ipList="row.ips" :rowData="row" @refresh="getDataList()" />
           </template>
           </template>
         </el-table-column>
         </el-table-column>
-        <el-table-column :label="'触发频率'" prop="triggerRule" width="150" show-overflow-tooltip>
+        <el-table-column :label="'触发频率'" prop="triggerNum" width="150" show-overflow-tooltip>
           <template #default="{ row }">
           <template #default="{ row }">
             <!-- <el-input-number v-model="row.triggerNum" :max=20 /> -->
             <!-- <el-input-number v-model="row.triggerNum" :max=20 /> -->
              {{ row.triggerNum }}
              {{ row.triggerNum }}
@@ -101,6 +101,11 @@
             </el-select>
             </el-select>
           </template>
           </template>
         </el-table-column>
         </el-table-column>
+        <el-table-column :label="'备注'" prop="remark" width="150" show-overflow-tooltip>
+          <template #default="{ row }">
+             {{ row.remark }}
+          </template>
+        </el-table-column>
         <el-table-column fixed="right" :label="t('common.action')" width="200">
         <el-table-column fixed="right" :label="t('common.action')" width="200">
           <template #default="scope">
           <template #default="scope">
             <div class="action" style="text-align: left;">
             <div class="action" style="text-align: left;">
@@ -158,7 +163,7 @@
 </template>
 </template>
 
 
 <script lang="ts" name="marketingApps" setup>
 <script lang="ts" name="marketingApps" setup>
-import { pageList, pageDel, delAppById } from '/@/api/marketing/apps';
+import { pageList, pageDel, delAppById, setAppInfo } from '/@/api/marketing/apps';
 import { BasicTableProps, useTable } from '/@/hooks/table';
 import { BasicTableProps, useTable } from '/@/hooks/table';
 import { useMessage, useMessageBox } from '/@/hooks/message';
 import { useMessage, useMessageBox } from '/@/hooks/message';
 import { useI18n } from 'vue-i18n';
 import { useI18n } from 'vue-i18n';
@@ -173,7 +178,7 @@ import { ElMessage, type TabsPaneContext } from 'element-plus'
 
 
 const activeName = ref('tab1')
 const activeName = ref('tab1')
 const handleClick = (tab: TabsPaneContext, event: Event) => {
 const handleClick = (tab: TabsPaneContext, event: Event) => {
-  console.log(tab, event);
+  activeName.value = tab.paneName as string;
   query();
   query();
 }
 }
 
 
@@ -189,7 +194,7 @@ const selectObjs = ref([]) as any;
 // 是否可以多选
 // 是否可以多选
 const multiple = ref(true);
 const multiple = ref(true);
 const state: BasicTableProps = reactive<BasicTableProps>({
 const state: BasicTableProps = reactive<BasicTableProps>({
-  pageList: activeName.value == 'tab1' ? pageList : pageDel,
+  pageList: pageList,
   createdIsNeed: false,
   createdIsNeed: false,
   queryForm: {
   queryForm: {
     appId: '',
     appId: '',
@@ -231,6 +236,7 @@ const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTa
 
 
 // 搜索事件
 // 搜索事件
 const query = () => {
 const query = () => {
+  activeName.value == 'tab1' ? state.pageList = pageList : state.pageList = pageDel;
   state.dataList = [];
   state.dataList = [];
   state.queryForm.domainType = state.queryForm.domainSelected == 0 ? '' : state.queryForm.domainSelected;
   state.queryForm.domainType = state.queryForm.domainSelected == 0 ? '' : state.queryForm.domainSelected;
   getDataList();
   getDataList();
@@ -238,13 +244,12 @@ const query = () => {
 
 
 // 修改单条配置信息
 // 修改单条配置信息
 const handleChange = (row: any) => {
 const handleChange = (row: any) => {
-  // update(row).then(() => {
-  //   useMessage().success(t('common.updateSuccessText'));
-  //   getDataList();
-  // }).catch((err: any) => {
-  //   useMessage().error(err.msg);
-  // });
-  useMessage().success('修改成功');
+  setAppInfo(row).then(() => {
+    useMessage().success('修改成功');
+    getDataList();
+  }).catch((err: any) => {
+    useMessage().error(err.msg);
+  });
 };
 };
 
 
 // 清空搜索条件
 // 清空搜索条件
@@ -256,12 +261,13 @@ const resetQuery = () => {
 
 
 // 批量修改配置
 // 批量修改配置
 const handleEdit = () => {
 const handleEdit = () => {
+  console.log('selectObjs', selectObjs);
+  
   if (selectObjs.value.length === 0) {
   if (selectObjs.value.length === 0) {
     useMessage().warning('请选择要修改的项');
     useMessage().warning('请选择要修改的项');
     return;
     return;
   }
   }
-  EditDialogRef.value.openDialog('', {});
-  // EditDialogRef.value.openDialog('edit', selectObjs.value);
+  EditDialogRef.value.openDialog('', selectObjs.value);
 };
 };
 
 
 // 多选
 // 多选
@@ -279,7 +285,7 @@ const onOpenStatistical = (row: any) => {
   statisticalDialogRef.value.openDialog(row);
   statisticalDialogRef.value.openDialog(row);
 };
 };
 
 
-// 删除操作
+// 拉黑操作
 const handleDelete = async (row: any) => {
 const handleDelete = async (row: any) => {
 	try {
 	try {
       await useMessageBox().confirm('是否确认拉黑该应用');
       await useMessageBox().confirm('是否确认拉黑该应用');
@@ -291,14 +297,6 @@ const handleDelete = async (row: any) => {
 	} catch {
 	} catch {
 		return;
 		return;
 	}
 	}
-
-	// try {
-	// 	await delObj(ids);
-	// 	getDataList();
-	// 	useMessage().success(t('common.delSuccessText'));
-	// } catch (err: any) {
-	// 	useMessage().error(err.msg);
-	// }
   query();
   query();
 };
 };
 
 

+ 2 - 1
src/views/marketing/statistics/index.vue

@@ -80,7 +80,7 @@ const showSearch = ref(true);
 const expandedRowKeys = ref<number[]>([]); // 记录展开的行
 const expandedRowKeys = ref<number[]>([]); // 记录展开的行
 
 
 // 接受props
 // 接受props
-defineProps({
+const props = defineProps({
   row: {
   row: {
     type: Object,
     type: Object,
     default: () => ({})
     default: () => ({})
@@ -99,6 +99,7 @@ defineProps({
 const state: BasicTableProps = reactive<BasicTableProps>({
 const state: BasicTableProps = reactive<BasicTableProps>({
   queryForm: {
   queryForm: {
     ip: '',
     ip: '',
+    appId: props.row.id || '',
   },
   },
   pageList: pageList,
   pageList: pageList,
   pagination: {
   pagination: {