appAdmin.vue 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834
  1. <template>
  2. <div class="layout-container">
  3. <!-- 菜单栏 -->
  4. <From :form-items="dynamicFormItems" @formSubmitted="handleFormSubmitted" @formReset="handleFormReset"
  5. is_add_button="新增" @addForm="edit" />
  6. <!-- 表格 -->
  7. <div class="layout-container">
  8. <Table @getTableData="changeTableData" v-model:page="page" ref="table" :data="tableData"
  9. @selection-change="handleSelectionChange">
  10. <el-table-column prop="appId" label="应用ID" width="140" />
  11. <el-table-column prop="appName" label="应用名称" width="100" />
  12. <el-table-column prop="appKey" label="应用秘钥" width="120">
  13. <template #default="scope">
  14. <label>{{ scope.row.showKey ? scope.row.appKey : "******" }}</label>
  15. <span style="margin-left: 15px">
  16. <el-icon v-if="scope.row.showKey" @click="() => { scope.row.showKey = !scope.row.showKey }">
  17. <View />
  18. </el-icon>
  19. <el-icon v-if="!scope.row.showKey" @click="() => { scope.row.showKey = !scope.row.showKey }">
  20. <Hide />
  21. </el-icon>
  22. </span>
  23. <span style="margin-left: 5px">
  24. <el-icon v-copy="scope.row.appKey">
  25. <CopyDocument />
  26. </el-icon>
  27. </span>
  28. </template>
  29. </el-table-column>
  30. <el-table-column prop="channelName" label="广告平台名称" width="160">
  31. <template #default="scope">
  32. <el-tag
  33. style="margin-left: 5px;"
  34. v-for="item in scope.row.channelName?.split(',')"
  35. :key="item"
  36. :type="item"
  37. effect="dark"
  38. >
  39. {{ item }}
  40. </el-tag>
  41. </template>
  42. </el-table-column>
  43. <el-table-column prop="ditchName" label="渠道名称" width="140">
  44. <template #default="scope">
  45. <el-tag
  46. type="success"
  47. effect="dark"
  48. v-if="scope.row.ditchName"
  49. >
  50. {{ scope.row.ditchName }}
  51. </el-tag>
  52. </template>
  53. </el-table-column>
  54. <el-table-column prop="networkAppName" label="关联渠道应用" width="120" />
  55. <el-table-column prop="nickName" label="所属用户" width="100" />
  56. <el-table-column prop="appType" label="应用类型" width="100">
  57. <template #default="scope">
  58. {{ getDictionaryName("app_type", scope.row.appType) }}
  59. </template>
  60. </el-table-column>
  61. <el-table-column prop="apkUrl" label="下载链接" width="120">
  62. <template #default="scope">
  63. <a :href="scope.row.apkUrl" target="_blank">{{ scope.row.appName }}.apk</a>
  64. </template>
  65. </el-table-column>
  66. <el-table-column prop="qrCode" min-width="130" label="二维码">
  67. <template #default="scope">
  68. <el-image style="width: 100px; height: 100px;" :z-index="99999" :src="scope.row.qrCode" fit="fill"
  69. :preview-src-list="[scope.row.qrCode]" preview-teleported="true" />
  70. </template>
  71. </el-table-column>
  72. <el-table-column prop="request" label="广告源请求数" width="120" />
  73. <el-table-column prop="fillrate" label="广告源填充率" width="120" />
  74. <el-table-column prop="impression" label="展示数" />
  75. <el-table-column prop="click" label="点击数" />
  76. <el-table-column prop="ecpm" label="ECPM" />
  77. <el-table-column prop="revenue" label="收益" />
  78. <el-table-column prop="impression_api" label="广告平台展示数" width="130" />
  79. <el-table-column prop="click_api" label="广告平台点击数" width="130" />
  80. <el-table-column prop="ecpm_api" label="广告平台ECPM" width="130" />
  81. <el-table-column prop="updateTips" label="更新提示" width="100" />
  82. <el-table-column prop="enabled" label="是否启用" width="100">
  83. <template #default="scope">
  84. {{ getDictionaryName("enabled", scope.row.enabled) }}
  85. </template>
  86. </el-table-column>
  87. <el-table-column label="操作" width="150" fixed="right">
  88. <template #default="scope">
  89. <div class="button">
  90. <el-button class="button-item" type="primary" style="margin-bottom: 5px"
  91. @click="openChannelLayer(scope.row)">
  92. 关联平台
  93. </el-button>
  94. <el-button class="button-item" type="primary" style="margin-bottom: 5px" @click="edit(scope.row)">
  95. 编辑
  96. </el-button>
  97. <el-popconfirm placement="left" title="确认删除该数据?" @confirm="removeApp(scope.row)">
  98. <template #reference>
  99. <el-button class="button-item" style="margin-bottom: 5px" type="warning">删除</el-button>
  100. </template>
  101. </el-popconfirm>
  102. </div>
  103. </template>
  104. </el-table-column>
  105. </Table>
  106. </div>
  107. <Layer :layer="channelLayer" @confirm="channelSubmit" @close="channelLayer.show = false">
  108. <el-radio-group style="width: 100%;" v-model="selectedChannelId" @change="handleRadioChange">
  109. <Table style="width: 100%;" @getTableData="getChanelData" v-model:page="channelPage" ref="channelTable"
  110. :data="channelData">
  111. <el-table-column width="60">
  112. <template #default="{ row }">
  113. <el-radio :label="row.channelId">&nbsp;</el-radio>
  114. </template>
  115. </el-table-column>
  116. <el-table-column prop="channelId" label="平台ID" />
  117. <el-table-column prop="channelName" label="平台名称" />
  118. <el-table-column prop="loginType" label="登录类型">
  119. <template #default="{ row }">
  120. {{ getDictionaryName("login_type", row.loginType) }}
  121. </template>
  122. </el-table-column>
  123. <el-table-column prop="channelAccount" label="平台账号" />
  124. <el-table-column prop="channelPwd" label="平台密码" />
  125. <el-table-column prop="channelStatus" label="平台状态">
  126. <template #default="{ row }">
  127. {{ getDictionaryName("channel_status", row.channelStatus) }}
  128. </template>
  129. </el-table-column>
  130. </Table>
  131. </el-radio-group>
  132. </Layer>
  133. <!-- 操作弹窗 -->
  134. <Layer :layer="layer" @confirm="submit(ruleForm)" @close="layer.show = false">
  135. <el-form :model="formEdit" :rules="rules" ref="ruleForm" label-width="130px" style="margin-right: 30px">
  136. <el-form-item label="出包渠道" required prop="ditchData">
  137. <el-select v-model="formEdit.ditchData">
  138. <el-option v-for="item in ditchListData" :key="item.ditchId" :value="item.ditchName" :label="item.ditchName" />
  139. </el-select>
  140. </el-form-item>
  141. <el-row :gutter="5">
  142. <el-col :span="10" v-if="formEdit.appId">
  143. <el-form-item label="应用ID" required prop="appId">
  144. <el-input v-model="formEdit.appId" disabled placeholder="请输入" clearable />
  145. </el-form-item>
  146. </el-col>
  147. <el-col :span="2" v-if="formEdit.appId"></el-col>
  148. <el-col :span="10">
  149. <el-form-item label="应用名称:" required prop="appName">
  150. <el-input v-model="formEdit.appName" placeholder="请输入" clearable />
  151. </el-form-item>
  152. </el-col>
  153. </el-row>
  154. <el-row :gutter="5">
  155. <el-col :span="10">
  156. <el-form-item label="应用类型" required prop="appType">
  157. <el-select v-model="formEdit.appType" @change="getPrimaryCategoriesData">
  158. <el-option :value="1" label="Android" />
  159. <el-option :value="2" label="IOS" />
  160. </el-select>
  161. </el-form-item>
  162. </el-col>
  163. <el-col :span="2"></el-col>
  164. <el-col :span="10">
  165. <el-form-item label="是否上架" required prop="store_on_sale">
  166. <el-select v-model="formEdit.store_on_sale">
  167. <el-option :value="1" label="否" />
  168. <el-option :value="2" label="是" />
  169. </el-select>
  170. </el-form-item>
  171. </el-col>
  172. </el-row>
  173. <el-row :gutter="5" v-if="formEdit.store_on_sale === 2">
  174. <el-col :span="10">
  175. <el-form-item label="上架商店" prop="store_type">
  176. <el-select v-model="formEdit.store_type" placeholder="请选择上架应用商店">
  177. <el-option :value="1" label="App Store" />
  178. <el-option :value="2" label="Google Play" />
  179. <el-option :value="3" label="华为商店" />
  180. <el-option :value="4" label="小米应用商店" />
  181. <el-option :value="5" label="OPPO商店" />
  182. <el-option :value="6" label="vivo商店" />
  183. <el-option :value="7" label="应用宝" />
  184. <el-option :value="8" label="TapTap" />
  185. <el-option :value="9" label="Amazon应用商店" />
  186. <el-option :value="10" label="三星应用商店" />
  187. <el-option :value="11" label="小米海外应用商店" />
  188. <el-option :value="12" label="华为海外应用市场" />
  189. <el-option :value="13" label="Roku" />
  190. <el-option :value="14" label="Samsung Smart TV" />
  191. <el-option :value="15" label="Microsoft" />
  192. <el-option :value="16" label="LG Smart TV" />
  193. <el-option :value="17" label="Vizio" />
  194. <el-option :value="99" label="其他" />
  195. </el-select>
  196. </el-form-item>
  197. </el-col>
  198. <el-col :span="2"></el-col>
  199. <el-col :span="10">
  200. <el-form-item label="商店链接" prop="store_url">
  201. <el-input v-model="formEdit.store_url" placeholder="请输入上架商店链接" clearable />
  202. </el-form-item>
  203. </el-col>
  204. </el-row>
  205. <el-row :gutter="5">
  206. <el-col :span="10">
  207. <el-form-item label="应用域名" prop="domain">
  208. <el-input v-model="formEdit.domain" placeholder="请输入应用域名" clearable />
  209. </el-form-item>
  210. </el-col>
  211. <el-col :span="2"></el-col>
  212. <el-col :span="10">
  213. <el-form-item label="应用包名" prop="package_name">
  214. <el-input v-model="formEdit.package_name" placeholder="请输入应用包名" clearable />
  215. </el-form-item>
  216. </el-col>
  217. </el-row>
  218. <el-row :gutter="5">
  219. <el-col :span="10">
  220. <el-form-item label="一级分类" required prop="category">
  221. <el-select :disabled="primaryCategoriesData.length === 0" v-model="formEdit.category"
  222. placeholder="请选择上架应用商店" @change="getSecondaryCategoriesData">
  223. <el-option v-for="(item, index) in primaryCategoriesData" :key="item" :value="item" :label="item" />
  224. </el-select>
  225. </el-form-item>
  226. </el-col>
  227. <el-col :span="2"></el-col>
  228. <el-col :span="10">
  229. <el-form-item label="二级分类" required prop="sub_category">
  230. <el-select :disabled="secondaryCategoriesData.length === 0" v-model="formEdit.sub_category"
  231. placeholder="请选择上架应用商店">
  232. <el-option v-for="(item, index) in secondaryCategoriesData" :key="item" :value="item" :label="item" />
  233. </el-select>
  234. </el-form-item>
  235. </el-col>
  236. </el-row>
  237. <el-row :gutter="5">
  238. <el-col :span="10">
  239. <el-form-item label="屏幕方向" prop="screen_orientation">
  240. <el-select v-model="formEdit.screen_orientation" placeholder="请选择屏幕方向">
  241. <el-option :value="1" label="竖屏" />
  242. <el-option :value="2" label="横屏" />
  243. <el-option :value="3" label="所有" />
  244. </el-select>
  245. </el-form-item>
  246. </el-col>
  247. <el-col :span="2"></el-col>
  248. <el-col :span="10">
  249. <el-form-item label="版本号" required prop="versionCode">
  250. <el-input v-model="formEdit.versionCode" placeholder="请输入版本号" clearable />
  251. </el-form-item>
  252. </el-col>
  253. </el-row>
  254. <el-row :gutter="5">
  255. <el-col :span="10">
  256. <el-form-item label="遵守美国COPPA" prop="coppa">
  257. <el-switch v-model="formEdit.coppa" :active-value="2" :inactive-value="1" />
  258. </el-form-item>
  259. </el-col>
  260. <el-col :span="2"></el-col>
  261. <el-col :span="10">
  262. <el-form-item label="遵守美国CCPA" required prop="ccpa">
  263. <el-switch v-model="formEdit.ccpa" :active-value="2" :inactive-value="1" />
  264. </el-form-item>
  265. </el-col>
  266. </el-row>
  267. <el-row :gutter="5">
  268. <el-col :span="10">
  269. <el-form-item label="二维码图片:" required prop="qrCode">
  270. <el-upload class="avatar-uploader" accept="image/jpg,image/jpeg,image/png,image/webp" action="#"
  271. :show-file-list="false" :before-upload="beforeAvatarUpload" :http-request="selfUpload">
  272. <el-image v-if="formEdit.qrCode" style="width: 100px; height: 100px" :src="formEdit.qrCode" fit="fill"
  273. />
  274. <el-icon v-else class="avatar-uploader-icon">
  275. <Plus />
  276. </el-icon>
  277. <template #tip>
  278. <div class="el-upload__tip">
  279. 仅支持上传 jpg / png / webp 图片格式
  280. </div>
  281. </template>
  282. </el-upload>
  283. </el-form-item>
  284. </el-col>
  285. <el-col :span="2"></el-col>
  286. <el-col :span="10">
  287. <el-form-item label="APP安装包:" required prop="apkUrl">
  288. <el-upload class="upload-demo" :before-upload="beforeUpload" :http-request="customUpload"
  289. :show-file-list="false">
  290. <el-button type="primary">点击上传</el-button>
  291. <template #tip>
  292. <div class="el-upload__tip">
  293. 仅支持上传 Android (.apk/.aab) 或 iOS (.ipa) 安装包
  294. </div>
  295. </template>
  296. </el-upload>
  297. </el-form-item>
  298. </el-col>
  299. </el-row>
  300. <!-- <el-form-item label="apk应用包地址:" required prop="apkUrl">
  301. <el-input v-model="formEdit.apkUrl" placeholder="请输入" clearable/>
  302. </el-form-item> -->
  303. <el-form-item label="更新提示:" required prop="updateTips">
  304. <el-input v-model="formEdit.updateTips" placeholder="请输入" clearable />
  305. </el-form-item>
  306. </el-form>
  307. </Layer>
  308. </div>
  309. </template>
  310. <script setup>
  311. import { onBeforeMount, ref, reactive } from "vue";
  312. import QRCode from 'qrcode' //生成二维码
  313. import { ElMessage,ElLoading } from "element-plus";
  314. import { useGetDictList } from "@/hooks/useGetDictList.js";
  315. import {
  316. channelAddOne,
  317. channelDeleteOne,
  318. channelUpdateOne,
  319. appList,
  320. saveOrUpdate,
  321. delApp, channelList, getSecondaryCategories, getPrimaryCategories, relativeChannel, relativeChannelList
  322. } from "@/api/formworkErection.js";
  323. import { attachFile, attachImage } from '@/api/common.js'
  324. import { ditchList } from '@/api/outBagModule.js'
  325. import From from "@/components/from/index.vue";
  326. import Table from "@/components/table/index.vue";
  327. import Layer from "@/components/layer/index.vue";
  328. import { CopyDocument, Hide, View, Plus, UploadFilled } from "@element-plus/icons-vue";
  329. import vCopy from '@/directive/copy'
  330. const { loadDictData, getDictionaryName } = useGetDictList();
  331. const tableData = ref([]);
  332. const channelData = ref([]);
  333. // 分页参数, 供table使用
  334. const page = reactive({
  335. pageNum: 1,
  336. pageSize: 20,
  337. total: 0,
  338. });
  339. const channelPage = reactive({
  340. pageNum: 1,
  341. pageSize: 20,
  342. total: 0,
  343. });
  344. const formSearch = ref({
  345. appName: null,
  346. channelName: null,
  347. appType: null,
  348. page: 1, // 当前页码
  349. limit: 20, // 当前页数量(查询量)
  350. });
  351. const dynamicFormItems = ref([]);
  352. onBeforeMount(() => {
  353. settingData();
  354. getList();
  355. getDitchList();
  356. getChannelList()
  357. });
  358. // 获取缓存数据设置筛选数据
  359. const settingData = () => {
  360. loadDictData().then(() => {
  361. dynamicFormItems.value = [
  362. {
  363. label: "应用名称",
  364. prop: "appName",
  365. type: "input",
  366. },
  367. {
  368. label: "广告平台",
  369. prop: "channelName",
  370. type: "select",
  371. options: []
  372. },
  373. {
  374. label: "应用类型",
  375. prop: "appType",
  376. type: "select",
  377. options: [
  378. {
  379. label: "全部",
  380. value: null,
  381. },
  382. {
  383. label: "Android",
  384. value: 1,
  385. },
  386. {
  387. label: "IOS",
  388. value: 2,
  389. },
  390. ],
  391. },
  392. ];
  393. });
  394. };
  395. // 获取广告平台
  396. const channelListData = ref([])
  397. const getChannelList = async() => {
  398. let res = await channelList({
  399. page: 1,
  400. limit: 999,
  401. })
  402. channelListData.value.push({
  403. label: '全部',
  404. value: null,
  405. })
  406. res.data.forEach((item,index) => {
  407. let options = {
  408. label: item.channelName,
  409. value: item.channelName,
  410. }
  411. channelListData.value.push(options)
  412. })
  413. dynamicFormItems.value[1].options = channelListData.value
  414. }
  415. // 获取出包渠道
  416. const ditchListData = ref([])
  417. const getDitchList = async() => {
  418. let res = await ditchList({
  419. page: 1,
  420. limit: 999,
  421. })
  422. ditchListData.value = res.data
  423. }
  424. function findByDitchName(name) {
  425. return ditchListData.value.find(item => item.ditchName === name)
  426. }
  427. // 分页数据
  428. const getList = async () => {
  429. let res = await appList({ ...formSearch.value });
  430. tableData.value = res.data;
  431. page.total = res.pageMeta.total;
  432. };
  433. const changeTableData = () => {
  434. formSearch.value.page = page.pageNum;
  435. formSearch.value.limit = page.pageSize;
  436. // 分页切换
  437. getList();
  438. };
  439. // 搜索
  440. const handleFormSubmitted = (formData) => {
  441. console.log("接收到子组件传递的数据", formData);
  442. formSearch.value.page = 1;
  443. formSearch.value.limit = 20;
  444. formSearch.value.channelName = formData?.channelName;
  445. formSearch.value.appName = formData?.appName;
  446. formSearch.value.appType = formData?.appType;
  447. getList();
  448. };
  449. // 表单重置
  450. const handleFormReset = () => {
  451. formSearch.value = {
  452. appName: null,
  453. channelName: null,
  454. appType: null,
  455. page: 1, // 当前页码
  456. limit: 20, // 当前页数量(查询量)
  457. };
  458. getList();
  459. };
  460. // 选择监听器
  461. const handleSelectionChange = (val) => {
  462. context.emit("selection-change", val);
  463. };
  464. // 关联平台
  465. const selectedChannelId = ref(null)
  466. const selectRow = ref({})
  467. function handleRadioChange(val) {
  468. console.log('选中平台ID', val)
  469. selectRow.value = channelData.value.find(item => item.channelId === val) || null
  470. console.log(selectRow.value)
  471. }
  472. const selectedRow = ref(null); // 存储选中的行
  473. // 选中行的方法
  474. const selectRowClick = (row) => {
  475. // 如果点击已选中的行,则取消选中;否则选中新行
  476. selectedRow.value = selectedRow.value === row ? null : row;
  477. };
  478. // 行点击事件(点击行任意位置选中)
  479. const handleRowClick = (row) => {
  480. selectRow(row);
  481. };
  482. // 弹窗
  483. const layer = ref({
  484. show: false,
  485. title: "新增App",
  486. showButton: true,
  487. width: "60vw",
  488. edit: false,
  489. });
  490. const channelLayer = ref({
  491. show: false,
  492. title: "关联平台",
  493. showButton: true,
  494. width: "50vw",
  495. edit: false,
  496. });
  497. const channelSubmit = async () => {
  498. console.log('关联平台', selectRow.value)
  499. // selectRow.value.appId = channelAppId.value
  500. // 目前只支持单个关联
  501. await relativeChannel({ ...selectRow.value, appId: channelAppId.value }).then((res) => {
  502. ElMessage.success('关联成功')
  503. channelLayer.value.show = false;
  504. getList();
  505. })
  506. }
  507. const formEdit = ref({
  508. ditchData: null, //渠道数据
  509. ditchId: null, //渠道ID
  510. ditchName: '',//渠道名称
  511. store_on_sale: "",
  512. store_type: null,
  513. store_url: "",
  514. category: "",
  515. sub_category: "",
  516. screen_orientation: "",
  517. versionCode: "",
  518. updateTips: "",
  519. apiSecret: "", //请求密令
  520. channelAccount: "", //广告平台账号
  521. channelId: "", //广告平台ID
  522. channelName: "", //广告平台名称
  523. channelPwd: "", //广告平台密码
  524. channelStatus: 0, //广告平台状态
  525. loginType: 0, //登录类型 1-账号密码 2-微信
  526. remark: "", //备注
  527. appId: "",
  528. apiKey: "", //请求秘钥
  529. appName: "",
  530. coppa: 0,
  531. ccpa: 0,
  532. qrCode: "",
  533. apkUrl: "",
  534. package_name: "",
  535. });
  536. const edit = (row) => {
  537. ruleForm.value?.resetFields();
  538. if (row) {
  539. layer.value.title = "编辑APP";
  540. layer.value.edit = true;
  541. formEdit.value = row;
  542. formEdit.value.store_on_sale = row.storeOnSale;
  543. formEdit.value.store_type = row.storeType;
  544. formEdit.value.package_name = row.packageName;
  545. formEdit.value.store_url = row.storeUrl;
  546. formEdit.value.sub_category = row.subCategory;
  547. formEdit.value.screen_orientation = row.screenOrientation;
  548. // formEdit.value.ditchData = findByDitchName(row.ditchName)
  549. formEdit.value.ditchData = row.ditchName
  550. } else {
  551. layer.value.title = "新增APP";
  552. layer.value.edit = false;
  553. formEdit.value = {};
  554. }
  555. layer.value.show = true;
  556. };
  557. const channelAppId = ref('')
  558. const openChannelLayer = async (row) => {
  559. if (row) {
  560. selectedChannelId.value = null
  561. channelAppId.value = row.appId
  562. let res =await relativeChannelList({appId: row.appId,page:channelPage.pageNum,limit:channelPage.pageSize})
  563. channelData.value = res.data;
  564. channelPage.total = res.pageMeta.total;
  565. channelLayer.value.show = true;
  566. }
  567. }
  568. const ruleForm = ref(null);
  569. const rules = reactive({
  570. ditchData: [
  571. { required: true, message: "请选择出包渠道", trigger: "change" },
  572. ],
  573. appName: [
  574. { required: true, message: "请输入应用名称", trigger: "blur" },
  575. ],
  576. appType: [
  577. { required: true, message: "请输入应用类型", trigger: "blur" },
  578. ],
  579. store_on_sale: [
  580. { required: true, message: "请选择是否上架", trigger: "change" },
  581. ],
  582. category: [
  583. { required: true, message: "请选择一级分类", trigger: "change" },
  584. ],
  585. sub_category: [
  586. { required: true, message: "请选择二级分类", trigger: "change" },
  587. ],
  588. versionCode: [
  589. { required: true, message: "请输入版本号", trigger: "blur" },
  590. ],
  591. qrCode: [
  592. { required: true, message: "请上传二维码图片", trigger: "change" },
  593. ],
  594. apkUrl: [
  595. { required: true, message: "请上传APP安装包", trigger: "change" },
  596. ],
  597. updateTips: [
  598. { required: true, message: "请输入更新提示", trigger: "blur" },
  599. ],
  600. });
  601. const submit = async (formEl) => {
  602. await formEl.validate(async (valid, fields) => {
  603. if (formEdit.value.store_on_sale === 2 && !formEdit.value.store_url && !formEdit.value.store_type) {
  604. ElMessage.error('上架商店或商店链接不能为空')
  605. return
  606. }
  607. if (valid) {
  608. formEdit.value.ditchData = findByDitchName(formEdit.value.ditchData)
  609. formEdit.value.ditchId = formEdit.value.ditchData.ditchId
  610. formEdit.value.ditchName = formEdit.value.ditchData.ditchName
  611. delete formEdit.value.ditchData
  612. // 提交内容
  613. if (layer.value.edit) {
  614. //api修改需要uuid
  615. formEdit.value.uuid = formEdit.value.appId;
  616. }
  617. const result = await saveOrUpdate({ ...formEdit.value });
  618. console.log(result)
  619. if (result.code === 200) {
  620. if (layer.value.edit) {
  621. ElMessage.success("修改成功");
  622. layer.value.show = false;
  623. } else {
  624. ElMessage.success("新增成功");
  625. layer.value.show = false;
  626. }
  627. handleFormSubmitted();
  628. } else {
  629. ElMessage.error(result.msg);
  630. }
  631. } else {
  632. console.log("error submit!", fields);
  633. }
  634. });
  635. };
  636. // 删除
  637. const removeApp = async (row) => {
  638. await delApp({ appId: row.appId }).then((res) => {
  639. ElMessage.success("删除成功");
  640. handleFormSubmitted();
  641. });
  642. };
  643. // 获取指定平台下的一级分类
  644. const primaryCategoriesData = ref([])
  645. const getPrimaryCategoriesData = async () => {
  646. formEdit.value.category = null
  647. secondaryCategoriesData.value = []
  648. let res = await getPrimaryCategories({
  649. platform: formEdit.value.appTyp === 1 ? 'Android' : 'IOS'
  650. })
  651. primaryCategoriesData.value = res.data
  652. }
  653. // 获取指定平台下的一级分类下的二级分类
  654. const secondaryCategoriesData = ref([])
  655. const getSecondaryCategoriesData = async () => {
  656. secondaryCategoriesData.value = []
  657. if (formEdit.value.appType && formEdit.value.category) {
  658. let res = await getSecondaryCategories({
  659. platform: formEdit.value.appTyp === 1 ? 'Android' : 'IOS',
  660. primaryCategory: formEdit.value.category
  661. })
  662. secondaryCategoriesData.value = res.data
  663. }
  664. }
  665. const loading = ref(null)
  666. // 加载信息
  667. const openFullScreen = (loadText) => {
  668. loading.value = ElLoading.service({
  669. lock: true,
  670. text: loadText,
  671. background: 'rgba(0, 0, 0, 0.7)',
  672. })
  673. }
  674. const closeFullScreen = () => {
  675. loading.value.close()
  676. }
  677. // 上传二维码图片
  678. const beforeAvatarUpload = (rawFile) => {
  679. let fileType = ["image/jpeg", "image/png", "image/webp"];
  680. if (!fileType.includes(rawFile.type)) {
  681. ElMessage.error("请上传图片格式为jpg/png/webp!");
  682. return false;
  683. } else if (rawFile.size / 1024 / 1024 > 2) {
  684. ElMessage.error(`图片大小不能超过 2MB!`);
  685. return false;
  686. }
  687. return true;
  688. }
  689. const selfUpload = async (param) => {
  690. const file = param.file
  691. const formData = new FormData()
  692. formData.append('file', file)
  693. openFullScreen('图片上传中')
  694. try {
  695. const res = await attachImage(formData)
  696. formEdit.value.qrCode = res.data.url
  697. ElMessage.success('图片上传成功')
  698. } catch (err) {
  699. ElMessage.error('图片上传失败')
  700. console.error(err)
  701. } finally {
  702. closeFullScreen()
  703. }
  704. };
  705. // 上传APP安装包
  706. // 自定义上传逻辑
  707. const customUpload = async (param) => {
  708. const file = param.file
  709. const formData = new FormData()
  710. formData.append('file', file)
  711. openFullScreen('APP上传中')
  712. try {
  713. const res = await attachFile(formData)
  714. formEdit.value.apkUrl = res.data.url
  715. console.log(res, res.data.url)
  716. ElMessage.success('上传成功')
  717. // 生成二维码
  718. QRCode.toDataURL(res.data.url).then(res => {
  719. formEdit.value.qrCode = res
  720. ElMessage.success('二维码生成成功!')
  721. })
  722. } catch (err) {
  723. ElMessage.error('APP上传失败')
  724. console.error(err)
  725. } finally {
  726. closeFullScreen()
  727. }
  728. }
  729. // 上传前校验文件类型
  730. const beforeUpload = (file) => {
  731. const fileExt = file.name.split('.').pop().toLowerCase()
  732. const validExts = ['apk', 'aab', 'ipa']
  733. if (!validExts.includes(fileExt)) {
  734. ElMessage.error('只允许上传 .apk、.aab 或 .ipa 文件')
  735. return false
  736. }
  737. return true
  738. }
  739. </script>
  740. <style scoped lang="scss">
  741. .layout-container {
  742. .card {
  743. .title {
  744. margin-bottom: 10px;
  745. font-weight: 600;
  746. }
  747. display: flex;
  748. flex-direction: column;
  749. align-items: start;
  750. width: calc(100% - 60px);
  751. margin: 30px 30px 0;
  752. }
  753. .button {
  754. display: flex;
  755. flex-direction: column;
  756. .button-item {
  757. margin: 4px;
  758. }
  759. }
  760. }
  761. .el-icon.avatar-uploader-icon {
  762. font-size: 28px;
  763. color: #8c939d;
  764. width: 100px;
  765. height: 100px;
  766. text-align: center;
  767. border: 1px solid #e4e7ed;
  768. }
  769. :deep(.el-image-viewer__wrapper) {
  770. z-index: 9999 !important;
  771. }
  772. </style>