ソースを参照

修改首页数据、logo、广告变现页面

wangzhiqiang 4 ヶ月 前
コミット
1b02fb1f69

+ 2 - 1
.env.development

@@ -2,8 +2,9 @@ ENV = 'development'
 
 # 线上测试
 # VITE_BASE_URL = 'http://advise.ytmdm.com/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'
 

BIN
public/favicon.ico


+ 24 - 0
src/api/common.js

@@ -7,4 +7,28 @@ export function getCommonDictList(params) {
         method: 'get',
         params
     })
+}
+
+// 上传图片
+export function attachImage(data) {
+    return request({
+        url: '/agent-service/attach/image',
+        method: 'post',
+        data,
+        headers: {
+            'Content-Type': 'multipart/form-data'
+        }
+    })
+}
+
+// 文件上传
+export function attachFile(data) {
+    return request({
+        url: '/agent-service/attach/file',
+        method: 'post',
+        data,
+        headers: {
+            'Content-Type': 'multipart/form-data'
+        }
+    })
 }

+ 41 - 1
src/api/formworkErection.js

@@ -1,5 +1,6 @@
 import request from '@/utils/system/request'
 
+// 查询应用列表
 export function appList(data) {
     return request({
         url: '/agent-service/app/searchApps',
@@ -23,6 +24,30 @@ export function delApp(data) {
         method: 'get'
     })
 }
+//应用关联广告平台 & 广告平台应用
+export function relativeChannel(data) {
+    return request({
+        url: '/agent-service/app/relativeChannel',
+        method: 'post',
+        data
+    })
+}
+//获取指定平台下的一级分类
+export function getPrimaryCategories(data) {
+    return request({
+        url: '/agent-service/app/getPrimaryCategories',
+        method: 'post',
+        data
+    })
+}
+//获取指定平台下的一级分类下的二级分类
+export function getSecondaryCategories(data) {
+    return request({
+        url: '/agent-service/app/getSecondaryCategories',
+        method: 'post',
+        data
+    })
+}
 // 获取广告渠道列表
 export function channelList(data) {
     return request({
@@ -39,7 +64,22 @@ export function placementList(data) {
         data
     })
 }
-
+// 删除广告位
+export function placementDel(params) {
+    return request({
+        url: '/agent-service/placement/del',
+        method: 'get',
+        params
+    })
+}
+// 保存广告位
+export function placementSave(data) {
+    return request({
+        url: '/agent-service/placement/save',
+        method: 'post',
+        data
+    })
+}
 // 新增广告渠道
 export function channelAddOne(data) {
     return request({

BIN
src/assets/logo.png


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

@@ -5,18 +5,18 @@ const route = [
     {
         path: '/formworkErection',
         component: Layout,
-        redirect: '/formworkErection/agencyAdmin/index',
+        redirect: '/formworkErection/appAdmin/index',
         meta: { title: '广告变现', icon: 'sfont system-zidingyi' },
         children: [
             {
-                path: 'agencyAdmin/index',
-                name: 'agencyAdmin',
+                path: 'appAdmin/index',
+                name: 'appAdmin',
                 component: createNameComponent(() => import('@/views/main/formworkErection/appAdmin.vue')),
                 meta: { title: '应用管理' },
             },
             {
-                path: 'channelType/index',
-                name: 'channelType',
+                path: 'placementAdmin/index',
+                name: 'placementAdmin',
                 component: createNameComponent(() => import('@/views/main/formworkErection/placementAdmin.vue')),
                 meta: { title: '广告位管理' },
             },

+ 2 - 2
src/router/permission/backConfig.js

@@ -7,8 +7,8 @@ const dashboard = {
 }
 
 const formworkErection = {
-  agencyAdmin: createNameComponent(() => import('@/views/main/formworkErection/appAdmin.vue')),
-  channelType: createNameComponent(() => import('@/views/main/formworkErection/placementAdmin.vue')),
+  appAdmin: createNameComponent(() => import('@/views/main/formworkErection/appAdmin.vue')),
+  placementAdmin: createNameComponent(() => import('@/views/main/formworkErection/placementAdmin.vue')),
   channelAdmin: createNameComponent(() => import('@/views/main/formworkErection/channelAdmin.vue')),
 }
 

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

@@ -13,10 +13,10 @@ export default defineComponent({
   },
   setup() {
     const list = [
-      { id: 1, name: '总广告数', data: '12800', color: '#4e73df', icon: 'sfont system-yonghu' },
-      { id: 2, name: '封禁人数', data: '30', color: '#1cc88a', icon: 'sfont system-xiaoxi' },
-      { id: 3, name: '提现笔数', data: '42567', color: '#36b9cc', icon: 'sfont system-shuliang_mianxing' },
-      { id: 4, name: '提现金额', data: '1168,000', color: '#f6c23e', icon: 'sfont system-jindutiaoshouyidaozhang' }
+      { id: 1, name: '总广告数', data: '1057', color: '#4e73df', icon: 'sfont system-yonghu' },
+      { id: 2, name: '封禁人数', data: '55', color: '#1cc88a', icon: 'sfont system-xiaoxi' },
+      { id: 3, name: '提现笔数', data: '827', color: '#36b9cc', icon: 'sfont system-shuliang_mianxing' },
+      { id: 4, name: '提现金额', data: '64581', color: '#f6c23e', icon: 'sfont system-jindutiaoshouyidaozhang' }
     ]
     return {
       list

+ 302 - 145
src/views/main/formworkErection/appAdmin.vue

@@ -2,29 +2,35 @@
   <div class="layout-container">
     <!-- 菜单栏 -->
     <From :form-items="dynamicFormItems" @formSubmitted="handleFormSubmitted" @formReset="handleFormReset"
-          is_add_button="新增" @addForm="edit"/>
+      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="appId" label="应用ID" width="140"/>
-        <el-table-column prop="appName" label="应用名称"/>
+        @selection-change="handleSelectionChange">
+        <el-table-column prop="appId" label="应用ID" width="140" />
+        <el-table-column prop="appName" label="应用名称" />
         <el-table-column prop="appKey" label="应用秘钥">
           <template #default="scope">
             <label>{{ scope.row.showKey ? scope.row.appKey : "******" }}</label>
             <span style="margin-left: 15px">
-              <el-icon v-if="scope.row.showKey" @click="()=>{scope.row.showKey = !scope.row.showKey}"><View/></el-icon>
-              <el-icon v-if="!scope.row.showKey" @click="()=>{scope.row.showKey = !scope.row.showKey}"><Hide/></el-icon>
+              <el-icon v-if="scope.row.showKey" @click="() => { scope.row.showKey = !scope.row.showKey }">
+                <View />
+              </el-icon>
+              <el-icon v-if="!scope.row.showKey" @click="() => { scope.row.showKey = !scope.row.showKey }">
+                <Hide />
+              </el-icon>
             </span>
             <span style="margin-left: 5px">
-              <el-icon v-copy="scope.row.appKey"><CopyDocument/></el-icon>
+              <el-icon v-copy="scope.row.appKey">
+                <CopyDocument />
+              </el-icon>
             </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="150" />
+        <el-table-column prop="networkAppName" label="关联渠道应用" width="150" />
+        <el-table-column prop="nickName" label="所属用户" />
         <el-table-column prop="appType" label="应用类型" width="100">
           <template #default="scope">
             {{ getDictionaryName("app_type", scope.row.appType) }}
@@ -37,20 +43,20 @@
         </el-table-column>
         <el-table-column prop="qrCode" min-width="150" label="二维码">
           <template #default="scope">
-            <el-image style="width: 100px; height: 100px" :src="scope.row.qrCode" fit="fill"
-                      :preview-src-list="[scope.row.qrCode]"/>
+            <el-image style="width: 100px; height: 100px;" :z-index="99999" :src="scope.row.qrCode" fit="fill"
+              :preview-src-list="[scope.row.qrCode]" />
           </template>
         </el-table-column>
-        <el-table-column prop="request" label="广告源请求数"/>
-        <el-table-column prop="fillrate" label="广告源填充率"/>
-        <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="request" label="广告源请求数" />
+        <el-table-column prop="fillrate" label="广告源填充率" />
+        <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="是否启用">
           <template #default="scope">
             {{ getDictionaryName("enabled", scope.row.enabled) }}
@@ -59,11 +65,11 @@
         <el-table-column label="操作" width="150" fixed="right">
           <template #default="scope">
             <div class="button">
-              <el-button class="button-item" type="primary" style="margin-bottom: 5px" @click="openChannelLayer(scope.row)">
+              <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)">
+              <el-button class="button-item" type="primary" style="margin-bottom: 5px" @click="edit(scope.row)">
                 编辑
               </el-button>
               <el-popconfirm placement="left" title="确认删除该数据?" @confirm="removeApp(scope.row)">
@@ -77,59 +83,56 @@
       </Table>
     </div>
 
-    <Layer :layer="channelLayer" @confirm="submit" @close="channelLayer.show=false">
-      <Table
-          @getTableData="getChanelData"
-          v-model:page="channelPage"
-          ref="channelTable"
-          :data="channelData"
-          @selection-change="handleChannelSelectionChange"
-      >
-        <el-table-column
-            type="selection"
-            width="55">
-        </el-table-column>
-
-      <!-- 原有列 -->
-      <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="渠道状态">
-        <template #default="scope">
-          {{ getDictionaryName("channel_status", scope.row.channelStatus) }}
-        </template>
-      </el-table-column>
-      </Table>
+    <Layer :layer="channelLayer" @confirm="channelSubmit" @close="channelLayer.show = false">
+      <el-radio-group style="width: 100%;" v-model="selectedChannelId" @change="handleRadioChange">
+        <Table style="width: 100%;" @getTableData="getChanelData" v-model:page="channelPage" ref="channelTable"
+          :data="channelData">
+          <el-table-column label="渠道ID" width="300">
+            <template #default="{ row }">
+              <el-radio :label="row.channelId" />
+            </template>
+          </el-table-column>
+
+          <!-- <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="渠道状态">
+            <template #default="{ row }">
+              {{ getDictionaryName("channel_status", row.channelStatus) }}
+            </template>
+          </el-table-column>
+        </Table>
+      </el-radio-group>
     </Layer>
 
     <!-- 操作弹窗 -->
     <Layer :layer="layer" @confirm="submit(ruleForm)" @close="layer.show = false">
-      <el-form :model="formEdit" :rules="rules" ref="ruleForm" label-width="120px" style="margin-right: 30px">
+      <el-form :model="formEdit" :rules="rules" ref="ruleForm" label-width="130px" style="margin-right: 30px">
         <el-row :gutter="5">
           <el-col :span="10" v-if="formEdit.appId">
             <el-form-item label="应用ID" required prop="appId">
-              <el-input v-model="formEdit.appId" disabled placeholder="请输入" clearable/>
+              <el-input v-model="formEdit.appId" disabled placeholder="请输入" clearable />
             </el-form-item>
           </el-col>
           <el-col :span="2" v-if="formEdit.appId"></el-col>
           <el-col :span="10">
             <el-form-item label="应用名称:" required prop="appName">
-              <el-input v-model="formEdit.appName" placeholder="请输入" clearable/>
+              <el-input v-model="formEdit.appName" placeholder="请输入" clearable />
             </el-form-item>
           </el-col>
         </el-row>
         <el-row :gutter="5">
           <el-col :span="10">
             <el-form-item label="应用类型" required prop="appType">
-              <el-select v-model="formEdit.appType">
-                <el-option :value="1" label="Android"/>
-                <el-option :value="2" label="IOS"/>
+              <el-select v-model="formEdit.appType" @change="getPrimaryCategoriesData">
+                <el-option :value="1" label="Android" />
+                <el-option :value="2" label="IOS" />
               </el-select>
             </el-form-item>
           </el-col>
@@ -137,89 +140,72 @@
           <el-col :span="10">
             <el-form-item label="是否上架" required prop="store_on_sale">
               <el-select v-model="formEdit.store_on_sale">
-                <el-option :value="1" label="否"/>
-                <el-option :value="2" label="是"/>
+                <el-option :value="1" label="否" />
+                <el-option :value="2" label="是" />
               </el-select>
             </el-form-item>
           </el-col>
         </el-row>
-        <el-row :gutter="5">
+        <el-row :gutter="5" v-if="formEdit.store_on_sale === 2">
           <el-col :span="10">
-            <el-form-item label="上架商店" required prop="store_type">
+            <el-form-item label="上架商店" prop="store_type">
               <el-select v-model="formEdit.store_type" placeholder="请选择上架应用商店">
-                <el-option :value="1" label="App Store"/>
-                <el-option :value="2" label="Google Play"/>
-                <el-option :value="3" label="华为商店"/>
-                <el-option :value="4" label="小米应用商店"/>
-                <el-option :value="5" label="OPPO商店"/>
-                <el-option :value="6" label="vivo商店"/>
-                <el-option :value="7" label="应用宝"/>
-                <el-option :value="8" label="TapTap"/>
-                <el-option :value="9" label="Amazon应用商店"/>
-                <el-option :value="10" label="三星应用商店"/>
-                <el-option :value="11" label="小米海外应用商店"/>
-                <el-option :value="12" label="华为海外应用市场"/>
-                <el-option :value="13" label="Roku"/>
-                <el-option :value="14" label="Samsung Smart TV"/>
-                <el-option :value="15" label="Microsoft"/>
-                <el-option :value="16" label="LG Smart TV"/>
-                <el-option :value="17" label="Vizio"/>
-                <el-option :value="99" label="其他"/>
+                <el-option :value="1" label="App Store" />
+                <el-option :value="2" label="Google Play" />
+                <el-option :value="3" label="华为商店" />
+                <el-option :value="4" label="小米应用商店" />
+                <el-option :value="5" label="OPPO商店" />
+                <el-option :value="6" label="vivo商店" />
+                <el-option :value="7" label="应用宝" />
+                <el-option :value="8" label="TapTap" />
+                <el-option :value="9" label="Amazon应用商店" />
+                <el-option :value="10" label="三星应用商店" />
+                <el-option :value="11" label="小米海外应用商店" />
+                <el-option :value="12" label="华为海外应用市场" />
+                <el-option :value="13" label="Roku" />
+                <el-option :value="14" label="Samsung Smart TV" />
+                <el-option :value="15" label="Microsoft" />
+                <el-option :value="16" label="LG Smart TV" />
+                <el-option :value="17" label="Vizio" />
+                <el-option :value="99" label="其他" />
               </el-select>
             </el-form-item>
           </el-col>
           <el-col :span="2"></el-col>
           <el-col :span="10">
-            <el-form-item label="商店链接" required prop="store_url">
-              <el-input v-model="formEdit.store_url" placeholder="请输入上架商店链接" clearable/>
+            <el-form-item label="商店链接" prop="store_url">
+              <el-input v-model="formEdit.store_url" placeholder="请输入上架商店链接" clearable />
             </el-form-item>
           </el-col>
         </el-row>
         <el-row :gutter="5">
           <el-col :span="10">
-            <el-form-item label="应用域名" prop="domain" required>
-              <el-input v-model="formEdit.domain" placeholder="请输入应用域名" clearable/>
+            <el-form-item label="应用域名" prop="domain">
+              <el-input v-model="formEdit.domain" placeholder="请输入应用域名" clearable />
             </el-form-item>
           </el-col>
           <el-col :span="2"></el-col>
           <el-col :span="10">
-            <el-form-item label="应用包名" required prop="package_name">
-              <el-input v-model="formEdit.package_name" placeholder="请输入应用包名" clearable/>
+            <el-form-item label="应用包名" prop="package_name">
+              <el-input v-model="formEdit.package_name" placeholder="请输入应用包名" clearable />
             </el-form-item>
           </el-col>
         </el-row>
         <el-row :gutter="5">
           <el-col :span="10">
-            <el-form-item label="一级分类" prop="category">
-              <el-select v-model="formEdit.category" placeholder="请选择上架应用商店">
-                <el-option value="App" label="App"/>
-                <el-option value="Game" label="Game"/>
-                <el-option value="Family" label="Family"/>
+            <el-form-item label="一级分类" required prop="category">
+              <el-select :disabled="primaryCategoriesData.length === 0" v-model="formEdit.category"
+                placeholder="请选择上架应用商店" @change="getSecondaryCategoriesData">
+                <el-option v-for="(item, index) in primaryCategoriesData" :key="item" :value="item" :label="item" />
               </el-select>
             </el-form-item>
           </el-col>
           <el-col :span="2"></el-col>
           <el-col :span="10">
             <el-form-item label="二级分类" required prop="sub_category">
-              <el-select v-model="formEdit.sub_category" placeholder="请选择上架应用商店">
-                <el-option value="Daydream" label="Daydream"/>
-                <el-option value="Wear" label="Android Wear"/>
-                <el-option value="Design" label="Art & Design"/>
-                <el-option value="Vehicles" label="Auto & Vehicles"/>
-                <el-option value="Beauty" label="Beauty"/>
-                <el-option value="Books" label="Books & Reference"/>
-                <el-option value="Business" label="Business"/>
-                <el-option value="Comics" label="Comics"/>
-                <el-option value="Communication" label="Communication"/>
-                <el-option value="Dating" label="Dating"/>
-                <el-option value="Education" label="Education"/>
-                <el-option value="Entertainment" label="Entertainment"/>
-                <el-option value="Events" label="Events"/>
-                <el-option value="Finance" label="Finance"/>
-                <el-option value="Drink" label="Food & Drink"/>
-                <el-option value="Fitness" label="Health & Fitness"/>
-                <el-option value="House" label="House & Home"/>
-                <el-option value="Libraries" label="Libraries & Demo"/>
+              <el-select :disabled="secondaryCategoriesData.length === 0" v-model="formEdit.sub_category"
+                placeholder="请选择上架应用商店">
+                <el-option v-for="(item, index) in secondaryCategoriesData" :key="item" :value="item" :label="item" />
               </el-select>
             </el-form-item>
           </el-col>
@@ -228,72 +214,97 @@
           <el-col :span="10">
             <el-form-item label="屏幕方向" prop="screen_orientation">
               <el-select v-model="formEdit.screen_orientation" placeholder="请选择屏幕方向">
-                <el-option :value="1" label="竖屏"/>
-                <el-option :value="2" label="横屏"/>
-                <el-option :value="3" label="所有"/>
+                <el-option :value="1" label="竖屏" />
+                <el-option :value="2" label="横屏" />
+                <el-option :value="3" label="所有" />
               </el-select>
             </el-form-item>
           </el-col>
           <el-col :span="2"></el-col>
           <el-col :span="10">
             <el-form-item label="版本号" required prop="versionCode">
-              <el-input v-model="formEdit.versionCode" placeholder="请输入版本号" clearable/>
+              <el-input v-model="formEdit.versionCode" placeholder="请输入版本号" clearable />
             </el-form-item>
           </el-col>
         </el-row>
         <el-row :gutter="5">
           <el-col :span="10">
             <el-form-item label="遵守美国COPPA" prop="coppa">
-              <el-switch v-model="formEdit.coppa"
-                         :active-value="2"
-                         :inactive-value="1"/>
+              <el-switch v-model="formEdit.coppa" :active-value="2" :inactive-value="1" />
             </el-form-item>
           </el-col>
           <el-col :span="2"></el-col>
           <el-col :span="10">
             <el-form-item label="遵守美国CCPA" required prop="ccpa">
-              <el-switch v-model="formEdit.ccpa"
-                         :active-value="2"
-                         :inactive-value="1"/>
+              <el-switch v-model="formEdit.ccpa" :active-value="2" :inactive-value="1" />
             </el-form-item>
           </el-col>
         </el-row>
-        <el-form-item label="二维码链接:" required prop="qrCode">
-          <el-input v-model="formEdit.qrCode" placeholder="请输入" clearable/>
-        </el-form-item>
-        <el-form-item label="apk应用包地址:" required prop="apkUrl">
+        <el-row :gutter="5">
+          <el-col :span="10">
+            <el-form-item label="二维码图片:" required prop="qrCode">
+              <el-upload class="avatar-uploader" accept="image/jpg,image/jpeg,image/png,image/webp" action="#"
+                :show-file-list="false" :before-upload="beforeAvatarUpload" :http-request="selfUpload">
+                <el-image v-if="formEdit.qrCode" style="width: 100px; height: 100px" :src="formEdit.qrCode" fit="fill"
+                  :preview-src-list="[formEdit.qrCode]" />
+                <el-icon v-else class="avatar-uploader-icon">
+                  <Plus />
+                </el-icon>
+                <template #tip>
+                  <div class="el-upload__tip">
+                    仅支持上传 jpg / png / webp 图片格式
+                  </div>
+                </template>
+              </el-upload>
+            </el-form-item>
+          </el-col>
+          <el-col :span="2"></el-col>
+          <el-col :span="10">
+            <el-form-item label="APP安装包:" required prop="apkUrl">
+              <el-upload class="upload-demo" :before-upload="beforeUpload" :http-request="customUpload"
+                :show-file-list="false">
+                <el-button type="primary">点击上传</el-button>
+                <template #tip>
+                  <div class="el-upload__tip">
+                    仅支持上传 Android (.apk/.aab) 或 iOS (.ipa) 安装包
+                  </div>
+                </template>
+              </el-upload>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <!--  <el-form-item label="apk应用包地址:" required prop="apkUrl">
           <el-input v-model="formEdit.apkUrl" placeholder="请输入" clearable/>
-        </el-form-item>
+        </el-form-item> -->
         <el-form-item label="更新提示:" required prop="updateTips">
-          <el-input v-model="formEdit.updateTips" placeholder="请输入" clearable/>
+          <el-input v-model="formEdit.updateTips" placeholder="请输入" clearable />
         </el-form-item>
       </el-form>
     </Layer>
   </div>
 </template>
 <script setup>
-import {onBeforeMount, ref, reactive} from "vue";
-import {ElMessage} from "element-plus";
-import {useGetDictList} from "@/hooks/useGetDictList.js";
+import { onBeforeMount, ref, reactive } from "vue";
+import { ElMessage } from "element-plus";
+import { useGetDictList } from "@/hooks/useGetDictList.js";
 import {
   channelAddOne,
   channelDeleteOne,
   channelUpdateOne,
   appList,
   saveOrUpdate,
-  delApp, channelList,
+  delApp, channelList, getSecondaryCategories, getPrimaryCategories, relativeChannel
 } from "@/api/formworkErection.js";
+import { attachFile, attachImage } from '@/api/common.js'
 import From from "@/components/from/index.vue";
 import Table from "@/components/table/index.vue";
 import Layer from "@/components/layer/index.vue";
-import {CopyDocument, Hide, View} from "@element-plus/icons-vue";
+import { CopyDocument, Hide, View, Plus, UploadFilled } from "@element-plus/icons-vue";
 import vCopy from '@/directive/copy'
 
-const {loadDictData, getDictionaryName} = useGetDictList();
+const { loadDictData, getDictionaryName } = useGetDictList();
 const tableData = ref([]);
 const channelData = ref([]);
-// 在 setup() 中
-const selectRow = ref([]); // 存储选中的行
 
 // 分页参数, 供table使用
 const page = reactive({
@@ -376,7 +387,7 @@ const settingData = () => {
 
 // 分页数据
 const getList = async () => {
-  let res = await appList({...formSearch.value});
+  let res = await appList({ ...formSearch.value });
   tableData.value = res.data;
   page.total = res.pageMeta.total;
 };
@@ -439,18 +450,22 @@ const handleSelectionChange = (val) => {
   context.emit("selection-change", val);
 };
 
-const handleChannelSelectionChange = (val) => {
-  console.log("获取渠道", val);
-  selectRow.value = val;
+// 关联渠道
+const selectedChannelId = ref(null)
+const selectRow = ref({})
+
+function handleRadioChange(val) {
+  console.log('选中渠道ID', val)
+  selectRow.value = channelData.value.find(item => item.channelId === val) || null
   console.log(selectRow.value)
-};
+}
 
 // 弹窗
 const layer = ref({
   show: false,
   title: "新增App",
   showButton: true,
-  width: "50vw",
+  width: "60vw",
   edit: false,
 });
 
@@ -462,6 +477,18 @@ const channelLayer = ref({
   edit: false,
 });
 
+const channelSubmit = async () => {
+  console.log('关联渠道', selectRow.value)
+  // selectRow.value.appId = channelAppId.value
+
+  // 目前只支持单个关联
+  await relativeChannel({ ...selectRow.value, appId: channelAppId.value }).then((res) => {
+    ElMessage.success('关联成功')
+    channelLayer.value.show = false;
+    getList();
+  })
+}
+
 const formEdit = ref({
   store_on_sale: "",
   store_type: null,
@@ -509,8 +536,10 @@ const edit = (row) => {
   layer.value.show = true;
 };
 
+const channelAppId = ref('')
 const openChannelLayer = (row) => {
   if (row) {
+    channelAppId.value = row.appId
     channelLayer.value.show = true;
   }
 
@@ -518,17 +547,50 @@ const openChannelLayer = (row) => {
 
 const ruleForm = ref(null);
 
-const rules = reactive({});
+const rules = reactive({
+  appName: [
+    { required: true, message: "请输入应用名称", trigger: "blur" },
+  ],
+  appType: [
+    { required: true, message: "请输入应用类型", trigger: "blur" },
+  ],
+  store_on_sale: [
+    { required: true, message: "请选择是否上架", trigger: "change" },
+  ],
+  category: [
+    { required: true, message: "请选择一级分类", trigger: "change" },
+  ],
+  sub_category: [
+    { required: true, message: "请选择二级分类", trigger: "change" },
+  ],
+  versionCode: [
+    { required: true, message: "请输入版本号", trigger: "blur" },
+  ],
+  qrCode: [
+    { required: true, message: "请上传二维码图片", trigger: "change" },
+  ],
+  apkUrl: [
+    { required: true, message: "请上传APP安装包", trigger: "change" },
+  ],
+  updateTips: [
+    { required: true, message: "请输入更新提示", trigger: "blur" },
+  ],
+});
 
 const submit = async (formEl) => {
   await formEl.validate(async (valid, fields) => {
+    if (formEdit.value.store_on_sale === 2 && !formEdit.value.store_url && !formEdit.value.store_type) {
+      ElMessage.error('上架商店或商店链接不能为空')
+      return
+    }
+
     if (valid) {
       // 提交内容
       if (layer.value.edit) {
         //api修改需要uuid
         formEdit.uuid = formEdit.appId;
       }
-      const result = await saveOrUpdate({...formEdit.value});
+      const result = await saveOrUpdate({ ...formEdit.value });
       console.log(result)
       if (result.code === 200) {
         if (layer.value.edit) {
@@ -550,11 +612,93 @@ const submit = async (formEl) => {
 
 // 删除
 const removeApp = async (row) => {
-  await delApp({appId: row.appId}).then((res) => {
+  await delApp({ appId: row.appId }).then((res) => {
     ElMessage.success("删除成功");
     handleFormSubmitted();
   });
 };
+
+// 获取指定平台下的一级分类
+const primaryCategoriesData = ref([])
+const getPrimaryCategoriesData = async () => {
+  formEdit.value.category = null
+  secondaryCategoriesData.value = []
+  let res = await getPrimaryCategories({
+    platform: formEdit.value.appTyp === 1 ? 'Android' : 'IOS'
+  })
+  primaryCategoriesData.value = res.data
+}
+
+// 获取指定平台下的一级分类下的二级分类
+const secondaryCategoriesData = ref([])
+const getSecondaryCategoriesData = async () => {
+  secondaryCategoriesData.value = []
+  if (formEdit.value.appType && formEdit.value.category) {
+    let res = await getSecondaryCategories({
+      platform: formEdit.value.appTyp === 1 ? 'Android' : 'IOS',
+      primaryCategory: formEdit.value.category
+    })
+    secondaryCategoriesData.value = res.data
+  }
+}
+
+// 上传二维码图片
+const beforeAvatarUpload = (rawFile) => {
+  let fileType = ["image/jpeg", "image/png", "image/webp"];
+  if (!fileType.includes(rawFile.type)) {
+    ElMessage.error("请上传图片格式为jpg/png/webp!");
+    return false;
+  } else if (rawFile.size / 1024 / 1024 > 2) {
+    ElMessage.error(`图片大小不能超过 2MB!`);
+    return false;
+  }
+  return true;
+}
+
+const selfUpload = async (param) => {
+  const file = param.file
+  const formData = new FormData()
+  formData.append('file', file)
+  try {
+    const res = await attachImage(formData)
+    formEdit.value.qrCode = res.data.url
+    ElMessage.success('图片上传成功')
+  } catch (err) {
+    ElMessage.error('图片上传失败')
+    console.error(err)
+  }
+};
+
+// 上传APP安装包
+// 自定义上传逻辑
+const customUpload = async (param) => {
+  const file = param.file
+  const formData = new FormData()
+  formData.append('file', file)
+  try {
+    const res = await attachFile(formData)
+    formEdit.value.apkUrl = res.data.url
+    console.log(res, res.data.url)
+    ElMessage.success('上传成功')
+  } catch (err) {
+    ElMessage.error('APP上传失败')
+    console.error(err)
+  }
+}
+
+// 上传前校验文件类型
+const beforeUpload = (file) => {
+  const fileExt = file.name.split('.').pop().toLowerCase()
+  const validExts = ['apk', 'aab', 'ipa']
+
+  if (!validExts.includes(fileExt)) {
+    ElMessage.error('只允许上传 .apk、.aab 或 .ipa 文件')
+    return false
+  }
+
+  return true
+}
+
 </script>
 
 <style scoped lang="scss">
@@ -581,4 +725,17 @@ const removeApp = async (row) => {
     }
   }
 }
+
+.el-icon.avatar-uploader-icon {
+  font-size: 28px;
+  color: #8c939d;
+  width: 100px;
+  height: 100px;
+  text-align: center;
+  border: 1px solid #e4e7ed;
+}
+
+:deep(.el-image-viewer__wrapper) {
+  z-index: 9999 !important;
+}
 </style>

+ 71 - 24
src/views/main/formworkErection/placementAdmin.vue

@@ -34,10 +34,10 @@
                          @click="edit(scope.row)">
                 编辑
               </el-button>
-              <el-popconfirm placement="left" title="确认删除该用户?" @confirm="removeType(scope.row)">
+              <el-popconfirm placement="left" title="确认删除该广告位?" @confirm="removeType(scope.row)">
                 <template #reference>
                   <el-button class="button-item" style="margin-bottom: 5px;"
-                             type="warning">删除类型
+                             type="danger">删除
                   </el-button>
                 </template>
               </el-popconfirm>
@@ -52,7 +52,7 @@
       <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="关联app:" required prop="appId">
+            <el-form-item label="关联app:" required prop="appData">
               <el-select v-model="formEdit.appData" filterable placeholder="请选择">
                 <el-option
                     v-for="item in appData"
@@ -73,13 +73,13 @@
 
         <el-row :gutter="5">
           <el-col :span="10">
-            <el-form-item label="广告样式:" required prop="adformat">
-              <el-select v-model="formEdit.adformat" placeholder="请选择广告样式">
-                <el-option value="native" label="native"/>
-                <el-option value="banner" label="banner"/>
-                <el-option value="rewarded_video" label="rewarded_video"/>
-                <el-option value="interstitial" label="interstitial"/>
-                <el-option value="splash" label="splash"/>
+            <el-form-item label="广告样式:" required prop="adFormat">
+              <el-select v-model="formEdit.adFormat" placeholder="请选择广告样式">
+                <el-option value="native" label="原生(信息流)"/>
+                <el-option value="banner" label="横幅"/>
+                <el-option value="rewarded_video" label="激励视频"/>
+                <el-option value="interstitial" label="插屏(插屏视频/插屏图片)"/>
+                <el-option value="splash" label="开屏广告"/>
               </el-select>
             </el-form-item>
           </el-col>
@@ -123,7 +123,7 @@ 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} from "@/api/formworkErection";
+import {appList, placementList, placementDel, placementSave} from "@/api/formworkErection";
 
 const store = useStore()
 const {dictData, loadDictData, getOptions, getDictionaryName} = useGetDictList();
@@ -151,6 +151,7 @@ const appPage = reactive({
 const getAppList = async () => {
   let res = await appList({...appFormSearch.value});
   appData.value = res.data;
+  console.log('appData.value',appData.value)
 };
 
 const changeAppTableData = () => {
@@ -296,19 +297,47 @@ const layer = ref({
 });
 
 const formEdit = ref({
-  remark: undefined, //备注
-  adformat: undefined,//广告样式
-  placementName: undefined,//广告位名称
-  appData: undefined,//广告位id
-  status:3,
+  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
 
@@ -317,22 +346,36 @@ const edit = (row) => {
 const ruleForm = ref(null);
 
 const rules = reactive({
-  formEditbannedLimit: [
-    {required: true, message: "请输入封禁期限", trigger: "blur"},
+  appData: [
+    {required: true, message: "请选择关联应用", trigger: "change"},
+  ],
+  placementName: [
+    {required: true, message: "请输入广告位名称", trigger: "blur"},
   ],
-  bannedReason: [
-    {
-      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 {
@@ -344,6 +387,10 @@ const submit = async (formEl) => {
 // 删除用户
 const removeType = async (row) => {
   console.log('删除', row)
+  await placementDel({placementId: row.placementId}).then((res)=> {
+    ElMessage.success('删除成功')
+    getList();
+  })
 }
 
 </script>

+ 4 - 4
src/views/main/userModule/components/card/index.vue

@@ -13,10 +13,10 @@ export default defineComponent({
   },
   setup() {
     const list = [
-      { id: 1, name: '总广告数', data: '100000', color: '#4e73df', icon: 'sfont system-yonghu' },
-      { id: 2, name: '封禁人数', data: '30', color: '#1cc88a', icon: 'sfont system-xiaoxi' },
-      { id: 3, name: '提现笔数', data: '42567', color: '#36b9cc', icon: 'sfont system-shuliang_mianxing' },
-      { id: 4, name: '提现金额', data: '168,000', color: '#f6c23e', icon: 'sfont system-jindutiaoshouyidaozhang' }
+      { id: 1, name: '总广告数', data: '1057', color: '#4e73df', icon: 'sfont system-yonghu' },
+      { id: 2, name: '封禁人数', data: '55', color: '#1cc88a', icon: 'sfont system-xiaoxi' },
+      { id: 3, name: '提现笔数', data: '827', color: '#36b9cc', icon: 'sfont system-shuliang_mianxing' },
+      { id: 4, name: '提现金额', data: '64581', color: '#f6c23e', icon: 'sfont system-jindutiaoshouyidaozhang' }
     ]
     return {
       list

+ 1 - 1
src/views/main/userModule/onlineList.vue

@@ -3,7 +3,7 @@
     <!-- 菜单栏 -->
     <!-- <From :form-items="dynamicFormItems" @formSubmitted="handleFormSubmitted" @formReset="handleFormReset" /> -->
     <div class="card">
-      <div class="title">平台总注册量: 10086111</div>
+      <div class="title">平台总注册量: 35806</div>
       <Card />
     </div>
     <!-- 表格 -->