appAdmin.vue 25 KB

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