cmy 2 settimane fa
parent
commit
1f74c989fa

+ 2 - 2
src/api/marketing/statistics.ts

@@ -5,7 +5,7 @@ import request from '/@/utils/request';
  */
 export function pageList(query: object) {
     return request({
-        url: '/admin/marketing/data/page',
+        url: '/marketing/data/page',
         method: 'get',
         params: query,
     });
@@ -17,7 +17,7 @@ export function pageList(query: object) {
  */
 export function detailList(query: object) {
     return request({
-        url: '/admin/marketing/data/detail',
+        url: '/marketing/data/detail',
         method: 'get',
         params: query,
     });

+ 2 - 1
src/theme/app.scss

@@ -353,6 +353,7 @@ body,
 	.el-sub-menu__title {
 		height: 48px;
 		border-bottom: 2px solid transparent !important;
+		border-right: 2px solid var(--menu-bar-active-font-color)!important;
 	}
 	.el-menu-item:hover,
 	.el-sub-menu:hover>.el-sub-menu__title,
@@ -375,7 +376,7 @@ body,
 	.el-sub-menu.is-active svg,
 	.el-sub-menu:hover svg {
 	    path {
-	        fill: rgba(22, 122, 240, 1) !important;
+	        fill: var(--menu-bar-active-font-color) !important;
 		}
 	}
 }

+ 135 - 0
src/views/marketing/apps/form.vue

@@ -0,0 +1,135 @@
+<template>
+  <div class="apps-form">
+    <el-dialog :title="'修改'" width="880" v-model="visible" :close-on-click-modal="false" :destroy-on-close="true" draggable>
+        <el-form ref="menuDialogFormRef"
+          :inline="true"
+          :model="state.ruleForm"
+          class="demo-form-inline"
+          v-loading="loading">
+        <el-form-item :label="'营销开关'" prop="isMarketing">
+          <el-switch v-model="state.ruleForm.isMarketing" style="--el-switch-on-color: rgb(48, 185, 113);" inline-prompt :active-value="1" :inactive-value="0" />
+        </el-form-item>
+        <el-form-item :label="'域名限制'" prop="isHttp">
+          <el-switch v-model="state.ruleForm.isHttp" style="--el-switch-on-color: rgb(48, 185, 113);" inline-prompt :active-value="1" :inactive-value="0" />
+        </el-form-item>
+      </el-form>
+      <div class="title">IP集合</div>
+      <div class="title">域名集合</div>
+      <div class="title">备注</div>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="visible = false">{{ $t('common.cancelButtonText') }}</el-button>
+          <el-button type="primary" @click="onSubmit" :disabled="loading">{{ $t('common.confirmButtonText') }}</el-button>
+        </span>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="ts" name="systemMenuDialog">
+import {useI18n} from 'vue-i18n';
+import {save} from '/@/api/marketing/config';
+import {useMessage} from '/@/hooks/message';
+
+// 定义子组件向父组件传值/事件
+const emit = defineEmits(['refresh']);
+const {t} = useI18n();
+
+// 定义变量内容
+const visible = ref(false);
+const loading = ref(false);
+const sign = ref('domain')
+const add = ref(false);
+const menuDialogFormRef = ref();
+// 定义需要的数据
+const state = reactive({
+  ruleForm: {
+    list: [''], 
+    https: [''], // 域名集合
+    ips: [''], // ip集合
+  },
+  appList: [] as any[], // 应用下拉框列表
+});
+
+// 分页参数
+const pagination = reactive({
+  current: 1,
+  size: 10,
+  total: 0
+})
+
+// 打开弹窗
+const openDialog = async (type: string, row: any, str: string = 'domain') => {
+  visible.value = true;
+  sign.value = str;
+  nextTick(() => {
+    menuDialogFormRef.value?.resetFields();
+  });
+  state.ruleForm = JSON.parse(JSON.stringify(row));
+  str === 'domain' ? state.ruleForm.list = row.https.map(e => ({val: e})) : state.ruleForm.list = row.ips.map(e => ({val: e}));
+};
+
+// 保存数据
+const onSubmit = async () => {
+  sign.value === 'domain' ? state.ruleForm.https = state.ruleForm.list.map(e => e.val) : state.ruleForm.ips = state.ruleForm.list.map(e => e.val);
+  delete state.ruleForm.list;
+  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;
+  }
+};
+
+// 暴露变量 只有暴漏出来的变量 父组件才能使用
+defineExpose({
+  openDialog,
+});
+</script>
+<style lang="scss">
+.config-container {
+  display: flex;
+  align-items: center;
+  margin-bottom: 10px;
+  font-size: 14px;
+  font-weight: 400;
+  width: 100%;
+  justify-content: start;
+}
+.config-actions {
+  width: 50px;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+
+.apps-loadmore.el-select-dropdown .el-scrollbar__wrap {
+  height: 330px !important;
+}
+.apps-form {
+  .el-overlay {
+    .el-overlay-dialog {
+      .el-dialog {
+        .el-dialog__body {
+          padding: 0 !important;
+        }
+      }
+    }
+  }
+  .demo-form-inline .el-switch {
+    margin-right: 50px;
+  }
+  .title {
+    line-height: 20px;
+    font-family: Source Han Sans SC;
+    font-size: 14px;
+    color: rgba(18, 18, 18, 1);
+    margin: 30px 0 12px;
+  }
+}
+</style>

+ 36 - 0
src/views/marketing/apps/i18n/en.ts

@@ -0,0 +1,36 @@
+export default {
+	marketingConfig: {
+		index: '#',
+		name: 'domain name',
+		config: 'config scheme',
+		enName: 'en menu name',
+		sortOrder: 'sortOrder',
+		path: 'path',
+		menuType: 'menuType',
+		keepAlive: 'keepAlive',
+		permission: 'permission',
+		inputNameTip: 'input domain name',
+		inputEnNameTip: 'input en name',
+		parentId: 'parent menu',
+		embedded: 'embedded',
+		visible: 'visible',
+		icon: 'icon',
+		inputMenuIdTip: 'input menuId',
+		inputPermissionTip: 'input permission',
+		inputPathTip: 'input path',
+		inputParentIdTip: 'input parentId',
+		inputIconTip: 'input icon',
+		inputVisibleTip: 'input visible',
+		inputSortOrderTip: 'input sortOrder',
+		inputKeepAliveTip: 'input keepAlive',
+		inputMenuTypeTip: 'input menuType',
+		inputCreateByTip: 'input createBy',
+		inputCreateTimeTip: 'input createTime',
+		inputUpdateByTip: 'input updateBy',
+		inputUpdateTimeTip: 'input updateTime',
+		inputDelFlagTip: 'input delFlag',
+		inputTenantIdTip: 'input tenantId',
+		inputEmbeddedTip: 'input embedded',
+		deleteDisabledTip: 'menu inclusion subordinates cannot be deleted',
+	},
+};

+ 32 - 0
src/views/marketing/apps/i18n/zh-cn.ts

@@ -0,0 +1,32 @@
+export default {
+	marketingApps: {
+		index: '#',
+		name: '应用名称',
+		config: '配置方案',
+		inputNameTip: '请输入应用名称名称',
+		inputIPTip: '请输入IP名称',
+		app: '应用',
+		inputAppSel: '请选择应用',
+
+		// ID
+		id: 'ID',
+		// 应用ID
+		appId: '应用ID',
+		// 应用图片
+		appImg: '应用图片',
+		// 应用类型
+		appType: '应用类型',
+		// 备注
+		remark: '备注',
+		// 触发规则
+		triggerRule: '触发规则',
+		// 黑名单
+		blackList: '黑名单',
+		// 白名单
+		whiteList: '白名单',
+		// 修改
+		edit: '修改',
+		// 统计
+		statistical: '统计',
+	},
+};

+ 287 - 0
src/views/marketing/apps/index.vue

@@ -0,0 +1,287 @@
+<template>
+  <div class="layout-padding">
+    <div class="layout-padding-auto layout-padding-view">
+      <el-row shadow="hover" v-show="showSearch" class="ml10">
+        <el-form :inline="true" :model="state.queryForm" @keyup.enter="getDataList" ref="queryRef">
+          <el-form-item :label="$t('marketingApps.name')" prop="domain">
+            <el-input :placeholder="$t('marketingApps.inputNameTip')" clearable style="max-width: 180px"
+              v-model="state.queryForm.domain" />
+          </el-form-item>
+          <el-form-item>
+            <el-button @click="query" class="ml10" icon="search" type="primary">
+              {{ $t('common.queryBtn') }}
+            </el-button>
+            <el-button @click="resetQuery" icon="Refresh">{{ $t('common.resetBtn') }}</el-button>
+            <el-button @click="handleEdit" icon="EditPen">{{ $t('common.editBtn') }}</el-button>
+          </el-form-item>
+        </el-form>
+      </el-row>
+      <el-row>
+        <div class="mb8" style="width: 100%">
+          <right-toolbar v-model:showSearch="showSearch" class="ml10" style="float: right; margin-right: 20px"
+            @queryTable="getDataList"></right-toolbar>
+        </div>
+      </el-row>
+      <el-table @selection-change="handleSelectionChange" ref="tableRef" :data="state.dataList" row-key="path"
+        style="width: 100%" v-loading="state.loading" border :cell-style="tableStyle.cellStyle"
+        :header-cell-style="tableStyle?.headerCellStyle">
+        <el-table-column type="selection" fixed="left" width="55" />
+        <el-table-column fixed="left" :label="$t('marketingApps.id')" prop="id" width="80" show-overflow-tooltip>
+          <template #default="{ row }">{{ row.id }}</template>
+        </el-table-column>
+        <el-table-column :label="$t('marketingApps.appId')" prop="appId" width="120" show-overflow-tooltip>
+          <template #default="{ row }">{{ row.appId }}</template>
+        </el-table-column>
+        <el-table-column :label="$t('marketingApps.appImg')" prop="appImg" width="150" show-overflow-tooltip>
+          <template #default="{ row }" style="display: flex; align-self: center;">
+            <el-image style="width: 100px; height: 100px" :src="row.appImg" :fit="fit" />
+          </template>
+        </el-table-column>
+        <el-table-column :label="$t('marketingApps.name')" prop="appName" width="150" show-overflow-tooltip>
+          <template #default="{ row }">
+            <el-link :href="row.url">{{ row.appName }}</el-link>
+          </template>
+        </el-table-column>
+        <el-table-column :label="$t('marketingApps.appType')" prop="appType" width="110" show-overflow-tooltip>
+          <template #default="{ row }">
+            <el-select v-model="row.appType" placeholder="" style="width: 80px">
+              <el-option v-for="item in appTypes" :key="item.value" :label="item.description" :value="item.value" />
+            </el-select>
+          </template>
+        </el-table-column>
+        <el-table-column label="营销投放" prop="isMarketing" width="120">
+          <template #default="{ row }">
+            <el-switch v-model="row.isMarketing" inline-prompt active-text="开启" inactive-text="关闭" :active-value="1"
+              :inactive-value="0" @change="handleChange(row)" />
+          </template>
+        </el-table-column>
+        <el-table-column label="域名限制" prop="isHttp" width="120">
+          <template #default="{ row }">
+            <el-switch v-model="row.isHttp" inline-prompt active-text="开启" inactive-text="关闭" :active-value="1"
+              :inactive-value="0" @change="handleChange(row)" />
+          </template>
+        </el-table-column>
+        <el-table-column label="域名集合" prop="https" width="240" show-overflow-tooltip>
+          <template #default="{ row }">
+            <div style="
+                  padding: 0 20px;
+                  text-wrap: auto;
+                  text-align: left;
+                ">
+              <el-tooltip effect="light">
+                <template #content>
+                  <div v-for="(item, index) in row.https" :key="index">{{ item }};</div>
+                </template>
+                <el-text line-clamp="6">
+                  <template v-for="(item, index) in row.https" :key="index">
+                    <div>{{ item }};</div>
+                  </template>
+                </el-text>
+              </el-tooltip>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="ip集合" prop="ips" width="300">
+          <template #default="{ row }">
+            <div style="
+                  padding: 0 20px;
+                  text-wrap: auto;
+                  text-align: left;
+                ">
+              <div style="text-align: left;">黑名单:</div>
+              <el-tooltip effect="light">
+                <template #content>
+                  <div v-for="(item, index) in row.ips" :key="index">{{ item }};</div>
+                </template>
+                <el-text line-clamp="4">
+                  <template v-for="(item, index) in row.ips" :key="index">
+                    <div>{{ item }};</div>
+                  </template>
+                </el-text>
+              </el-tooltip>
+
+              <div style="text-align: left;">白名单:</div>
+              <el-tooltip effect="light">
+                <template #content>
+                  <div v-for="(item, index) in row.ips" :key="index">{{ item }};</div>
+                </template>
+                <el-text line-clamp="4">
+                  <template v-for="(item, index) in row.ips" :key="index">
+                    <div>{{ item }};</div>
+                  </template>
+                </el-text>
+              </el-tooltip>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column :label="$t('marketingApps.triggerRule')" prop="triggerRule" width="180" show-overflow-tooltip>
+          <template #default="{ row }">
+            <el-select v-model="row.triggerRule" placeholder="" style="width: 150px">
+              <el-option v-for="item in triggerRules" :key="item.value" :label="item.label" :value="item.value" />
+            </el-select>
+          </template>
+        </el-table-column>
+        <el-table-column fixed="right" :label="$t('common.action')" width="150">
+          <template #default="scope">
+            <div class="action" style="text-align: left;">
+              <!-- <el-button icon="edit-pen" @click="onOpenEditMenu('edit', scope.row, 'domain')" text type="primary"
+                v-auth="'sys_menu_edit'">域名
+              </el-button>
+              <el-button icon="edit-pen" @click="onOpenEditMenu('edit', scope.row, 'ip')" text type="primary"
+                v-auth="'sys_menu_edit'">IP
+              </el-button>
+              <br /> -->
+              <el-button icon="edit-pen" @click="onOpenEditMenu('edit', scope.row)" text type="primary"
+                v-auth="'sys_menu_edit'">修改
+              </el-button>
+              <el-button icon="edit-pen" @click="onOpenStatistical(scope.row)" text type="primary"
+                v-auth="'sys_menu_edit'">统计
+              </el-button>
+            </div>
+          </template>
+        </el-table-column>
+      </el-table>
+      <pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination" />
+    </div>
+    <MenuDialog @refresh="getDataList()" ref="menuDialogRef" />
+    <StatisticalDialog @refresh="getDataList()" ref="statisticalDialogRef" />
+  </div>
+</template>
+
+<script lang="ts" name="marketingApps" setup>
+import { delObj, pageList, update } from '/@/api/marketing/config';
+import { BasicTableProps, useTable } from '/@/hooks/table';
+import { useMessage, useMessageBox } from '/@/hooks/message';
+import { useI18n } from 'vue-i18n';
+import { fetchItemList } from '/@/api/admin/dict';
+
+// 引入组件
+const MenuDialog = defineAsyncComponent(() => import('./form.vue'));
+const StatisticalDialog = defineAsyncComponent(() => import('./statistical.vue'));
+const { t } = useI18n();
+// 定义变量内容
+const tableRef = ref();
+const menuDialogRef = ref();
+const statisticalDialogRef = ref();
+const queryRef = ref();
+const showSearch = ref(true);
+// 多选rows
+const selectObjs = ref([]) as any;
+// 是否可以多选
+const multiple = ref(true);
+const state: BasicTableProps = reactive<BasicTableProps>({
+  pageList: pageList, // H
+  queryForm: {
+    domain: '',
+  }
+});
+
+const appTypes = ref([]) as any;
+const getAppTypes = async () => {
+  const { data } = await fetchItemList({
+    dictType: 'DomianType'
+  });
+  appTypes.value = data?.records || [];
+}
+getAppTypes();
+
+const triggerRules = [
+  {
+    label: '不弹出',
+    value: 'unset',
+  },
+  {
+    label: '仅一次 - URL',
+    value: 'url1',
+  },
+  {
+    label: '每次弹出 - URL',
+    value: 'url2',
+  },
+  {
+    label: '仅一次 - 视频',
+    value: 'video1',
+  },
+  {
+    label: '每次弹出 - 视频',
+    value: 'video2',
+  },
+]
+
+const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state);
+
+// 搜索事件
+const query = () => {
+  state.dataList = [];
+  getDataList();
+};
+
+// 修改单条配置信息
+const handleChange = (row: any) => {
+  // update(row).then(() => {
+  //   useMessage().success(t('common.updateSuccessText'));
+  //   getDataList();
+  // }).catch((err: any) => {
+  //   useMessage().error(err.msg);
+  // });
+};
+
+// 清空搜索条件
+const resetQuery = () => {
+  queryRef.value.resetFields();
+  state.dataList = [];
+  getDataList();
+};
+
+// 批量修改配置
+const handleEdit = () => {
+  if (selectObjs.value.length === 0) {
+    useMessage().warning('请选择要修改的项');
+    return;
+  }
+  // menuDialogRef.value.openDialog('edit', selectObjs.value);
+};
+
+// 多选
+const handleSelectionChange = (selection: any) => {
+  selectObjs.value = selection;
+};
+
+// 打开编辑菜单弹窗
+const onOpenEditMenu = (type: string, row: any) => {
+  menuDialogRef.value.openDialog(type, row);
+};
+
+// 打开统计弹窗
+const onOpenStatistical = (row: any) => {
+  statisticalDialogRef.value.openDialog(row);
+};
+
+// 删除操作
+const handleDelete = async (ids: string[]) => {
+  try {
+    await useMessageBox().confirm(t('common.delConfirmText'));
+  } catch {
+    return;
+  }
+
+  try {
+    await delObj(ids);
+    getDataList();
+    useMessage().success(t('common.delSuccessText'));
+  } catch (err: any) {
+    useMessage().error(err.msg);
+  }
+};
+</script>
+<style scoped lang="scss">
+:deep(.el-link__inner) {
+  display: inline-block;
+  width: 100%;
+  justify-content: start;
+}
+
+:deep(.el-link.is-underline:hover:after) {
+  display: none;
+}
+</style>

+ 56 - 0
src/views/marketing/apps/statistical.vue

@@ -0,0 +1,56 @@
+<template>
+  <el-dialog
+    :title="'统计'"
+    width="80%"
+    v-model="visible"
+    :destroy-on-close="true"
+    :close-on-click-modal="false" 
+    draggable>
+    <div class="statistics-table-wrapper">
+      <StatisticsIndex />
+    </div>
+  </el-dialog>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue';
+import StatisticsIndex from '../statistics/index.vue';
+
+const visible = ref(false);
+
+// 打开弹窗
+const openDialog = async (row: any) => {
+  visible.value = true;
+};
+
+// 暴露变量 只有暴漏出来的变量 父组件才能使用
+defineExpose({
+  openDialog,
+});
+</script>
+<style>
+.config-container {
+  display: flex;
+  align-items: center;
+  margin-bottom: 10px;
+  font-size: 14px;
+  font-weight: 400;
+  width: 100%;
+  justify-content: start;
+}
+.config-actions {
+  width: 50px;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+
+.apps-loadmore.el-select-dropdown .el-scrollbar__wrap {
+  height: 330px !important;
+}
+
+.statistics-table-wrapper {
+  height: 70vh;
+  position: relative;
+}
+</style>

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

@@ -1,5 +1,5 @@
 <template>
-  <div class="layout-padding">
+  <div class="layout-padding" style="width: 100%;">
     <div class="layout-padding-auto layout-padding-view">
       <el-row class="ml10" v-show="showSearch">
         <el-form :inline="true" :model="state.queryForm" @keyup.enter="withCollapsedChildren(getDataList)" ref="queryRef">
@@ -67,10 +67,11 @@
   </div>
 </template>
 
-<script lang="ts" setup>
+<script lang="ts" setup name="StatisticsIndex">
 import { BasicTableProps, useTable } from '/@/hooks/table';
 import { pageList, detailList } from '/@/api/marketing/statistics';
 import { useI18n } from 'vue-i18n';
+import { ref, reactive, watchEffect } from 'vue';
 
 const { t } = useI18n();
 // 定义变量内容
@@ -78,6 +79,22 @@ const queryRef = ref();
 const showSearch = ref(true);
 const expandedRowKeys = ref<number[]>([]); // 记录展开的行
 
+// 接受props
+defineProps({
+  row: {
+    type: Object,
+    default: () => ({})
+  },
+  type: {
+    type: String,
+    default: 'index'
+  },
+  appName: {
+    type: String,
+    default: ''
+  }
+});
+
 //  table hook
 const state: BasicTableProps = reactive<BasicTableProps>({
   queryForm: {