Prechádzať zdrojové kódy

添加出包管理菜单

wangzhiqiang 4 mesiacov pred
rodič
commit
3ee377b9a7

+ 2 - 2
.env.development

@@ -3,9 +3,9 @@ ENV = 'development'
 # 线上测试
 # VITE_BASE_URL = 'http://advise.ytmdm.com/yt-gateway'
 # VITE_BASE_URL = 'https://test.book.ytpm.net/yt-gateway'
-VITE_BASE_URL = 'http://119.45.71.139:25001'
+# VITE_BASE_URL = 'http://119.45.71.139:25001'
 
 # 本地 
 # VITE_BASE_URL = 'http://192.168.1.9:25001'
-# VITE_BASE_URL = 'http://192.168.1.159:25001'
+VITE_BASE_URL = 'http://192.168.1.159:25001'
 

+ 1 - 2
index.html

@@ -3,9 +3,8 @@
   <head>
     <meta charset="UTF-8" />
     <link rel="icon" href="/favicon.ico" />
-    <!-- <link rel="icon" type="image/svg+xml" href="/vite.svg" /> -->
     <link rel="stylesheet" href="//at.alicdn.com/t/font_2570680_gkyjimtz1d.css">
-    <link rel="stylesheet" href="//at.alicdn.com/t/c/font_4934570_j516p363jf.css">
+    <link rel="stylesheet" href="//at.alicdn.com/t/c/font_4934570_3lp73ojw2ti.css">
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     <title></title>
   </head>

+ 4 - 4
src/router/modules/formworkErection.js

