123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565 |
- <template>
- <div class="layout-padding">
- <el-tabs v-model="activeName" type="card" class="demo-tabs" @tab-click="handleClick">
- <el-tab-pane :label="t('marketingConfig.ipList')" name="IP分组" class="layout-padding-auto layout-padding-view">
- <Title class="ml-4" :title="t('marketingConfig.ipList')" />
- <div class="p-4 rounded">
- <el-button style="margin-bottom: 10px;" type="primary" @click="onClickAdd('ip')">{{ t('marketingConfig.addIpList') }}</el-button><br>
- 筛选分组:<el-input placeholder="请输入分组名称" clearable
- style="display: inline-block; width: 200px; margin-left: 5px;"
- v-model="queryIPName" />
- <div v-if="ipData.length > 0" class="overflow-y-auto mt-2" style="height: calc(100vh - 300px)">
- <JCollapse
- @update="(item) => onClickEdit(item, 'ip')"
- @delete="(item) => onOpenDelete(item, 'ip')"
- :data="showIPData"
- :activeNames="ipActiveId"
- :deleteText="t('marketingConfig.deleteListText')"
- :updateText="t('marketingConfig.updateText')"
- />
- </div>
- </div>
- </el-tab-pane>
- <el-tab-pane :label="t('marketingConfig.domainList')" name="域名分组" class="layout-padding-auto layout-padding-view">
- <Title class="ml-4" :title="t('marketingConfig.domainList')" />
- <div class="p-4 rounded">
- <el-button style="margin-bottom: 10px;" type="primary" @click="onClickAdd('domain')">{{ t('marketingConfig.addDomainList') }}</el-button><br>
- 筛选分组:<el-input placeholder="请输入分组名称" clearable
- style="display: inline-block; width: 200px; margin-left: 5px;"
- v-model="queryDomainName" />
- <div v-if="domainData.length > 0" class="overflow-y-auto mt-2" style="height: calc(100vh - 300px)">
- <JCollapse
- @update="(item) => onClickEdit(item, 'domain')"
- :data="showDomainData"
- @delete="(item) => onOpenDelete(item, 'domain')"
- :activeNames="domainActiveId"
- :deleteText="t('marketingConfig.deleteListText')"
- :updateText="t('marketingConfig.updateText')"
- />
- </div>
- <div v-else class="ml-2">暂无数据</div>
- </div>
- </el-tab-pane>
- <el-tab-pane :label="t('marketingConfig.disposition')" name="全局配置" class="layout-padding-auto layout-padding-view">
- <Title class="ml-4" :title="t('marketingConfig.disposition')" />
- <div class="p-4 rounded overflow-y-auto" style="max-height: calc(100vh - 350px)">
- <JCollapse
- :data="[{ title: 'IP集合', id: '1' }]"
- :activeNames="['1']"
- @update="(item) => (listEditOpen = true)"
- @delete="(item) => (closeIpTags = !closeIpTags)"
- :deleteText="closeIpTags ? t('marketingConfig.cancel') : t('marketingConfig.deleteIp')"
- :updateText="t('marketingConfig.addIp')"
- >
- <template #default>
- <div class="border-b p-2 items-center flex flex-wrap">
- <span class="mr-2">白名单</span>
- <span v-for="item in ipWhiteList" :key="item.id">
- <el-popover v-if="item?.sourceType === 1" width="280" @show="onLoadDetail(item)" trigger="hover" placement="top">
- <div v-if="item.list.length > 0" class="flex flex-wrap">
- <span v-for="ip in item.list" :key="ip" class="ml-2">
- {{ ip }}
- </span>
- </div>
- <div v-else>暂无数据</div>
- <template #reference>
- <el-tag
- effect="light"
- color="#f4f4f4"
- :closable="closeIpTags"
- @close="handleDelete(item, 'ip')"
- round
- class="ml-1 cursor-pointer"
- >
- {{ item.groupName }}
- </el-tag>
- </template>
- </el-popover>
- <el-tag
- v-else
- effect="light"
- color="#f4f4f4"
- :closable="closeIpTags"
- @close="handleDelete(item, 'ip')"
- round
- class="ml-1 cursor-pointer"
- >
- {{ ipSplicing(item.startIp, item.endIp) }}
- </el-tag>
- </span>
- </div>
- <div class="p-2 items-center flex flex-wrap">
- <span class="mr-2">黑名单</span>
- <span v-for="item in ipBlackList" :key="item.id">
- <el-popover v-if="item?.sourceType === 1" width="280" @show="onLoadDetail(item, ipList)" trigger="hover" placement="top">
- <div v-if="item.list.length > 0" class="flex flex-wrap">
- <span v-for="ip in item.list" :key="ip" class="ml-2">
- {{ ip }}
- </span>
- </div>
- <div v-else>暂无数据</div>
- <template #reference>
- <el-tag
- effect="light"
- color="#f4f4f4"
- :closable="closeIpTags"
- @close="handleDelete(item, 'ip')"
- round
- class="ml-1 cursor-pointer"
- >
- {{ item.groupName }}
- </el-tag>
- </template>
- </el-popover>
- <el-tag
- v-else
- effect="light"
- color="#f4f4f4"
- :closable="closeIpTags"
- @close="handleDelete(item, 'ip')"
- round
- class="ml-1 cursor-pointer"
- >
- {{ ipSplicing(item.startIp, item.endIp) }}
- </el-tag>
- </span>
- </div>
- </template>
- </JCollapse>
- <JCollapse
- class="mt-4"
- :data="[{ title: '域名集合', id: '1' }]"
- :activeNames="['1']"
- @update="() => (domainEditOpen = true)"
- @delete="() => (closeDomainTags = !closeDomainTags)"
- :deleteText="closeDomainTags ? t('marketingConfig.cancel') : t('marketingConfig.deleteDomain')"
- :updateText="t('marketingConfig.addDomain')"
- >
- <template #default>
- <div class="p-2 items-center flex flex-wrap">
- <span v-if="domainList.length > 0">
- <span v-for="item in domainList" :key="item.id">
- <el-popover v-if="item?.sourceType === 1" width="280" @show="onLoadDetail(item)" trigger="hover" placement="top">
- <div v-if="item.list.length > 0" class="flex flex-wrap">
- <span v-for="domain in item.list" :key="domain.id" class="ml-2">
- {{ domain.domain }}
- </span>
- </div>
- <div v-else>暂无数据</div>
- <template #reference>
- <el-tag
- effect="light"
- color="#f4f4f4"
- :closable="closeDomainTags"
- @close="handleDelete(item, 'domain')"
- round
- class="ml-1 cursor-pointer"
- >
- {{ item.groupName }}
- </el-tag>
- </template>
- </el-popover>
- <el-tag
- v-else
- effect="light"
- color="#f4f4f4"
- :closable="closeDomainTags"
- @close="handleDelete(item, 'domain')"
- round
- class="ml-1 cursor-pointer"
- >
- {{ item.domain }}
- </el-tag>
- </span>
- </span>
- <div class="text-gray-400 ml-2" v-else>--</div>
- </div>
- </template>
- </JCollapse>
- </div>
- <div class="w-[66%] ml-[-8px] mt-5">
- <el-form ref="ruleFormRef" :model="formData" :rules="dataRules" label-width="90px" class="flex flex-wrap">
- <el-form-item :label="t('marketingConfig.jumpMode')" prop="triggerMode" class="w-1/3">
- <JDictSelect
- v-model:value="formData.triggerMode"
- :placeholder="t('marketingConfig.jumpModeTip')"
- :dictType="'triggerMode'"
- :selectFirst="true"
- :styleClass="'w-full'"
- />
- </el-form-item>
- <el-form-item :label="t('marketingConfig.triggerType')" prop="triggerRule" class="w-1/3">
- <JDictSelect
- v-model:value="formData.triggerRule"
- :placeholder="t('marketingConfig.triggerTypeTip')"
- :dictType="'triggerRule'"
- :selectFirst="true"
- :styleClass="'w-full'"
- />
- </el-form-item>
- <el-form-item :label="t('marketingConfig.triggerFrequency')" prop="triggerNum" class="w-1/3">
- <el-input v-model="formData.triggerNum" type="text" :placeholder="t('marketingConfig.triggerFrequencyTip')" />
- </el-form-item>
- <div class="w-full mb-[18px]">
- <el-form-item
- v-if="formData.triggerMode == '2' || formData.triggerMode == '3'"
- :label="t('marketingConfig.prompt')"
- prop="promptMsg"
- class="w-1/3"
- >
- <el-input v-model="formData.promptMsg" type="text" :placeholder="t('marketingConfig.promptTip')"></el-input>
- </el-form-item>
- <el-form-item
- v-if="formData.triggerMode == '1' || formData.triggerMode == '3'"
- :label="t('marketingConfig.jumpLink')"
- prop="url"
- class="w-1/3"
- >
- <el-input v-model="formData.url" type="text" :placeholder="t('marketingConfig.jumpLinkTip')"></el-input>
- </el-form-item>
- </div>
- <div class="w-full">
- <el-button type="primary" @click="onSubmit(ruleFormRef)" class="w-[80px] ml-5">{{ t('common.saveBtn') }}</el-button>
- </div>
- </el-form>
- </div>
- </el-tab-pane>
- </el-tabs>
- <DomainEdit :select-data="domainData" v-model:open="domainEditOpen" @onsuccess="getConfig" />
- <ListEdit :select-data="ipData" v-model:open="listEditOpen" @onsuccess="getConfig" />
- <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>
- <template #footer>
- <div class="dialog-footer">
- <el-button @click="delOpen = false">取消</el-button>
- <el-button type="primary" :disabled="loading" @click="onDel(delObj)"> 确定 </el-button>
- </div>
- </template>
- </el-dialog>
- </div>
- </template>
- <script lang="ts" name="marketingConfig" setup>
- import {
- delGroup,
- pageListDomain,
- pageListIp,
- getConfigIpList,
- getConfigDomainList,
- delIpList,
- delDomainList,
- getGroupDetail,
- getConfigDetail,
- saveConfigDetail,
- } from '/@/api/marketing/config';
- import { useI18n } from 'vue-i18n';
- import { useMessage } from '/@/hooks/message';
- import { rule } from '/@/utils/validate';
- import { ipSplicing } from '/@/utils/ipUpdate';
- // 引入组件
- const JCollapse = defineAsyncComponent(() => import('/@/components/JCollapse/index.vue'));
- const Title = defineAsyncComponent(() => import('/@/components/Title/index.vue'));
- const DomainEdit = defineAsyncComponent(() => import('./components/domainEdit.vue'));
- const ListEdit = defineAsyncComponent(() => import('./components/listEdit.vue'));
- const GroupingEdit = defineAsyncComponent(() => import('./components/ipGroupingEdit.vue'));
- const IpListEdit = defineAsyncComponent(() => import('./components/ipListEdit.vue'));
- const JDictSelect = defineAsyncComponent(() => import('/@/components/JDictSelect/index.vue'));
- const { t } = useI18n();
- // 定义变量内容
- const activeName = ref('IP分组');
- const queryIPName = ref('');
- const queryDomainName = ref('');
- const showIPData = computed(() => {
- return ipData.value.filter((item) => item.groupName.includes(queryIPName.value));
- });
- const showDomainData = computed(() => {
- return domainData.value.filter((item) => item.groupName.includes(queryDomainName.value));
- });
- //关闭或打开tabs的关闭按钮
- const closeDomainTags = ref(false);
- const closeIpTags = ref(false);
- // 弹窗
- const domainEditOpen = ref(false);
- const listEditOpen = ref(false);
- const groupingEditOpen = ref(false);
- 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([]);
- const delObj = ref({});
- const openType = ref('ip'); // 'ip' or 'domain'
- const ipWhiteList = ref([]); // ip白名单
- const ipBlackList = ref([]); // ip黑名单
- const domainList = ref([]);
- const ruleFormRef = ref();
- const formData = ref({
- promptMsg: '',
- triggerMode: '',
- triggerRule: '',
- url: '',
- triggerNum: '',
- });
- const onOpenDelete = (item: any, type: any) => {
- console.log(item);
- delObj.value = { ...item, type };
- console.log(delObj.value);
- delOpen.value = true;
- };
- const getData = () => {
- openType.value === 'ip' ? getIpData() : getDomainData();
- };
- const onLoadDetail = async (item: any) => {
- if (item.list.length !== 0) return;
- await getGroupDetail({ id: item.groupId }).then((val) => {
- item.list = val.data.ips.map((item) => {
- return ipSplicing(item.startIp, item.endIp);
- });
- });
- };
- // // 表单校验规则
- const dataRules = reactive({
- url: [
- { required: true, message: '跳转连接不能为空', trigger: 'blur' },
- { validator: rule.domain, trigger: 'blur' },
- ],
- promptMsg: [
- { required: true, message: '提示信息不能为空', trigger: 'blur' },
- ],
- triggerNum: [
- { required: true, message: '触发频率不能为空', trigger: 'blur' },
- {
- pattern: /^(10000|[1-9]\d{0,3}|0)$|^(100%|[1-9]?\d%|0%)$/,
- message: '请输入 0-10000 的正整数或 0%-100% 的百分比',
- trigger: 'blur',
- },
- ],
- });
- const onSubmit = async () => {
- try {
- await ruleFormRef.value.validateField('triggerNum');
- } catch (error) {
- // 验证失败,阻止后续逻辑执行
- return;
- }
- if (formData.value.triggerMode === '1' || formData.value.triggerMode === '3') {
- try {
- await ruleFormRef.value.validateField('url');
- } catch (error) {
- // 验证失败,阻止后续逻辑执行
- return;
- }
- }
- try {
- loading.value = true;
- // 处理 triggerNum 参数
- let triggerNum = formData.value.triggerNum;
- if (typeof triggerNum === 'string' && triggerNum.includes('%')) {
- // 如果包含百分比符号,去除百分比符号并除以100
- const num = parseFloat(triggerNum.replace('%', ''));
- if (!isNaN(num)) {
- triggerNum = num / 100;
- }
- }
- await saveConfigDetail({
- ...formData.value,
- triggerNum: triggerNum.toString(),
- });
- useMessage().success(t('common.editSuccessText'));
- } catch (err) {
- useMessage().error(err.msg);
- } finally {
- loading.value = false;
- getConfig();
- }
- };
- const handleClick = (data: any) => {
- if (data.props.label === 'IP分组') {
- getIpData();
- } else if (data.props.label === '域名分组') {
- getDomainData();
- } else {
- getConfig();
- }
- };
- const handleDelete = async (item: any, type: string) => {
- console.log(item, type);
- delObj.value = { ...item, delListType: type };
- delOpen.value = true;
- };
- const onDel = async (data: any) => {
- if (data?.delListType) {
- try {
- if (data?.delListType === 'ip') {
- await delIpList(data.id);
- useMessage().success(t('common.delSuccessText'));
- } else if (data?.delListType === 'domain') {
- await delDomainList(data.id);
- useMessage().success(t('common.delSuccessText'));
- }
- } catch (err: any) {
- useMessage().error(err.msg);
- } finally {
- loading.value = false;
- delOpen.value = false;
- getConfig();
- }
- return;
- }
- try {
- loading.value = true;
- await delGroup({
- groupType: data.type === 'ip' ? 1 : 2,
- ids: [data.id],
- });
- useMessage().success(t('common.delSuccessText'));
- } catch (err: any) {
- useMessage().error(err.msg);
- } finally {
- loading.value = false;
- delOpen.value = false;
- data.type === 'ip' ? getIpData() : getDomainData();
- }
- };
- const onClickAdd = (type: string) => {
- openType.value = type;
- groupingEditOpen.value = true;
- };
- const onClickEdit = (item: any, type: string) => {
- openType.value = type;
- // ipListEditOpen.value = true;
- onOpenEditMenu(type, item);
- };
- // 打开编辑菜单弹窗
- const onOpenEditMenu = (type: string, row: any) => {
- menuDialogRef.value.openDialog(type, row);
- };
- const getConfig = () => {
- configIp();
- configDomain();
- };
- const configDomain = async () => {
- await getConfigDomainList().then((val) => {
- domainList.value = val.data.map((item) => {
- return {
- ...item,
- list: [],
- };
- });
- });
- };
- const configIp = async () => {
- await getConfigIpList().then((val) => {
- ipWhiteList.value = val.data
- .filter((item) => item.ipType === 1)
- .map((item) => {
- return { ...item, list: [] };
- });
- ipBlackList.value = val.data
- .filter((item) => item.ipType === 2)
- .map((item) => {
- return { ...item, list: [] };
- });
- });
- };
- const getDomainData = async () => {
- await pageListDomain().then((val) => {
- domainActiveId.value = [];
- domainData.value = val.data.map((item: any) => {
- domainActiveId.value.push(item.id);
- return {
- ...item,
- title: item.groupName,
- id: item.id,
- list: item.domains.map((items: any) => {
- return {
- ...items,
- id: items.id,
- value: items.domain,
- };
- }),
- };
- });
- });
- };
- const getIpData = async () => {
- await pageListIp().then((val) => {
- ipActiveId.value = [];
- ipData.value = val.data.map((item: any) => {
- ipActiveId.value.push(item.id);
- return {
- ...item,
- title: item.groupName,
- id: item.id,
- list: item.ips.map((items: any) => {
- return {
- ...items,
- id: items.id,
- value: ipSplicing(items.startIp, items.endIp),
- };
- }),
- };
- });
- });
- await getConfigDetail().then((val) => {
- formData.value = {
- promptMsg: '',
- triggerMode: '',
- triggerRule: '',
- url: '',
- triggerNum: '',
- };
- formData.value = {
- ...val.data,
- triggerMode: val.data?.triggerMode.toString(),
- triggerRule: val.data?.triggerRule.toString(),
- triggerNum: parseFloat(val.data?.triggerNum) < 1 ? parseFloat(val.data?.triggerNum) * 100 + '%' : val.data?.triggerNum,
- };
- });
- };
- onMounted(() => {
- //获取IP列表
- getIpData();
- getConfig();
- });
- </script>
- <style lang="scss">
- .is-top {
- margin-bottom: 0 !important;
- }
- .el-collapse-item__content {
- padding-bottom: 0 !important;
- }
- .el-tabs--card > .el-tabs__header .el-tabs__item {
- background-color: #fff;
- }
- .el-tabs--card > .el-tabs__header .el-tabs__item.is-active {
- background-color: #e8f2fe;
- border-bottom-color: #e8f2fe;
- }
- </style>
|