@@ -21,10 +21,10 @@ const route = [
                 meta: { title: '广告位管理' },
             },
             {
-                path: 'channelAdmin/index',
-                name: 'channelAdmin',
-                component: createNameComponent(() => import('@/views/main/formworkErection/channelAdmin.vue')),
-                meta: { title: '广告渠道管理' },
+                path: 'adPlatformAdmin/index',
+                name: 'adPlatformAdmin',
+                component: createNameComponent(() => import('@/views/main/formworkErection/adPlatformAdmin.vue')),
+                meta: { title: '广告平台管理' },
             },
             /* {
                 path: 'adSourceAdmin/index',

+ 21 - 0
src/router/modules/outBagModule.js

@@ -0,0 +1,21 @@
+import Layout from '@/layout/index.vue'
+import { createNameComponent } from '../createNode'
+
+const route = [
+    {
+        path: '/outBagModule',
+        redirect: '/outBagModule/channelTypeAdmin/index',
+        component: Layout,
+        meta: { title: '出包管理', icon: 'iconfont icon-yingyong' },
+        children: [
+            {
+                path: 'channelTypeAdmin/index',
+                name: 'ChannelTypeAdmin',
+                component: createNameComponent(() => import('@/views/main/outBagModule/channelTypeAdmin.vue')),
+                meta: { title: '出包渠道' },
+            }
+        ],
+    }
+]
+
+export default route

+ 6 - 1
src/router/permission/backConfig.js

@@ -9,7 +9,7 @@ const dashboard = {
 const formworkErection = {
   appAdmin: createNameComponent(() => import('@/views/main/formworkErection/appAdmin.vue')),
   placementAdmin: createNameComponent(() => import('@/views/main/formworkErection/placementAdmin.vue')),
-  channelAdmin: createNameComponent(() => import('@/views/main/formworkErection/channelAdmin.vue')),
+  adPlatformAdmin: createNameComponent(() => import('@/views/main/formworkErection/adPlatformAdmin.vue')),
   adSourceAdmin: createNameComponent(() => import('@/views/main/formworkErection/adSourceAdmin.vue')),
 }
 
@@ -36,6 +36,10 @@ const riskModule = {
   riskControlConfig: createNameComponent(() => import('@/views/main/riskModule/riskControlConfig.vue')),
 }
 
+const outBagModule = {
+  channelTypeAdmin: createNameComponent(() => import('@/views/main/outBagModule/channelTypeAdmin.vue')),
+}
+
 /** 导出所有路由,供后端配置使用 */
 const allRoutes = {
   dashboard,
@@ -45,6 +49,7 @@ const allRoutes = {
   agentModule,
   operatorModule,
   riskModule,
+  outBagModule
 }
 
 export default allRoutes

+ 2 - 0
src/router/permission/front.js

@@ -4,6 +4,7 @@
 
 /** 引入需要权限的Modules */
 import Dashboard from '../modules/dashboard'
+import outBagModule from '../modules/outBagModule'
 import FormworkErection from '../modules/formworkErection'
 import UserModule from '../modules/userModule'
 import WithdrawModule from '../modules/withdrawModule'
@@ -16,6 +17,7 @@ import RiskModule from '../modules/riskModule'
 /** 登录后需要动态加入的本地路由 */
 const FrontRoutes = [
   ...Dashboard,
+  ...outBagModule,
   ...FormworkErection,
   ...UserModule,
   // ...WithdrawModule,

+ 301 - 312
src/views/main/dashboard/components/charts/ecpmChaet.vue

@@ -1,5 +1,5 @@
 <template>
-    <div class="layout-container-main" id="jcsb">
+    <!-- <div class="layout-container-main" id="jcsb">
         <div class="item" id="box1">
             <div class="layout-container-main-top" style="display: flex;justify-content: space-between;">
                 <div class="top-left">
@@ -12,14 +12,14 @@
                 <Chart :option="options" />
             </div>
         </div>
-    </div>
-    <div class="layout-container-main" style="margin-top: -10px;" id="jcsb">
+    </div> -->
+    <div class="layout-container-main" id="jcsb">
 
         <div class="item" id="box1">
             <div class="layout-container-main-top" style="display: flex;justify-content: space-between;">
                 <div class="top-left">
                     <div class="line"></div>
-                    <span>平均ECPM</span>
+                    <span>收益统计</span>
                 </div>
                 <div class="main"></div>
             </div>
@@ -36,360 +36,349 @@
 </template>
 
 <script setup>
-    import { defineComponent, reactive, ref, onMounted } from 'vue'
-    import Chart from '@/components/charts/index.vue'
-    //穿山甲、优量汇、快手、Sigmob、百度
+import { defineComponent, reactive, ref, onMounted } from 'vue'
+import Chart from '@/components/charts/index.vue'
+//穿山甲、优量汇、快手、Sigmob、百度
 
-    const xAxisList = [
-        '00:00', '01:00', '02:00', '03:00', '04:00', '05:00', '06:00', '07:00', '08:00',
-        '09:00', '10:00', '11:00', '12:00', '13:00', '14:00', '15:00', '16:00', '17:00',
-        '18:00', '19:00', '20:00', '21:00', '22:00', '23:00'
-    ];
+const xAxisList = [
+    '00:00', '01:00', '02:00', '03:00', '04:00', '05:00', '06:00', '07:00', '08:00',
+    '09:00', '10:00', '11:00', '12:00', '13:00', '14:00', '15:00', '16:00', '17:00',
+    '18:00', '19:00', '20:00', '21:00', '22:00', '23:00'
+];
 
-    const seriesData = [450, 200, 300, 520, 800, 760, 700, 680, 700, 650, 690, 710, 450, 200, 300, 520, 800, 760, 700, 680, 700, 650, 690, 710];
-    // 穿山甲数据(随机生成,保持原40-80范围)
-    const seriesData1 = [42, 25, 33, 55, 76, 68, 72, 63, 71, 65, 62, 69, 45, 22, 31, 53, 79, 73, 68, 64, 72, 61, 67, 70];
+const seriesData = [450, 200, 300, 520, 800, 760, 700, 680, 700, 650, 690, 710, 450, 200, 300, 520, 800, 760, 700, 680, 700, 650, 690, 710];
+// 穿山甲数据(随机生成,保持原40-80范围)
+const seriesData1 = [42, 25, 33, 55, 76, 68, 72, 63, 71, 65, 62, 69, 45, 22, 31, 53, 79, 73, 68, 64, 72, 61, 67, 70];
 
-    // 优量汇数据(模拟中型广告平台,范围200-500)
-    const seriesData2 = (() => {
-        const base = [380, 210, 290, 470, 490, 420, 450, 430, 460, 410, 440, 480];
-        return [...base, ...base.map(v => v * 0.9)]; // 下午数据略降
-    })();
+// 优量汇数据(模拟中型广告平台,范围200-500)
+const seriesData2 = (() => {
+    const base = [380, 210, 290, 470, 490, 420, 450, 430, 460, 410, 440, 480];
+    return [...base, ...base.map(v => v * 0.9)]; // 下午数据略降
+})();
 
-    // 快手数据(模拟短视频平台,高峰在晚间)
-    const seriesData3 = [
-        320, 180, 220, 300, 350,
-        420, 550, 680, 720, 650, // 早高峰
-        600, 580, 320, 280, 350,
-        480, 620, 750, 820, 880, // 晚高峰
-        760, 680, 550, 450
-    ];
+// 快手数据(模拟短视频平台,高峰在晚间)
+const seriesData3 = [
+    320, 180, 220, 300, 350,
+    420, 550, 680, 720, 650, // 早高峰
+    600, 580, 320, 280, 350,
+    480, 620, 750, 820, 880, // 晚高峰
+    760, 680, 550, 450
+];
 
-    // Sigmob数据(模拟国际广告平台,较平稳)
-    const seriesData4 = Array(24).fill(0).map(
-        (_, i) => 350 + Math.sin(i / 4) * 100 + (i > 12 ? -20 : 0) + Math.random() * 30
-    ).map(v => Math.round(v));
+// Sigmob数据(模拟国际广告平台,较平稳)
+const seriesData4 = Array(24).fill(0).map(
+    (_, i) => 350 + Math.sin(i / 4) * 100 + (i > 12 ? -20 : 0) + Math.random() * 30
+).map(v => Math.round(v));
 
-    // 百度数据(模拟搜索广告,白天稳定)
-    const seriesData5 = xAxisList.map((_, i) =>
-        i < 8 ? 280 + i * 20 :       // 清晨上升
-            i < 18 ? 420 + (i % 3) * 15 :  // 白天稳定
-                400 - (i - 18) * 25            // 夜间下降
-    );
+// 百度数据(模拟搜索广告,白天稳定)
+const seriesData5 = xAxisList.map((_, i) =>
+    i < 8 ? 280 + i * 20 :       // 清晨上升
+        i < 18 ? 420 + (i % 3) * 15 :  // 白天稳定
+            400 - (i - 18) * 25            // 夜间下降
+);
 
 
-    const options = {
-        /* title: {
-            text: "单位:个",
-            textStyle: {
-                color: "#808080",
-                fontSize: 12
-            },
-            left: "20",
-            top: "20"
-        }, */
-        tooltip: {
-            trigger: 'axis', // 触发类型为坐标轴
-            axisPointer: {
-                type: 'cross' // 指示器类型为十字准星
-            },
-            formatter: function (params) {
-                // 自定义提示框内容
-                let result = params[0].axisValue + '<br>'; // 显示时间点
-                params.forEach(function (item) {
-                    // 为每个系列添加数据行
-                    result += '<span style="display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:'
-                        + item.color + '"></span>'
-                        + item.seriesName + ': ' + item.value + '<br>';
-                });
-                return result;
-            }
+const options = {
+    /* title: {
+        text: "单位:个",
+        textStyle: {
+            color: "#808080",
+            fontSize: 12
         },
-        legend: {
-            data: ['平均ecpm', '穿山甲', '优量汇', '快手', 'Sigmob', '百度'],
-            top: 'top',
-            textStyle: {
-                color: '#808080'
-            }
+        left: "20",
+        top: "20"
+    }, */
+    tooltip: {
+        trigger: 'axis', // 触发类型为坐标轴
+        axisPointer: {
+            type: 'cross' // 指示器类型为十字准星
         },
-        xAxis: {
-            type: 'category',
-            data: xAxisList,
-            axisLabel: {
-                color: "#808080",
-                fontSize: 12
-            },
-            axisLine: {
-                show: false,
+        formatter: function (params) {
+            // 自定义提示框内容
+            let result = params[0].axisValue + '<br>'; // 显示时间点
+            params.forEach(function (item) {
+                // 为每个系列添加数据行
+                result += '<span style="display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:'
+                    + item.color + '"></span>'
+                    + item.seriesName + ': ' + item.value + '<br>';
+            });
+            return result;
+        }
+    },
+    legend: {
+        data: ['平均ecpm', '穿山甲', '优量汇', '快手', 'Sigmob', '百度'],
+        top: 'top',
+        textStyle: {
+            color: '#808080'
+        }
+    },
+    xAxis: {
+        type: 'category',
+        data: xAxisList,
+        axisLabel: {
+            color: "#808080",
+            fontSize: 12
+        },
+        axisLine: {
+            show: false,
+        },
+        axisTick: {
+            show: false
+        },
+        splitLine: {
+            show: false
+        },
+    },
+    yAxis: {
+        min: 0,
+        nameTextStyle: {
+            color: '#808080',
+            fontSize: 12,
+        },
+        axisLabel: {
+            color: "#808080",
+            fontSize: 12
+        },
+        axisLine: {
+            show: false
+        },
+        axisTick: {
+            show: false
+        },
+        splitLine: {
+            show: true,
+            lineStyle: {
+                color: 'rgba(0,0,0,0.15)',
+            }
+        }
+    },
+    series: [
+        {
+            type: 'line',
+            name: '平均ecpm',
+            symbolSize: 6,
+            z: 1,
+            label: {
+                show: false // Hide labels
             },
-            axisTick: {
-                show: false
+            itemStyle: {
+                color: '#70e3cd'
             },
-            splitLine: {
-                show: false
+            lineStyle: {
+                color: '#70e3cd',
+                width: 2,
             },
+            data: seriesData,
         },
-        yAxis: {
-            min: 0,
-            nameTextStyle: {
-                color: '#808080',
-                fontSize: 12,
-            },
-            axisLabel: {
-                color: "#808080",
-                fontSize: 12
+        {
+            type: 'line',
+            name: '穿山甲',
+            symbolSize: 6,
+            z: 1,
+            label: {
+                show: false // Hide labels
             },
-            axisLine: {
-                show: false
+            itemStyle: {
+                color: '#add7f6'
             },
-            axisTick: {
-                show: false
+            lineStyle: {
+                color: '#add7f6',
+                width: 2,
             },
-            splitLine: {
-                show: true,
-                lineStyle: {
-                    color: 'rgba(0,0,0,0.15)',
-                }
-            }
+            data: seriesData1,
         },
-        series: [
-            {
-                type: 'line',
-                name: '平均ecpm',
-                symbolSize: 6,
-                z: 1,
-                label: {
-                    show: false // Hide labels
-                },
-                itemStyle: {
-                    color: '#70e3cd'
-                },
-                lineStyle: {
-                    color: '#70e3cd',
-                    width: 2,
-                },
-                data: seriesData,
-            },
-            {
-                type: 'line',
-                name: '穿山甲',
-                symbolSize: 6,
-                z: 1,
-                label: {
-                    show: false // Hide labels
-                },
-                itemStyle: {
-                    color: '#add7f6'
-                },
-                lineStyle: {
-                    color: '#add7f6',
-                    width: 2,
-                },
-                data: seriesData1,
-            },
-            {
-                type: 'line',
-                name: '优量汇',
-                symbolSize: 6,
-                z: 1,
-                label: {
-                    show: false // Hide labels
-                },
-                itemStyle: {
-                    color: '#f7a8b8'
-                },
-                lineStyle: {
-                    color: '#f7a8b8',
-                    width: 2,
-                },
-                data: seriesData2,
+        {
+            type: 'line',
+            name: '优量汇',
+            symbolSize: 6,
+            z: 1,
+            label: {
+                show: false // Hide labels
             },
-            {
-                type: 'line',
-                name: '快手',
-                symbolSize: 6,
-                z: 1,
-                label: {
-                    show: false // Hide labels
-                },
-                itemStyle: {
-                    color: '#ffcc5c'
-                },
-                lineStyle: {
-                    color: '#ffcc5c',
-                    width: 2,
-                },
-                data: seriesData3,
+            itemStyle: {
+                color: '#f7a8b8'
             },
-            {
-                type: 'line',
-                name: 'Sigmob',
-                symbolSize: 6,
-                z: 1,
-                label: {
-                    show: false // Hide labels
-                },
-                itemStyle: {
-                    color: '#88d8b0'
-                },
-                lineStyle: {
-                    color: '#88d8b0',
-                    width: 2,
-                },
-                data: seriesData4,
+            lineStyle: {
+                color: '#f7a8b8',
+                width: 2,
             },
-            {
-                type: 'line',
-                name: '百度',
-                symbolSize: 6,
-                z: 1,
-                label: {
-                    show: false // Hide labels
-                },
-                itemStyle: {
-                    color: '#9966cc'
-                },
-                lineStyle: {
-                    color: '#9966cc',
-                    width: 2,
-                },
-                data: seriesData5,
-            }
-        ]
-    };
-
-
-    // 生成随机数据函数
-    const generateRandomValue = (min, max, fixed = 2) => {
-        return (Math.random() * (max - min) + min).toFixed(fixed);
-    };
-
-    // 表格数据
-    const tableData = ref([
-        {
-            platform: '穿山甲',
-            today: '0.00',
-            yesterday: '0.00',
-            month: '0.00'
-        },
-        {
-            platform: '优量汇',
-            today: generateRandomValue(5, 15),
-            yesterday: '9.70',
-            month: '53.43'
+            data: seriesData2,
         },
         {
-            platform: '快手',
-            today: generateRandomValue(10, 20),
-            yesterday: '16.43',
-            month: '16.03'
+            type: 'line',
+            name: '快手',
+            symbolSize: 6,
+            z: 1,
+            label: {
+                show: false // Hide labels
+            },
+            itemStyle: {
+                color: '#ffcc5c'
+            },
+            lineStyle: {
+                color: '#ffcc5c',
+                width: 2,
+            },
+            data: seriesData3,
         },
         {
-            platform: 'Sigmob',
-            today: '0.00',
-            yesterday: '0.00',
-            month: '0.00'
+            type: 'line',
+            name: 'Sigmob',
+            symbolSize: 6,
+            z: 1,
+            label: {
+                show: false // Hide labels
+            },
+            itemStyle: {
+                color: '#88d8b0'
+            },
+            lineStyle: {
+                color: '#88d8b0',
+                width: 2,
+            },
+            data: seriesData4,
         },
         {
-            platform: '百度',
-            today: '0.00',
-            yesterday: '0.00',
-            month: '0.00'
+            type: 'line',
+            name: '百度',
+            symbolSize: 6,
+            z: 1,
+            label: {
+                show: false // Hide labels
+            },
+            itemStyle: {
+                color: '#9966cc'
+            },
+            lineStyle: {
+                color: '#9966cc',
+                width: 2,
+            },
+            data: seriesData5,
         }
-    ]);
+    ]
+};
+
+
+
+
+// 收益统计表格数据格式
+const tableData = ref([
+    {
+        platform: '穿山甲',
+        today: '0.00',
+        yesterday: '0.00',
+        month: '0.00'
+    },
+    {
+        platform: '优量汇',
+        today: '10',
+        yesterday: '9.70',
+        month: '53.43'
+    },
+    {
+        platform: '快手',
+        today: '18',
+        yesterday: '16.43',
+        month: '16.03'
+    },
+    {
+        platform: 'Sigmob',
+        today: '0.00',
+        yesterday: '0.00',
+        month: '0.00'
+    },
+    {
+        platform: '百度',
+        today: '0.00',
+        yesterday: '0.00',
+        month: '0.00'
+    }
+]);
 </script>
 
 <style lang="scss" scoped>
-    .box {
-        margin: 20px auto 0;
-        width: calc(100% - 40px);
-        /* height: 400px; */
-        background: var(--system-page-background);
-        padding: 20px 20px 10px;
-        overflow: hidden;
-    }
+.layout-container-main {
+    margin-top: 10px;
+    display: flex;
+    /* margin: 0 1.25rem; */
+    flex-wrap: wrap;
+    justify-content: space-between;
 
-    .layout-container-main {
+    .item {
+        width: 100%;
+        /* width: 40vw; */
         display: flex;
-        /* margin: 0 1.25rem; */
-        flex-wrap: wrap;
-        justify-content: space-between;
-
-        .item {
-            width: 100%;
-            /* width: 40vw; */
-            display: flex;
-            flex-direction: column;
-            padding: 1.13rem 1.31rem;
-            margin-bottom: 1.25rem;
-            /* height: 26.94rem; */
-            border-radius: 0.5rem;
-            background: #fff;
-            box-shadow: 0 0 0.13rem #00000029;
+        flex-direction: column;
+        padding: 1.13rem 1.31rem;
+        margin-bottom: 1.25rem;
+        /* height: 26.94rem; */
+        border-radius: 0.5rem;
+        background: #fff;
+        box-shadow: 0 0 0.13rem #00000029;
 
-            .layout-container-main-top {
-                height: 6vh;
+        .layout-container-main-top {
+            height: 6vh;
 
-                .top-left {
-                    display: flex;
+            .top-left {
+                display: flex;
 
-                    .line {
-                        width: 0.25rem;
-                        height: 1.56rem;
-                        border-radius: 0.13rem;
-                        background: #fdb818;
-                    }
+                .line {
+                    width: 0.25rem;
+                    height: 1.56rem;
+                    border-radius: 0.13rem;
+                    background: #fdb818;
+                }
 
-                    span {
-                        margin: 0 1rem;
-                        font-family: "PingFang SC SNaNremibold";
-                        font-weight: 600;
-                        font-size: 1.13rem;
+                span {
+                    margin: 0 1rem;
+                    font-family: "PingFang SC SNaNremibold";
+                    font-weight: 600;
+                    font-size: 1.13rem;
 
-                        color: #333;
-                    }
+                    color: #333;
+                }
 
-                    .text {
-                        display: inline-block;
-                        /* // margin: 0 0.63rem; */
-                        width: 3.63rem;
-                        font-weight: 400;
-                        height: 1.88rem;
-                        line-height: 1.88rem;
-                        border-radius: 0.25rem;
-                        background: transparent;
-                        border: 0.03rem solid #999;
-                        font-size: 0.88rem;
-                        text-align: center;
-                        color: #999;
-                        cursor: pointer;
-                    }
+                .text {
+                    display: inline-block;
+                    /* // margin: 0 0.63rem; */
+                    width: 3.63rem;
+                    font-weight: 400;
+                    height: 1.88rem;
+                    line-height: 1.88rem;
+                    border-radius: 0.25rem;
+                    background: transparent;
+                    border: 0.03rem solid #999;
+                    font-size: 0.88rem;
+                    text-align: center;
+                    color: #999;
+                    cursor: pointer;
+                }
 
-                    .active {
-                        border-radius: 0.25rem;
-                        background: #fdb818;
-                        color: #fff;
-                        border: 0.03rem solid #fff;
-                    }
+                .active {
+                    border-radius: 0.25rem;
+                    background: #fdb818;
+                    color: #fff;
+                    border: 0.03rem solid #fff;
                 }
             }
+        }
 
-            .main-echart {
-                width: 100%;
-                /* height: 55vh; */
-                /* background-color: red; */
-            }
+        .main-echart {
+            width: 100%;
+            /* height: 55vh; */
+            /* background-color: red; */
         }
     }
+}
 
 
-    .stats-table {
-        margin: 20px;
-        width: 100vw;
-    }
+.stats-table {
+    margin: 20px;
+    width: 100vw;
+}
 
-    :deep(.el-table__header th) {
-        background-color: #f5f7fa;
-        font-weight: bold;
-    }
+:deep(.el-table__header th) {
+    background-color: #f5f7fa;
+    font-weight: bold;
+}
 </style>

+ 4 - 1
src/views/main/dashboard/components/charts/index.vue

@@ -1,6 +1,7 @@
 <template>
   <div>
     <ecpmChaet />
+    <userDataChart />
     <!-- <onlineChart /> -->
     <!-- <earningsChart /> -->
 
@@ -24,6 +25,7 @@
   import onlineChart from './onlineChart.vue'
   import earningsChart from './earningsChart.vue'
   import ecpmChaet from './ecpmChaet.vue'
+  import userDataChart from './userDataChart.vue'
   export default defineComponent({
     components: {
       onlineChart,
@@ -31,7 +33,8 @@
       pieChart,
       circleChart,
       earningsChart,
-      ecpmChaet
+      ecpmChaet,
+      userDataChart
     },
     setup() {
 

+ 206 - 0
src/views/main/dashboard/components/charts/userDataChart.vue

@@ -0,0 +1,206 @@
+<template>
+    <div class="layout-container-main" id="jcsb">
+        <div class="item" id="box1">
+            <div class="layout-container-main-top" style="display: flex;justify-content: space-between;">
+                <div class="top-left">
+                    <div class="line"></div>
+                    <span>平台用户行为数据统计与趋势图</span>
+                </div>
+                <div class="main"></div>
+            </div>
+            <div class="main-echart" style="height: 55vh;" ref="chartRef">
+                <el-table class="table" :data="tableData" border height="100%">
+                    <el-table-column prop="name" label="统计" align="center" />
+                    <el-table-column prop="today" label="今日" align="center" />
+                    <el-table-column prop="yesterday" label="昨日" align="center" />
+                    <el-table-column prop="month" label="本月" align="center" />
+                </el-table>
+                <Chart class="line" :option="options" />
+            </div>
+        </div>
+    </div>
+</template>
+
+<script setup>
+import { ref } from 'vue'
+import Chart from '@/components/charts/index.vue'
+//穿山甲、优量汇、快手、Sigmob、百度
+
+const options = {
+    tooltip: {
+        trigger: 'axis'
+    },
+    legend: {
+        data: ['新增数(ZID)', '注册数', '登录数', '新增封禁', '总封禁', '新增封号', '总封号']
+    },
+    xAxis: {
+        type: 'category',
+        boundaryGap: false,
+        data: [
+            '2025-06-23 00:00', '2025-06-23 04:00', '2025-06-23 08:00',
+            '2025-06-23 12:00', '2025-06-23 16:00', '2025-06-23 20:00'
+        ]
+    },
+    yAxis: {
+        type: 'value'
+    },
+    series: [
+        {
+            name: '新增数(ZID)',
+            type: 'line',
+            data: [0.03, 0.1, 0.1, 0.3, 0.2, 0.5]
+        },
+        {
+            name: '注册数',
+            type: 'line',
+            data: [0.3, 0, 0.2, 0.1, 0.3, 0.2]
+        },
+        {
+            name: '登录数',
+            type: 'line',
+            data: [0.2, 0.4, 0.2, 1, 0, 0.3]
+        },
+        {
+            name: '新增封禁',
+            type: 'line',
+            data: [0, 0.2, 0.4, 0, 0.5, 0.7]
+        },
+        {
+            name: '总封禁',
+            type: 'line',
+            data: [0.1, 0.1, 0, 0.1, 0, 0]
+        },
+        {
+            name: '新增封号',
+            type: 'line',
+            data: [0, 0.1, 0, 0.3, 0, 0]
+        },
+        {
+            name: '总封号',
+            type: 'line',
+            data: [0, 0, 0, 0, 0, 0]
+        }
+    ]
+}
+
+
+// 表格数据
+const tableData = ref([
+    { name: '注册数', today: 0, yesterday: 0, month: 877 },
+    { name: '新增数(ZID)', today: 0, yesterday: 0, month: 62 },
+    { name: '登录数', today: 1, yesterday: 0, month: 1376 },
+    { name: '新增封禁', today: 1, yesterday: 3, month: 653 },
+    { name: '总封禁数', today: 0, yesterday: 0, month: 636 },
+    { name: '新增封号', today: 1, yesterday: 3, month: 32 },
+    { name: '总封号', today: 0, yesterday: 0, month: 19 }
+]);
+</script>
+
+<style lang="scss" scoped>
+.box {
+    margin: 20px auto 0;
+    width: calc(100% - 40px);
+    /* height: 400px; */
+    background: var(--system-page-background);
+    padding: 20px 20px 10px;
+    overflow: hidden;
+}
+
+.layout-container-main {
+    display: flex;
+    /* margin: 0 1.25rem; */
+    flex-wrap: wrap;
+    justify-content: space-between;
+
+    .item {
+        width: 100%;
+        /* width: 40vw; */
+        display: flex;
+        flex-direction: column;
+        padding: 1.13rem 1.31rem;
+        margin-bottom: 1.25rem;
+        /* height: 26.94rem; */
+        border-radius: 0.5rem;
+        background: #fff;
+        box-shadow: 0 0 0.13rem #00000029;
+
+        .layout-container-main-top {
+            height: 6vh;
+
+            .top-left {
+                display: flex;
+
+                .line {
+                    width: 0.25rem;
+                    height: 1.56rem;
+                    border-radius: 0.13rem;
+                    background: #fdb818;
+                }
+
+                span {
+                    margin: 0 1rem;
+                    font-family: "PingFang SC SNaNremibold";
+                    font-weight: 600;
+                    font-size: 1.13rem;
+
+                    color: #333;
+                }
+
+                .text {
+                    display: inline-block;
+                    /* // margin: 0 0.63rem; */
+                    width: 3.63rem;
+                    font-weight: 400;
+                    height: 1.88rem;
+                    line-height: 1.88rem;
+                    border-radius: 0.25rem;
+                    background: transparent;
+                    border: 0.03rem solid #999;
+                    font-size: 0.88rem;
+                    text-align: center;
+                    color: #999;
+                    cursor: pointer;
+                }
+
+                .active {
+                    border-radius: 0.25rem;
+                    background: #fdb818;
+                    color: #fff;
+                    border: 0.03rem solid #fff;
+                }
+            }
+        }
+
+        .main-echart {
+            width: 100%;
+            height: 500%;
+            display: flex;
+
+            .table {
+                flex: 1;
+            }
+
+            .table :deep(.el-table__body-wrapper tbody tr) {
+                // height: calc(100% / 7);
+                height: calc(50.8vh / 7);
+                /* 均匀分7行 */
+            }
+
+            .line {
+                flex: 3;
+            }
+        }
+    }
+}
+
+
+.stats-table {
+    margin: 20px;
+    width: 100vw;
+}
+
+:deep(.el-table__header th) {
+    background-color: #f5f7fa;
+    font-weight: bold;
+}
+</style>

+ 8 - 8
src/views/main/formworkErection/channelAdmin.vue → src/views/main/formworkErection/adPlatformAdmin.vue

@@ -8,16 +8,16 @@
     <div class="layout-container">
       <Table @getTableData="changeTableData" v-model:page="page" ref="table" :data="tableData"
         @selection-change="handleSelectionChange">
-        <el-table-column prop="channelId" label="渠道ID" />
-        <el-table-column prop="channelName" label="渠道名称" />
+        <el-table-column prop="channelId" label="平台ID" />
+        <el-table-column prop="channelName" label="平台名称" />
         <el-table-column prop="loginType" label="登录类型"  >
           <template #default="scope">
             {{getDictionaryName("login_type",scope.row.loginType)}}
           </template>
         </el-table-column>
-        <el-table-column prop="channelAccount" label="渠道账号"  />
-        <el-table-column prop="channelPwd" label="渠道密码" />
-        <el-table-column prop="channelStatus" label="渠道状态" >
+        <el-table-column prop="channelAccount" label="平台账号"  />
+        <el-table-column prop="channelPwd" label="平台密码" />
+        <el-table-column prop="channelStatus" label="平台状态" >
           <template #default="scope">
             {{getDictionaryName("channel_status", scope.row.channelStatus)}}
           </template>
@@ -70,13 +70,13 @@
 
         <el-row :gutter="5">
           <el-col :span="10">
-            <el-form-item label="渠道账号:" required prop="channelAccount">
+            <el-form-item label="平台账号:" required prop="channelAccount">
               <el-input v-model="formEdit.channelAccount" placeholder="请输入" clearable />
             </el-form-item>
           </el-col>
           <el-col :span="2"></el-col>
           <el-col :span="10">
-            <el-form-item label="渠道密码:" required prop="channelPwd">
+            <el-form-item label="平台密码:" required prop="channelPwd">
               <el-input v-model="formEdit.channelPwd" placeholder="请输入" clearable />
             </el-form-item>
           </el-col>
@@ -84,7 +84,7 @@
 
         <el-row :gutter="5">
           <el-col :span="10">
-            <el-form-item label="渠道状态:" required prop="channelStatus">
+            <el-form-item label="平台状态:" required prop="channelStatus">
               <el-select v-model="formEdit.channelStatus">
                 <el-option :value="1" label="正常"/>
                 <el-option :value="2" label="锁定"/>

+ 26 - 25
src/views/main/formworkErection/appAdmin.vue

@@ -9,8 +9,8 @@
       <Table @getTableData="changeTableData" v-model:page="page" ref="table" :data="tableData"
         @selection-change="handleSelectionChange">
         <el-table-column prop="appId" label="应用ID" width="140" />
-        <el-table-column prop="appName" label="应用名称" />
-        <el-table-column prop="appKey" label="应用秘钥">
+        <el-table-column prop="appName" label="应用名称" width="100" />
+        <el-table-column prop="appKey" label="应用秘钥"  width="120">
           <template #default="scope">
             <label>{{ scope.row.showKey ? scope.row.appKey : "******" }}</label>
             <span style="margin-left: 15px">
@@ -28,36 +28,36 @@
             </span>
           </template>
         </el-table-column>
-        <el-table-column prop="channelName" label="广告渠道名称" width="150" />
-        <el-table-column prop="networkAppName" label="关联渠道应用" width="150" />
-        <el-table-column prop="nickName" label="所属用户" />
+        <el-table-column prop="channelName" label="广告平台名称" width="120" />
+        <el-table-column prop="networkAppName" label="关联渠道应用" width="120" />
+        <el-table-column prop="nickName" label="所属用户" width="100" />
         <el-table-column prop="appType" label="应用类型" width="100">
           <template #default="scope">
             {{ getDictionaryName("app_type", scope.row.appType) }}
           </template>
         </el-table-column>
-        <el-table-column prop="apkUrl" label="下载链接">
+        <el-table-column prop="apkUrl" label="下载链接"  width="120">
           <template #default="scope">
             <a :href="scope.row.apkUrl" target="_blank">{{ scope.row.appName }}.apk</a>
           </template>
         </el-table-column>
-        <el-table-column prop="qrCode" min-width="150" label="二维码">
+        <el-table-column prop="qrCode" min-width="130" label="二维码">
           <template #default="scope">
             <el-image style="width: 100px; height: 100px;" :z-index="99999" :src="scope.row.qrCode" fit="fill"
               :preview-src-list="[scope.row.qrCode]" preview-teleported="true" />
           </template>
         </el-table-column>
-        <el-table-column prop="request" label="广告源请求数" />
-        <el-table-column prop="fillrate" label="广告源填充率" />
+        <el-table-column prop="request" label="广告源请求数"  width="120" />
+        <el-table-column prop="fillrate" label="广告源填充率"  width="120" />
         <el-table-column prop="impression" label="展示数" />
         <el-table-column prop="click" label="点击数" />
         <el-table-column prop="ecpm" label="ECPM" />
         <el-table-column prop="revenue" label="收益" />
-        <el-table-column prop="impression_api" label="广告平台展示数" />
-        <el-table-column prop="click_api" label="广告平台点击数" />
-        <el-table-column prop="ecpm_api" label="广告平台ECPM" />
-        <el-table-column prop="updateTips" label="更新提示" />
-        <el-table-column prop="enabled" label="是否启用">
+        <el-table-column prop="impression_api" label="广告平台展示数"  width="130" />
+        <el-table-column prop="click_api" label="广告平台点击数"  width="130" />
+        <el-table-column prop="ecpm_api" label="广告平台ECPM"  width="130" />
+        <el-table-column prop="updateTips" label="更新提示"  width="100" />
+        <el-table-column prop="enabled" label="是否启用"  width="100">
           <template #default="scope">
             {{ getDictionaryName("enabled", scope.row.enabled) }}
           </template>
@@ -67,7 +67,7 @@
             <div class="button">
               <el-button class="button-item" type="primary" style="margin-bottom: 5px"
                 @click="openChannelLayer(scope.row)">
-                关联渠道
+                关联平台
               </el-button>
               <el-button class="button-item" type="primary" style="margin-bottom: 5px" @click="edit(scope.row)">
                 编辑
@@ -92,16 +92,16 @@
               <el-radio :label="row.channelId">&nbsp;</el-radio>
             </template>
           </el-table-column>
-          <el-table-column prop="channelId" label="渠道ID" />
-          <el-table-column prop="channelName" label="渠道名称" />
+          <el-table-column prop="channelId" label="平台ID" />
+          <el-table-column prop="channelName" label="平台名称" />
           <el-table-column prop="loginType" label="登录类型">
             <template #default="{ row }">
               {{ getDictionaryName("login_type", row.loginType) }}
             </template>
           </el-table-column>
-          <el-table-column prop="channelAccount" label="渠道账号" />
-          <el-table-column prop="channelPwd" label="渠道密码" />
-          <el-table-column prop="channelStatus" label="渠道状态">
+          <el-table-column prop="channelAccount" label="平台账号" />
+          <el-table-column prop="channelPwd" label="平台密码" />
+          <el-table-column prop="channelStatus" label="平台状态">
             <template #default="{ row }">
               {{ getDictionaryName("channel_status", row.channelStatus) }}
             </template>
@@ -357,7 +357,7 @@ const settingData = () => {
         type: "input",
       },
       {
-        label: "广告渠道",
+        label: "广告平台",
         prop: "channelName",
         type: "input",
       },
@@ -434,12 +434,12 @@ const handleSelectionChange = (val) => {
   context.emit("selection-change", val);
 };
 
-// 关联渠道
+// 关联平台
 const selectedChannelId = ref(null)
 const selectRow = ref({})
 
 function handleRadioChange(val) {
-  console.log('选中渠道ID', val)
+  console.log('选中平台ID', val)
   selectRow.value = channelData.value.find(item => item.channelId === val) || null
   console.log(selectRow.value)
 }
@@ -468,14 +468,14 @@ const layer = ref({
 
 const channelLayer = ref({
   show: false,
-  title: "关联渠道",
+  title: "关联平台",
   showButton: true,
   width: "50vw",
   edit: false,
 });
 
 const channelSubmit = async () => {
-  console.log('关联渠道', selectRow.value)
+  console.log('关联平台', selectRow.value)
   // selectRow.value.appId = channelAppId.value
 
   // 目前只支持单个关联
@@ -684,6 +684,7 @@ const customUpload = async (param) => {
     // 生成二维码
     QRCode.toDataURL(res.data.url).then(res => {
       formEdit.value.qrCode = res
+      ElMessage.success('二维码生成成功!')
     })
   } catch (err) {
     ElMessage.error('APP上传失败')

+ 372 - 0
src/views/main/outBagModule/channelTypeAdmin.vue

@@ -0,0 +1,372 @@
+<template>
+  <div class="layout-container">
+    <!-- 菜单栏 -->
+    <From :form-items="dynamicFormItems" @formSubmitted="handleFormSubmitted" @formReset="handleFormReset"
+      is_add_button="新增" @addForm="edit" />
+
+    <!-- 表格 -->
+    <div class="layout-container">
+      <Table @getTableData="changeTableData" v-model:page="page" ref="table" :data="tableData"
+        @selection-change="handleSelectionChange">
+        <el-table-column prop="placementId" label="渠道类型" />
+        <el-table-column prop="placementName" label="用途" />
+        <el-table-column prop="adFormat" label="所属代理">
+          <template #default="scope">
+            {{ getDictionaryName("ad_format", scope.row.adFormat) }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="appName" label="创建时间" />
+        <el-table-column prop="networkAppName" label="更新时间" />
+        <el-table-column prop="appType" label="操作人" width="100">
+          <template #default="scope">
+            {{ getDictionaryName("app_type", scope.row.appType) }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="appName" label="备注" />
+        <el-table-column label="操作" fixed="right">
+          <template #default="scope">
+            <div class="button">
+              <el-button class="button-item" type="primary" style="margin-bottom: 5px;" @click="edit(scope.row)">
+                编辑
+              </el-button>
+              <el-popconfirm placement="left" title="确认删除该广告位?" @confirm="removeType(scope.row)">
+                <template #reference>
+                  <el-button class="button-item" style="margin-bottom: 5px;" type="danger">删除
+                  </el-button>
+                </template>
+              </el-popconfirm>
+            </div>
+          </template>
+        </el-table-column>
+      </Table>
+    </div>
+
+    <!-- 操作弹窗 -->
+    <Layer :layer="layer" @confirm="submit(ruleForm)" @close="layer.show = false">
+      <el-form :model="formEdit" :rules="rules" ref="ruleForm" label-width="240px" style="margin-right:30px;">
+        <el-row :gutter="5">
+          <el-col :span="10">
+            <el-form-item label="渠道类型名称:" required prop="placementName">
+              <el-input v-model="formEdit.placementName" placeholder="请输入渠道类型名称" clearable />
+            </el-form-item>
+          </el-col>
+          <el-col :span="2"></el-col>
+          <el-col :span="10">
+            <el-form-item label="用途:" required prop="appData">
+              <el-select v-model="formEdit.appData" filterable placeholder="请选择用途">
+                <el-option v-for="item in useWayOptions" :key="item.label" :label="item.label" :value="item.value">
+                </el-option>
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="5">
+          <el-col :span="10">
+            <el-form-item label="所属代理:" required prop="adFormat">
+              <el-select v-model="formEdit.adFormat" placeholder="请选择所属代理">
+                <el-option v-for="item in agencyOptions" :key="item.label" :label="item.label" :value="item.value">
+                </el-option>
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="2"></el-col>
+          <el-col :span="10">
+            <el-form-item label="备注:" required prop="remark">
+              <el-input v-model="formEdit.remark" placeholder="请输入备注" clearable />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+    </Layer>
+  </div>
+</template>
+
+<script setup>
+import { onBeforeMount, ref, reactive } from "vue";
+import From from "@/components/from/index.vue";
+import Table from "@/components/table/index.vue";
+import Layer from '@/components/layer/index.vue'
+import { ElMessage } from 'element-plus'
+import { getUserList, getStaticList, riskBannedUser, riskLockUser } from '@/api/userModule.js'
+import { riskChangeUserStatus } from '@/api/riskModule.js'
+import { convertUTCToBeijing } from '@/utils/index.js'
+import { useGetDictList } from '@/hooks/useGetDictList.js'
+import { useStore } from 'vuex'
+import { appList, placementList, placementDel, placementSave } from "@/api/formworkErection";
+
+const store = useStore()
+const { dictData, loadDictData, getOptions, getDictionaryName } = useGetDictList();
+const form = ref(null);
+const tableData = ref([]);
+const appData = ref([]);
+
+// 分页参数, 供table使用
+const page = reactive({
+  pageNum: 1,
+  pageSize: 20,
+  limit: 20,
+  total: 0,
+});
+
+// 分页参数, 供table使用
+const appPage = reactive({
+  pageNum: 1,
+  pageSizes: 999,
+  limit: 999,
+  total: 0,
+});
+
+// 用途options
+const useWayOptions = reactive([
+  { value: 1, label: '应用商城' },
+  { value: 2, label: '投放' },
+  { value: 3, label: '代理' },
+  { value: 4, label: '游戏赚' },
+])
+
+// 所属代理options
+const agencyOptions = reactive([
+  { value: 1, label: '乐助客' },
+  { value: 2, label: '众人帮' },
+  { value: 3, label: '秒单' },
+  { value: 4, label: '赏帮赚' },
+  { value: 5, label: '兴旺富贵' },
+  { value: 6, label: '合众' },
+])
+
+// 分页数据
+const getAppList = async () => {
+  let res = await appList({ ...appFormSearch.value });
+  appData.value = res.data;
+  console.log('appData.value', appData.value)
+};
+
+const changeAppTableData = () => {
+  appFormSearch.value.pageNum = appPage.pageNum;
+  appFormSearch.value.pageSizes = appPage.pageSizes;
+  appFormSearch.value.limit = appPage.limit;
+  // 分页切换
+  getAppList();
+};
+
+const formSearch = ref({
+  channelOrigin: null,// 渠道来源
+  channelType: null,// 渠道类型
+  lastLoginTime: '',// 最新登录时间
+  limit: 20,// 当前页数量(查询量)
+  nickName: null,// 用户昵称
+  page: 1,// 当前页码
+  pageSizes: 20,// 总页数
+  registryTime: null,// 注册时间
+  total: null,// 总条数
+  userType: null,// 用户类型
+});
+
+
+const appFormSearch = ref({
+  page: 1,// 当前页码
+  pageSizes: 999,// 总页数
+  limit: 999,
+  total: null,// 总条数
+});
+
+const dynamicFormItems = ref([])
+
+onBeforeMount(() => {
+  settingData()
+  getList();
+  changeAppTableData()
+});
+
+// 获取缓存数据设置筛选数据
+const settingData = () => {
+  loadDictData().then(() => {
+    dynamicFormItems.value = [
+      {
+        label: '渠道名称',
+        prop: 'placementId',
+        type: 'input',
+      },
+      {
+        label: '所属代理',
+        prop: 'placementId1',
+        type: 'select',
+        options: agencyOptions
+      },
+    ]
+  })
+}
+
+// 分页数据
+const getList = async () => {
+  let res = await placementList({ ...formSearch.value });
+  tableData.value = res.data;
+  page.total = res.pageMeta.total;
+};
+
+const changeTableData = () => {
+  formSearch.value.pageNum = page.pageNum;
+  formSearch.value.pageSize = page.pageSize;
+  formSearch.value.limit = page.limit;
+  // 分页切换
+  getList();
+};
+
+// 搜索
+const handleFormSubmitted = (formData) => {
+  // console.log("接收到子组件传递的数据", formData);
+  formSearch.value.page = 1;
+  formSearch.value.pageSizes = 20;
+  formSearch.value.total = page.total;
+  formSearch.value.limit = 20;
+  formSearch.value.nickName = formData.nickName;
+  formSearch.value.channelOrigin = formData.channelOrigin;
+  formSearch.value.channelType = formData.channelType;
+  formSearch.value.userType = formData.userType;
+  formSearch.value.lastLoginTime = formData.lastLoginTime || null
+  formSearch.value.registryTime = formData.registryTime || null
+
+  getList();
+};
+
+// 表单重置
+const handleFormReset = () => {
+  formSearch.value = {
+    channelOrigin: null,// 渠道来源
+    channelType: null,// 渠道类型
+    lastLoginTime: null,// 最新登录时间
+    limit: 20,// 当前页数量(查询量)
+    nickName: null,// 用户昵称
+    page: 1,// 当前页码
+    pageSizes: 20,// 总页数
+    registryTime: null,// 注册时间
+    total: null,// 总条数
+    userType: null,// 用户类型
+  };
+  getList();
+};
+
+// 选择监听器
+const handleSelectionChange = (val) => {
+  context.emit("selection-change", val)
+}
+
+// 弹窗
+const layer = ref({
+  show: false,
+  title: "新增广告位",
+  showButton: true,
+  width: '60vw'
+});
+
+const formEdit = ref({
+  adFormat: undefined,//广告样式 *
+  appId: undefined,//应用ID *
+  appName: undefined,//应用名称 *
+  enabled: undefined,//启用状态
+  networkAppId: '1210971759',//广告渠道应用ID
+  networkAppName: 'youlianghui',//广告渠道应用名称
+  placementId: undefined,//广告位id
+  placementName: undefined,//广告位名称 *
+  remark: undefined, //备注 *
+  status: undefined, //广告位状态 1-锁定 2-待审核 3-正常 *
+  appData: [],//关联应用
+})
+
+const edit = (row) => {
+  ruleForm.value?.resetFields()
+  if (row) {
+    layer.value.title = '编辑广告位'
+    formEdit.value.placementName = row.placementName
+    formEdit.value.adFormat = row.adFormat
+    formEdit.value.remark = row.remark
+    formEdit.value.status = row.status
+    formEdit.value.placementId = row.placementId
+    formEdit.value.appData = appData.value.find(item => item.appId === row.appId);
+
+  } else {
+    layer.value.title = '新增广告位'
+    // 重置数据
+    let formDatas = {
+      adFormat: undefined,//广告样式
+      appId: undefined,//应用ID
+      appName: undefined,//应用名称
+      enabled: undefined,//启用状态
+      networkAppId: '1210971759',//广告渠道应用ID
+      networkAppName: 'youlianghui',//广告渠道应用名称
+      placementId: undefined,//广告位id
+      placementName: undefined,//广告位名称
+      remark: undefined, //备注
+      status: undefined, //广告位状态 1-锁定 2-待审核 3-正常
+      appData: [],//关联应用
+    }
+    Object.assign(formEdit.value, formDatas)
+  }
+  layer.value.show = true
+
+}
+
+const ruleForm = ref(null);
+
+const rules = reactive({
+  appData: [
+    { required: true, message: "请选择关联应用", trigger: "change" },
+  ],
+  placementName: [
+    { required: true, message: "请输入广告位名称", trigger: "blur" },
+  ],
+  adFormat: [
+    { required: true, message: "请选择广告样式", trigger: "change" },
+  ],
+  remark: [
+    { required: true, message: "请输入备注", trigger: "blur" },
+  ],
+  status: [
+    { required: true, message: "请选择广告位状态", trigger: "change" },
+  ],
+});
+
+const submit = async (formEl) => {
+  formEdit.value.appId = formEdit.value.appData.appId
+  formEdit.value.appName = formEdit.value.appData.appName
+  await formEl.validate(async (valid, fields) => {
+    if (valid) {
+      // 提交内容
+      delete formEdit.value.appData
+      console.log('新增提交的内容', formEdit.value)
+
+      await placementSave({ ...formEdit.value }).then((res) => {
+        ElMessage.success('保存成功')
+        getList();
+      })
+
+      layer.value.show = false
+    } else {
+      console.log("error submit!", fields);
+    }
+  })
+}
+
+// 删除用户
+const removeType = async (row) => {
+  console.log('删除', row)
+  await placementDel({ placementId: row.placementId }).then((res) => {
+    ElMessage.success('删除成功')
+    getList();
+  })
+}
+
+</script>
+
+<style scoped lang="scss">
+.layout-container {
+  .button {
+    display: flex;
+    flex-direction: column;
+
+    .button-item {
+      margin: 4px;
+    }
+  }
+}
+</style>

+ 8 - 7
src/views/main/riskModule/components/configForm.vue

@@ -112,14 +112,15 @@
                                     @input="clearFieldError(index, 'configVal')" />
                             </div>
                         </template>
+                    
+                        <template v-if="!layer.disabled">
+                            <el-button v-if="configFormList.configList.length === index + 1" class="button-item"
+                                type="primary" @click="addItem">
+                                添加
+                            </el-button>
+                            <el-button class="button-item" type="danger" @click="removeItem(index)">删除</el-button>
+                        </template>
                     </div>
-                    <template v-if="!layer.disabled">
-                        <el-button v-if="configFormList.configList.length === index + 1" class="button-item"
-                            type="primary" @click="addItem">
-                            添加
-                        </el-button>
-                        <el-button class="button-item" type="danger" @click="removeItem(index)">删除</el-button>
-                    </template>
                 </div>
             </el-scrollbar>
             <template #footer v-if="layer.showButton">