|
@@ -1,269 +1,600 @@
|
|
|
<template>
|
|
<template>
|
|
|
- <section class="page stack">
|
|
|
|
|
|
|
+ <section class="page">
|
|
|
<div class="panel">
|
|
<div class="panel">
|
|
|
<div class="section-head">
|
|
<div class="section-head">
|
|
|
<div>
|
|
<div>
|
|
|
<p class="section-tag">App 管理</p>
|
|
<p class="section-tag">App 管理</p>
|
|
|
- <h2>应用列表</h2>
|
|
|
|
|
|
|
+ <h2>{{ selectedApp ? selectedApp.application.name : '请选择应用' }}</h2>
|
|
|
|
|
+ <p class="muted">
|
|
|
|
|
+ {{ selectedApp ? selectedApp.application.packageName : '点击左侧 App 管理后,先从弹窗中选择一个应用。' }}
|
|
|
|
|
+ </p>
|
|
|
</div>
|
|
</div>
|
|
|
- <button class="primary" @click="showCreateApp = !showCreateApp">创建 App</button>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <form v-if="showCreateApp" class="form-grid" @submit.prevent="createApp">
|
|
|
|
|
- <label><span>名称</span><input v-model="appForm.name" required /></label>
|
|
|
|
|
- <label><span>包名</span><input v-model="appForm.packageName" required /></label>
|
|
|
|
|
- <label><span>插件包名前缀</span><input v-model="appForm.pluginPackageName" /></label>
|
|
|
|
|
- <label class="full"><span>说明</span><input v-model="appForm.description" /></label>
|
|
|
|
|
- <label class="toggle"><input type="checkbox" v-model="appForm.pluginManagementEnabled" />支持插件化</label>
|
|
|
|
|
- <button class="primary">保存 App</button>
|
|
|
|
|
- </form>
|
|
|
|
|
-
|
|
|
|
|
- <div class="list-grid">
|
|
|
|
|
- <button
|
|
|
|
|
- v-for="item in applications"
|
|
|
|
|
- :key="item.application.id"
|
|
|
|
|
- class="list-card"
|
|
|
|
|
- :data-active="selectedApp?.application.id === item.application.id"
|
|
|
|
|
- @click="selectApp(item)"
|
|
|
|
|
- >
|
|
|
|
|
- <strong>{{ item.application.name }}</strong>
|
|
|
|
|
- <span>{{ item.application.packageName }}</span>
|
|
|
|
|
- <span>{{ item.application.pluginManagementEnabled ? '支持插件化' : '仅宿主' }}</span>
|
|
|
|
|
- </button>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <div class="panel" v-if="selectedApp">
|
|
|
|
|
- <div class="section-head">
|
|
|
|
|
- <div>
|
|
|
|
|
- <p class="section-tag">当前应用</p>
|
|
|
|
|
- <h2>{{ selectedApp.application.name }}</h2>
|
|
|
|
|
|
|
+ <div class="actions">
|
|
|
|
|
+ <button class="secondary" @click="openSelector">切换应用</button>
|
|
|
|
|
+ <button v-if="selectedApp" class="primary" @click="openCreateModal">
|
|
|
|
|
+ {{ tab === 'packages' ? '添加 APK' : '添加插件' }}
|
|
|
|
|
+ </button>
|
|
|
</div>
|
|
</div>
|
|
|
- <button class="secondary" @click="loadAll">刷新</button>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <form class="form-grid" @submit.prevent="saveApp">
|
|
|
|
|
- <label><span>名称</span><input v-model="appEdit.name" required /></label>
|
|
|
|
|
- <label><span>包名</span><input v-model="appEdit.packageName" required /></label>
|
|
|
|
|
- <label><span>插件包名前缀</span><input v-model="appEdit.pluginPackageName" /></label>
|
|
|
|
|
- <label class="full"><span>说明</span><input v-model="appEdit.description" /></label>
|
|
|
|
|
- <label class="toggle"><input type="checkbox" v-model="appEdit.pluginManagementEnabled" />支持插件化</label>
|
|
|
|
|
- <button class="primary">修改 App 信息</button>
|
|
|
|
|
- </form>
|
|
|
|
|
-
|
|
|
|
|
- <div class="tab-row">
|
|
|
|
|
- <button :class="tab === 'packages' ? 'primary' : 'secondary'" @click="tab = 'packages'">安装包</button>
|
|
|
|
|
- <button
|
|
|
|
|
- v-if="selectedApp.application.pluginManagementEnabled"
|
|
|
|
|
- :class="tab === 'plugins' ? 'primary' : 'secondary'"
|
|
|
|
|
- @click="tab = 'plugins'"
|
|
|
|
|
- >
|
|
|
|
|
- 插件列表
|
|
|
|
|
- </button>
|
|
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- <template v-if="tab === 'packages'">
|
|
|
|
|
- <form class="form-grid" @submit.prevent="uploadAppPackage">
|
|
|
|
|
- <label><span>包名</span><input v-model="appPackageForm.packageName" required /></label>
|
|
|
|
|
- <label><span>版本名</span><input v-model="appPackageForm.versionName" required /></label>
|
|
|
|
|
- <label><span>版本码</span><input v-model.number="appPackageForm.versionCode" type="number" required /></label>
|
|
|
|
|
- <label><span>文件名</span><input v-model="appPackageForm.uploadedFileName" required /></label>
|
|
|
|
|
- <label class="full"><span>下载地址</span><input v-model="appPackageForm.downloadUrl" required /></label>
|
|
|
|
|
- <label class="full"><span>标题</span><input v-model="appPackageForm.title" required /></label>
|
|
|
|
|
- <label class="full"><span>更新说明</span><textarea v-model="appPackageForm.changelog" rows="3" /></label>
|
|
|
|
|
- <button class="primary">上传安装包</button>
|
|
|
|
|
- </form>
|
|
|
|
|
-
|
|
|
|
|
- <table class="table">
|
|
|
|
|
- <thead><tr><th>版本</th><th>状态</th><th>操作</th></tr></thead>
|
|
|
|
|
- <tbody>
|
|
|
|
|
- <tr v-for="release in appPackages" :key="release.id">
|
|
|
|
|
- <td>{{ release.versionName }} ({{ release.versionCode }})</td>
|
|
|
|
|
- <td>{{ release.status }}</td>
|
|
|
|
|
- <td class="actions">
|
|
|
|
|
- <button class="ghost" @click="publishFull(release.id)">发布当前安装包</button>
|
|
|
|
|
- <button class="ghost" @click="prepareGray(release.id)">配置灰度</button>
|
|
|
|
|
- </td>
|
|
|
|
|
- </tr>
|
|
|
|
|
- </tbody>
|
|
|
|
|
- </table>
|
|
|
|
|
- </template>
|
|
|
|
|
-
|
|
|
|
|
- <template v-else>
|
|
|
|
|
- <form class="form-grid" @submit.prevent="createPlugin">
|
|
|
|
|
- <label><span>插件名</span><input v-model="pluginForm.name" required /></label>
|
|
|
|
|
- <label><span>插件包名</span><input v-model="pluginForm.packageName" required /></label>
|
|
|
|
|
- <label class="full"><span>入口 Activity</span><input v-model="pluginForm.entryActivity" /></label>
|
|
|
|
|
- <label class="full"><span>说明</span><input v-model="pluginForm.description" /></label>
|
|
|
|
|
- <button class="primary">新建插件</button>
|
|
|
|
|
- </form>
|
|
|
|
|
|
|
+ <template v-if="selectedApp">
|
|
|
|
|
+ <div class="selected-bar">
|
|
|
|
|
+ <div class="chips">
|
|
|
|
|
+ <span>{{ selectedApp.application.pluginManagementEnabled ? '支持插件化' : '仅宿主 APK' }}</span>
|
|
|
|
|
+ <span>{{ selectedApp.application.businessModules.join(' / ') }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <button class="secondary" @click="loadSelectedApp">刷新当前应用</button>
|
|
|
|
|
+ </div>
|
|
|
|
|
|
|
|
- <div class="list-grid">
|
|
|
|
|
|
|
+ <div class="tab-row wide-tabs">
|
|
|
|
|
+ <button :class="tab === 'packages' ? 'primary' : 'secondary'" @click="tab = 'packages'">APK 列表</button>
|
|
|
<button
|
|
<button
|
|
|
- v-for="plugin in plugins"
|
|
|
|
|
- :key="plugin.id"
|
|
|
|
|
- class="list-card"
|
|
|
|
|
- :data-active="selectedPlugin?.id === plugin.id"
|
|
|
|
|
- @click="selectPlugin(plugin)"
|
|
|
|
|
|
|
+ :class="tab === 'plugins' ? 'primary' : 'secondary'"
|
|
|
|
|
+ :disabled="!selectedApp.application.pluginManagementEnabled"
|
|
|
|
|
+ @click="tab = 'plugins'"
|
|
|
>
|
|
>
|
|
|
- <strong>{{ plugin.name }}</strong>
|
|
|
|
|
- <span>{{ plugin.packageName }}</span>
|
|
|
|
|
- <span>{{ plugin.enabled ? '已启用' : '未启用' }}</span>
|
|
|
|
|
|
|
+ 插件列表
|
|
|
</button>
|
|
</button>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- <div v-if="selectedPlugin" class="sub-panel">
|
|
|
|
|
- <h3>{{ selectedPlugin.name }} 安装包</h3>
|
|
|
|
|
- <form class="form-grid" @submit.prevent="uploadPluginPackage">
|
|
|
|
|
- <label><span>版本名</span><input v-model="pluginPackageForm.versionName" required /></label>
|
|
|
|
|
- <label><span>版本码</span><input v-model.number="pluginPackageForm.versionCode" type="number" required /></label>
|
|
|
|
|
- <label><span>宿主最低版本码</span><input v-model.number="pluginPackageForm.minHostVersionCode" type="number" /></label>
|
|
|
|
|
- <label><span>宿主最低版本名</span><input v-model="pluginPackageForm.minHostVersionName" /></label>
|
|
|
|
|
- <label><span>文件名</span><input v-model="pluginPackageForm.uploadedFileName" required /></label>
|
|
|
|
|
- <label class="full"><span>下载地址</span><input v-model="pluginPackageForm.downloadUrl" required /></label>
|
|
|
|
|
- <label class="full"><span>标题</span><input v-model="pluginPackageForm.title" required /></label>
|
|
|
|
|
- <label class="full"><span>更新说明</span><textarea v-model="pluginPackageForm.changelog" rows="3" /></label>
|
|
|
|
|
- <button class="primary">上传插件安装包</button>
|
|
|
|
|
- </form>
|
|
|
|
|
|
|
+ <div class="sub-panel">
|
|
|
|
|
+ <div class="section-head">
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <p class="section-tag">{{ tab === 'packages' ? 'APK 管理' : '插件管理' }}</p>
|
|
|
|
|
+ <h3>{{ tab === 'packages' ? 'APK 安装包列表' : '插件安装包列表' }}</h3>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
|
|
|
<table class="table">
|
|
<table class="table">
|
|
|
- <thead><tr><th>版本</th><th>宿主最低版本</th><th>状态</th><th>操作</th></tr></thead>
|
|
|
|
|
- <tbody>
|
|
|
|
|
- <tr v-for="release in pluginPackages" :key="release.id">
|
|
|
|
|
|
|
+ <thead v-if="tab === 'packages'">
|
|
|
|
|
+ <tr><th>版本</th><th>文件名</th><th>状态</th><th>操作</th></tr>
|
|
|
|
|
+ </thead>
|
|
|
|
|
+ <thead v-else>
|
|
|
|
|
+ <tr><th>插件</th><th>版本</th><th>宿主最低版本</th><th>状态</th><th>操作</th></tr>
|
|
|
|
|
+ </thead>
|
|
|
|
|
+ <tbody v-if="tab === 'packages'">
|
|
|
|
|
+ <tr v-for="release in appPackages" :key="release.id">
|
|
|
|
|
+ <td>{{ release.versionName }} ({{ release.versionCode }})</td>
|
|
|
|
|
+ <td>{{ release.uploadedFileName }}</td>
|
|
|
|
|
+ <td>{{ release.status }}</td>
|
|
|
|
|
+ <td class="actions">
|
|
|
|
|
+ <button class="ghost" @click="downloadFile(release.downloadUrl)">下载</button>
|
|
|
|
|
+ <button class="ghost" @click="publishFull(release.id)">发布</button>
|
|
|
|
|
+ <button class="ghost" @click="prepareGray(release.id)">灰度</button>
|
|
|
|
|
+ <button class="ghost" @click="openEditReleaseModal(release, 'APP')">编辑</button>
|
|
|
|
|
+ <button v-if="release.status === 'DRAFT'" class="ghost danger" @click="removeRelease(release.id)">删除</button>
|
|
|
|
|
+ </td>
|
|
|
|
|
+ </tr>
|
|
|
|
|
+ </tbody>
|
|
|
|
|
+ <tbody v-else>
|
|
|
|
|
+ <tr v-for="release in pluginReleaseRows" :key="release.id">
|
|
|
|
|
+ <td>{{ release.pluginName }}</td>
|
|
|
<td>{{ release.versionName }} ({{ release.versionCode }})</td>
|
|
<td>{{ release.versionName }} ({{ release.versionCode }})</td>
|
|
|
<td>{{ release.minHostVersionName || '-' }}</td>
|
|
<td>{{ release.minHostVersionName || '-' }}</td>
|
|
|
<td>{{ release.status }}</td>
|
|
<td>{{ release.status }}</td>
|
|
|
<td class="actions">
|
|
<td class="actions">
|
|
|
- <button class="ghost" @click="publishFull(release.id)">发版</button>
|
|
|
|
|
|
|
+ <button class="ghost" @click="downloadFile(release.downloadUrl)">下载</button>
|
|
|
|
|
+ <button class="ghost" @click="publishFull(release.id)">发布</button>
|
|
|
<button class="ghost" @click="prepareGray(release.id)">灰度</button>
|
|
<button class="ghost" @click="prepareGray(release.id)">灰度</button>
|
|
|
|
|
+ <button class="ghost" @click="openEditReleaseModal(release, 'PLUGIN')">编辑</button>
|
|
|
|
|
+ <button v-if="release.status === 'DRAFT'" class="ghost danger" @click="removeRelease(release.id)">删除</button>
|
|
|
</td>
|
|
</td>
|
|
|
</tr>
|
|
</tr>
|
|
|
</tbody>
|
|
</tbody>
|
|
|
</table>
|
|
</table>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+
|
|
|
|
|
+ <div v-if="grayReleaseId" class="sub-panel">
|
|
|
|
|
+ <h3>灰度信息配置</h3>
|
|
|
|
|
+ <div class="filters">
|
|
|
|
|
+ <label><span>分组</span>
|
|
|
|
|
+ <select v-model="filters.groupCode" @change="loadUsers">
|
|
|
|
|
+ <option value="">全部</option>
|
|
|
|
|
+ <option v-for="group in groups" :key="group.code" :value="group.code">{{ group.name }}</option>
|
|
|
|
|
+ </select>
|
|
|
|
|
+ </label>
|
|
|
|
|
+ <label><span>快选</span>
|
|
|
|
|
+ <select v-model="filters.quickSelectionCode" @change="loadUsers">
|
|
|
|
|
+ <option value="">全部</option>
|
|
|
|
|
+ <option v-for="item in quickSelections" :key="item.code" :value="item.code">{{ item.name }}</option>
|
|
|
|
|
+ </select>
|
|
|
|
|
+ </label>
|
|
|
|
|
+ <label class="grow"><span>搜索</span><input v-model="filters.keyword" @input="loadUsers" /></label>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <table class="table">
|
|
|
|
|
+ <thead><tr><th></th><th>ID</th><th>昵称</th><th>地区</th><th>分组</th></tr></thead>
|
|
|
|
|
+ <tbody>
|
|
|
|
|
+ <tr v-for="user in users" :key="user.id">
|
|
|
|
|
+ <td><input type="checkbox" :checked="selectedUsers.includes(user.id)" @change="toggleUser(user.id)" /></td>
|
|
|
|
|
+ <td>{{ user.id }}</td>
|
|
|
|
|
+ <td>{{ user.nickname }}</td>
|
|
|
|
|
+ <td>{{ user.region }}</td>
|
|
|
|
|
+ <td>{{ user.groupName }}</td>
|
|
|
|
|
+ </tr>
|
|
|
|
|
+ </tbody>
|
|
|
|
|
+ </table>
|
|
|
|
|
+ <div class="actions">
|
|
|
|
|
+ <button class="primary" @click="publishGray">确认灰度发布</button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
+ </div>
|
|
|
|
|
|
|
|
- <div v-if="grayReleaseId" class="sub-panel">
|
|
|
|
|
- <h3>灰度信息配置</h3>
|
|
|
|
|
- <div class="filters">
|
|
|
|
|
- <label><span>分组</span>
|
|
|
|
|
- <select v-model="filters.groupCode" @change="loadUsers">
|
|
|
|
|
- <option value="">全部</option>
|
|
|
|
|
- <option v-for="group in groups" :key="group.code" :value="group.code">{{ group.name }}</option>
|
|
|
|
|
- </select>
|
|
|
|
|
- </label>
|
|
|
|
|
- <label><span>快选</span>
|
|
|
|
|
- <select v-model="filters.quickSelectionCode" @change="loadUsers">
|
|
|
|
|
- <option value="">全部</option>
|
|
|
|
|
- <option v-for="item in quickSelections" :key="item.code" :value="item.code">{{ item.name }}</option>
|
|
|
|
|
- </select>
|
|
|
|
|
- </label>
|
|
|
|
|
- <label class="grow"><span>搜索</span><input v-model="filters.keyword" @input="loadUsers" /></label>
|
|
|
|
|
|
|
+ <div v-if="selectorOpen" class="modal-mask" @click.self="selectorOpen = false">
|
|
|
|
|
+ <div class="modal-card">
|
|
|
|
|
+ <div class="section-head">
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <p class="section-tag">应用选择</p>
|
|
|
|
|
+ <h3>App 列表</h3>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="actions">
|
|
|
|
|
+ <button class="secondary" @click="openCreateAppModal">创建应用</button>
|
|
|
|
|
+ <button class="secondary" @click="selectorOpen = false">关闭</button>
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
- <table class="table">
|
|
|
|
|
- <thead><tr><th></th><th>ID</th><th>昵称</th><th>地区</th><th>分组</th></tr></thead>
|
|
|
|
|
- <tbody>
|
|
|
|
|
- <tr v-for="user in users" :key="user.id">
|
|
|
|
|
- <td><input type="checkbox" :checked="selectedUsers.includes(user.id)" @change="toggleUser(user.id)" /></td>
|
|
|
|
|
- <td>{{ user.id }}</td>
|
|
|
|
|
- <td>{{ user.nickname }}</td>
|
|
|
|
|
- <td>{{ user.region }}</td>
|
|
|
|
|
- <td>{{ user.groupName }}</td>
|
|
|
|
|
- </tr>
|
|
|
|
|
- </tbody>
|
|
|
|
|
- </table>
|
|
|
|
|
- <div class="actions">
|
|
|
|
|
- <button class="primary" @click="publishGray">确认灰度发布</button>
|
|
|
|
|
|
|
+
|
|
|
|
|
+ <div class="list-grid">
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-for="item in applications"
|
|
|
|
|
+ :key="item.application.id"
|
|
|
|
|
+ class="list-card"
|
|
|
|
|
+ :data-active="selectedApp?.application.id === item.application.id"
|
|
|
|
|
+ >
|
|
|
|
|
+ <strong>{{ item.application.name }}</strong>
|
|
|
|
|
+ <span>{{ item.application.packageName }}</span>
|
|
|
|
|
+ <span>{{ item.application.pluginManagementEnabled ? '支持插件化' : '仅宿主' }}</span>
|
|
|
|
|
+ <div class="actions">
|
|
|
|
|
+ <button class="ghost" @click="selectApp(item)">进入</button>
|
|
|
|
|
+ <button class="ghost" @click="openEditAppModal(item)">编辑</button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div v-if="appModalOpen" class="modal-mask" @click.self="closeAppModal">
|
|
|
|
|
+ <div class="modal-card">
|
|
|
|
|
+ <div class="section-head">
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <p class="section-tag">应用设置</p>
|
|
|
|
|
+ <h3>{{ appModalMode === 'create' ? '创建应用' : '编辑应用' }}</h3>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <button class="secondary" @click="closeAppModal">关闭</button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <form class="form-grid" @submit.prevent="submitAppModal">
|
|
|
|
|
+ <label><span>名称</span><input v-model="appForm.name" required /></label>
|
|
|
|
|
+ <label><span>包名</span><input v-model="appForm.packageName" required /></label>
|
|
|
|
|
+ <label><span>插件包名前缀</span><input v-model="appForm.pluginPackageName" /></label>
|
|
|
|
|
+ <label class="full"><span>说明</span><input v-model="appForm.description" /></label>
|
|
|
|
|
+ <label class="toggle"><input type="checkbox" v-model="appForm.pluginManagementEnabled" />支持插件化</label>
|
|
|
|
|
+ <button class="primary">保存</button>
|
|
|
|
|
+ </form>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div v-if="releaseModalOpen" class="modal-mask" @click.self="closeReleaseModal">
|
|
|
|
|
+ <div class="modal-card">
|
|
|
|
|
+ <div class="section-head">
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <p class="section-tag">{{ releaseModalType === 'APP' ? 'APK' : '插件' }}</p>
|
|
|
|
|
+ <h3>{{ releaseModalMode === 'create' ? '新增安装包' : '编辑安装包' }}</h3>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <button class="secondary" @click="closeReleaseModal">关闭</button>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+
|
|
|
|
|
+ <template v-if="releaseModalType === 'APP'">
|
|
|
|
|
+ <form class="form-grid" @submit.prevent="submitAppRelease">
|
|
|
|
|
+ <label v-if="releaseModalMode === 'create'" class="full">
|
|
|
|
|
+ <span>安装包文件</span>
|
|
|
|
|
+ <input type="file" accept=".apk" @change="onAppFileSelected" />
|
|
|
|
|
+ </label>
|
|
|
|
|
+ <label><span>版本名</span><input v-model="appPackageForm.versionName" readonly /></label>
|
|
|
|
|
+ <label><span>版本码</span><input v-model.number="appPackageForm.versionCode" type="number" readonly /></label>
|
|
|
|
|
+ <label><span>文件名</span><input v-model="appPackageForm.uploadedFileName" readonly /></label>
|
|
|
|
|
+ <label class="full"><span>下载地址</span><input v-model="appPackageForm.downloadUrl" readonly /></label>
|
|
|
|
|
+ <label class="full"><span>标题</span><input v-model="appPackageForm.title" required /></label>
|
|
|
|
|
+ <label class="full"><span>更新说明</span><textarea v-model="appPackageForm.changelog" rows="3" /></label>
|
|
|
|
|
+ <button class="primary" :disabled="uploadingArtifact">{{ uploadingArtifact ? '上传中...' : '保存' }}</button>
|
|
|
|
|
+ </form>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ <template v-else>
|
|
|
|
|
+ <div v-if="releaseModalMode === 'create'" class="tab-row">
|
|
|
|
|
+ <button type="button" :class="pluginCreateMode === 'plugin' ? 'primary' : 'secondary'" @click="pluginCreateMode = 'plugin'">
|
|
|
|
|
+ 新建插件
|
|
|
|
|
+ </button>
|
|
|
|
|
+ <button type="button" :class="pluginCreateMode === 'package' ? 'primary' : 'secondary'" @click="pluginCreateMode = 'package'">
|
|
|
|
|
+ 上传插件包
|
|
|
|
|
+ </button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <form v-if="releaseModalMode === 'create' && pluginCreateMode === 'plugin'" class="form-grid" @submit.prevent="submitCreatePlugin">
|
|
|
|
|
+ <label><span>插件名</span><input v-model="pluginForm.name" required /></label>
|
|
|
|
|
+ <label><span>插件包名</span><input v-model="pluginForm.packageName" required /></label>
|
|
|
|
|
+ <label class="full"><span>入口 Activity</span><input v-model="pluginForm.entryActivity" /></label>
|
|
|
|
|
+ <label class="full"><span>说明</span><input v-model="pluginForm.description" /></label>
|
|
|
|
|
+ <button class="primary">保存插件</button>
|
|
|
|
|
+ </form>
|
|
|
|
|
+
|
|
|
|
|
+ <form v-else class="form-grid" @submit.prevent="submitPluginRelease">
|
|
|
|
|
+ <label v-if="releaseModalMode === 'create'">
|
|
|
|
|
+ <span>插件</span>
|
|
|
|
|
+ <select v-model="pluginReleaseForm.pluginId" required>
|
|
|
|
|
+ <option value="">请选择插件</option>
|
|
|
|
|
+ <option v-for="plugin in plugins" :key="plugin.id" :value="plugin.id">{{ plugin.name }}</option>
|
|
|
|
|
+ </select>
|
|
|
|
|
+ </label>
|
|
|
|
|
+ <label v-if="releaseModalMode === 'create'" class="full">
|
|
|
|
|
+ <span>安装包文件</span>
|
|
|
|
|
+ <input type="file" accept=".apk" @change="onPluginFileSelected" />
|
|
|
|
|
+ </label>
|
|
|
|
|
+ <label><span>版本名</span><input v-model="pluginReleaseForm.versionName" readonly /></label>
|
|
|
|
|
+ <label><span>版本码</span><input v-model.number="pluginReleaseForm.versionCode" type="number" readonly /></label>
|
|
|
|
|
+ <label><span>宿主最低版本码</span><input v-model.number="pluginReleaseForm.minHostVersionCode" type="number" /></label>
|
|
|
|
|
+ <label><span>宿主最低版本名</span><input v-model="pluginReleaseForm.minHostVersionName" /></label>
|
|
|
|
|
+ <label><span>文件名</span><input v-model="pluginReleaseForm.uploadedFileName" readonly /></label>
|
|
|
|
|
+ <label class="full"><span>下载地址</span><input v-model="pluginReleaseForm.downloadUrl" readonly /></label>
|
|
|
|
|
+ <label class="full"><span>标题</span><input v-model="pluginReleaseForm.title" required /></label>
|
|
|
|
|
+ <label class="full"><span>更新说明</span><textarea v-model="pluginReleaseForm.changelog" rows="3" /></label>
|
|
|
|
|
+ <button class="primary" :disabled="uploadingArtifact">{{ uploadingArtifact ? '上传中...' : '保存' }}</button>
|
|
|
|
|
+ </form>
|
|
|
|
|
+ </template>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</section>
|
|
</section>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
<script setup lang="ts">
|
|
|
-import { onMounted, reactive, ref } from 'vue'
|
|
|
|
|
|
|
+import { computed, onMounted, reactive, ref } from 'vue'
|
|
|
import { api, type ApplicationDetail, type AudienceGroup, type AudienceUser, type PluginConfig, type QuickSelection, type ReleaseRecord } from '../api/client'
|
|
import { api, type ApplicationDetail, type AudienceGroup, type AudienceUser, type PluginConfig, type QuickSelection, type ReleaseRecord } from '../api/client'
|
|
|
|
|
|
|
|
|
|
+type ReleaseModalType = 'APP' | 'PLUGIN'
|
|
|
|
|
+type ModalMode = 'create' | 'edit'
|
|
|
|
|
+type PluginReleaseRow = ReleaseRecord & { pluginName: string }
|
|
|
|
|
+
|
|
|
|
|
+const STORAGE_KEY = 'ops-selected-app-id'
|
|
|
|
|
+
|
|
|
const applications = ref<ApplicationDetail[]>([])
|
|
const applications = ref<ApplicationDetail[]>([])
|
|
|
const selectedApp = ref<ApplicationDetail | null>(null)
|
|
const selectedApp = ref<ApplicationDetail | null>(null)
|
|
|
-const selectedPlugin = ref<PluginConfig | null>(null)
|
|
|
|
|
const appPackages = ref<ReleaseRecord[]>([])
|
|
const appPackages = ref<ReleaseRecord[]>([])
|
|
|
-const pluginPackages = ref<ReleaseRecord[]>([])
|
|
|
|
|
const plugins = ref<PluginConfig[]>([])
|
|
const plugins = ref<PluginConfig[]>([])
|
|
|
|
|
+const pluginReleaseRows = ref<PluginReleaseRow[]>([])
|
|
|
const users = ref<AudienceUser[]>([])
|
|
const users = ref<AudienceUser[]>([])
|
|
|
const groups = ref<AudienceGroup[]>([])
|
|
const groups = ref<AudienceGroup[]>([])
|
|
|
const quickSelections = ref<QuickSelection[]>([])
|
|
const quickSelections = ref<QuickSelection[]>([])
|
|
|
const selectedUsers = ref<string[]>([])
|
|
const selectedUsers = ref<string[]>([])
|
|
|
const grayReleaseId = ref('')
|
|
const grayReleaseId = ref('')
|
|
|
-const showCreateApp = ref(false)
|
|
|
|
|
|
|
+const uploadingArtifact = ref(false)
|
|
|
|
|
+const selectorOpen = ref(false)
|
|
|
|
|
+const appModalOpen = ref(false)
|
|
|
|
|
+const releaseModalOpen = ref(false)
|
|
|
|
|
+const appModalMode = ref<ModalMode>('create')
|
|
|
|
|
+const releaseModalMode = ref<ModalMode>('create')
|
|
|
|
|
+const releaseModalType = ref<ReleaseModalType>('APP')
|
|
|
|
|
+const pluginCreateMode = ref<'plugin' | 'package'>('package')
|
|
|
|
|
+const editingAppId = ref('')
|
|
|
|
|
+const editingReleaseId = ref('')
|
|
|
const tab = ref<'packages' | 'plugins'>('packages')
|
|
const tab = ref<'packages' | 'plugins'>('packages')
|
|
|
|
|
|
|
|
const filters = reactive({ keyword: '', groupCode: '', quickSelectionCode: '' })
|
|
const filters = reactive({ keyword: '', groupCode: '', quickSelectionCode: '' })
|
|
|
const appForm = reactive({ name: '', packageName: '', pluginPackageName: '', description: '', pluginManagementEnabled: true, businessModules: ['IM', 'PUSH', 'VERSION'] })
|
|
const appForm = reactive({ name: '', packageName: '', pluginPackageName: '', description: '', pluginManagementEnabled: true, businessModules: ['IM', 'PUSH', 'VERSION'] })
|
|
|
-const appEdit = reactive({ name: '', packageName: '', pluginPackageName: '', description: '', pluginManagementEnabled: true, businessModules: ['IM', 'PUSH', 'VERSION'] })
|
|
|
|
|
const pluginForm = reactive({ name: '', packageName: '', entryActivity: '', description: '' })
|
|
const pluginForm = reactive({ name: '', packageName: '', entryActivity: '', description: '' })
|
|
|
const appPackageForm = reactive({ packageName: '', versionCode: 1, versionName: '', title: '发现新版本', changelog: '', downloadUrl: '', uploadedFileName: '', entryActivity: '', forceUpdate: false })
|
|
const appPackageForm = reactive({ packageName: '', versionCode: 1, versionName: '', title: '发现新版本', changelog: '', downloadUrl: '', uploadedFileName: '', entryActivity: '', forceUpdate: false })
|
|
|
-const pluginPackageForm = reactive({ versionCode: 1, versionName: '', title: '插件更新', changelog: '', downloadUrl: '', uploadedFileName: '', entryActivity: '', minHostVersionCode: 0, minHostVersionName: '' })
|
|
|
|
|
|
|
+const pluginReleaseForm = reactive({ pluginId: '', versionCode: 1, versionName: '', title: '插件更新', changelog: '', downloadUrl: '', uploadedFileName: '', entryActivity: '', minHostVersionCode: 0, minHostVersionName: '' })
|
|
|
|
|
+
|
|
|
|
|
+const pluginOptions = computed(() => plugins.value.map(item => ({ label: item.name, value: item.id })))
|
|
|
|
|
+
|
|
|
|
|
+function resetAppForm() {
|
|
|
|
|
+ Object.assign(appForm, {
|
|
|
|
|
+ name: '',
|
|
|
|
|
+ packageName: '',
|
|
|
|
|
+ pluginPackageName: '',
|
|
|
|
|
+ description: '',
|
|
|
|
|
+ pluginManagementEnabled: true,
|
|
|
|
|
+ businessModules: ['IM', 'PUSH', 'VERSION'],
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function resetAppPackageForm() {
|
|
|
|
|
+ Object.assign(appPackageForm, {
|
|
|
|
|
+ packageName: selectedApp.value?.application.packageName ?? '',
|
|
|
|
|
+ versionCode: 1,
|
|
|
|
|
+ versionName: '',
|
|
|
|
|
+ title: '有新版本待更新',
|
|
|
|
|
+ changelog: '',
|
|
|
|
|
+ downloadUrl: '',
|
|
|
|
|
+ uploadedFileName: '',
|
|
|
|
|
+ entryActivity: '',
|
|
|
|
|
+ forceUpdate: false,
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function resetPluginForm() {
|
|
|
|
|
+ Object.assign(pluginForm, {
|
|
|
|
|
+ name: '',
|
|
|
|
|
+ packageName: '',
|
|
|
|
|
+ entryActivity: '',
|
|
|
|
|
+ description: '',
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function resetPluginReleaseForm() {
|
|
|
|
|
+ Object.assign(pluginReleaseForm, {
|
|
|
|
|
+ pluginId: pluginOptions.value[0]?.value ?? '',
|
|
|
|
|
+ versionCode: 1,
|
|
|
|
|
+ versionName: '',
|
|
|
|
|
+ title: '有新版本待更新',
|
|
|
|
|
+ changelog: '',
|
|
|
|
|
+ downloadUrl: '',
|
|
|
|
|
+ uploadedFileName: '',
|
|
|
|
|
+ entryActivity: '',
|
|
|
|
|
+ minHostVersionCode: 0,
|
|
|
|
|
+ minHostVersionName: '',
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function persistSelectedApp(appId: string) {
|
|
|
|
|
+ localStorage.setItem(STORAGE_KEY, appId)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function loadStoredAppId() {
|
|
|
|
|
+ return localStorage.getItem(STORAGE_KEY)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function openSelector() {
|
|
|
|
|
+ selectorOpen.value = true
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function openCreateAppModal() {
|
|
|
|
|
+ appModalMode.value = 'create'
|
|
|
|
|
+ editingAppId.value = ''
|
|
|
|
|
+ resetAppForm()
|
|
|
|
|
+ appModalOpen.value = true
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function openEditAppModal(item: ApplicationDetail) {
|
|
|
|
|
+ appModalMode.value = 'edit'
|
|
|
|
|
+ editingAppId.value = item.application.id
|
|
|
|
|
+ Object.assign(appForm, item.application)
|
|
|
|
|
+ appModalOpen.value = true
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function closeAppModal() {
|
|
|
|
|
+ appModalOpen.value = false
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function openCreateModal() {
|
|
|
|
|
+ releaseModalMode.value = 'create'
|
|
|
|
|
+ editingReleaseId.value = ''
|
|
|
|
|
+ releaseModalType.value = tab.value === 'packages' ? 'APP' : 'PLUGIN'
|
|
|
|
|
+ if (releaseModalType.value === 'APP') {
|
|
|
|
|
+ resetAppPackageForm()
|
|
|
|
|
+ } else {
|
|
|
|
|
+ pluginCreateMode.value = plugins.value.length > 0 ? 'package' : 'plugin'
|
|
|
|
|
+ resetPluginForm()
|
|
|
|
|
+ resetPluginReleaseForm()
|
|
|
|
|
+ }
|
|
|
|
|
+ releaseModalOpen.value = true
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function openEditReleaseModal(release: ReleaseRecord, type: ReleaseModalType) {
|
|
|
|
|
+ releaseModalMode.value = 'edit'
|
|
|
|
|
+ releaseModalType.value = type
|
|
|
|
|
+ editingReleaseId.value = release.id
|
|
|
|
|
+ if (type === 'APP') {
|
|
|
|
|
+ Object.assign(appPackageForm, {
|
|
|
|
|
+ packageName: release.packageName,
|
|
|
|
|
+ versionCode: release.versionCode,
|
|
|
|
|
+ versionName: release.versionName,
|
|
|
|
|
+ title: release.title,
|
|
|
|
|
+ changelog: release.changelog,
|
|
|
|
|
+ downloadUrl: release.downloadUrl,
|
|
|
|
|
+ uploadedFileName: release.uploadedFileName,
|
|
|
|
|
+ entryActivity: release.entryActivity ?? '',
|
|
|
|
|
+ forceUpdate: release.forceUpdate ?? false,
|
|
|
|
|
+ })
|
|
|
|
|
+ } else {
|
|
|
|
|
+ Object.assign(pluginReleaseForm, {
|
|
|
|
|
+ pluginId: release.pluginId ?? '',
|
|
|
|
|
+ versionCode: release.versionCode,
|
|
|
|
|
+ versionName: release.versionName,
|
|
|
|
|
+ title: release.title,
|
|
|
|
|
+ changelog: release.changelog,
|
|
|
|
|
+ downloadUrl: release.downloadUrl,
|
|
|
|
|
+ uploadedFileName: release.uploadedFileName,
|
|
|
|
|
+ entryActivity: release.entryActivity ?? '',
|
|
|
|
|
+ minHostVersionCode: release.minHostVersionCode ?? 0,
|
|
|
|
|
+ minHostVersionName: release.minHostVersionName ?? '',
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ releaseModalOpen.value = true
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function closeReleaseModal() {
|
|
|
|
|
+ releaseModalOpen.value = false
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
async function loadAll() {
|
|
async function loadAll() {
|
|
|
applications.value = await api.listApplications()
|
|
applications.value = await api.listApplications()
|
|
|
const [groupList, quickList] = await Promise.all([api.listAudienceGroups(), api.listQuickSelections()])
|
|
const [groupList, quickList] = await Promise.all([api.listAudienceGroups(), api.listQuickSelections()])
|
|
|
groups.value = groupList
|
|
groups.value = groupList
|
|
|
quickSelections.value = quickList
|
|
quickSelections.value = quickList
|
|
|
- if (applications.value.length > 0 && !selectedApp.value) {
|
|
|
|
|
- await selectApp(applications.value[0])
|
|
|
|
|
|
|
+
|
|
|
|
|
+ const storedAppId = loadStoredAppId()
|
|
|
|
|
+ const matched = applications.value.find(item => item.application.id === storedAppId) ?? null
|
|
|
|
|
+ if (matched) {
|
|
|
|
|
+ await selectApp(matched, false)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ selectorOpen.value = true
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function loadSelectedApp() {
|
|
|
|
|
+ if (!selectedApp.value) {
|
|
|
|
|
+ selectorOpen.value = true
|
|
|
|
|
+ return
|
|
|
}
|
|
}
|
|
|
|
|
+ const latest = await api.listApplications()
|
|
|
|
|
+ applications.value = latest
|
|
|
|
|
+ const matched = latest.find(item => item.application.id === selectedApp.value?.application.id)
|
|
|
|
|
+ if (matched) {
|
|
|
|
|
+ await selectApp(matched, false)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function refreshPluginReleaseRows() {
|
|
|
|
|
+ const packageGroups = await Promise.all(
|
|
|
|
|
+ plugins.value.map(async plugin => {
|
|
|
|
|
+ const releases = await api.listPluginPackages(plugin.id)
|
|
|
|
|
+ return releases.map(release => ({ ...release, pluginName: plugin.name }))
|
|
|
|
|
+ }),
|
|
|
|
|
+ )
|
|
|
|
|
+ pluginReleaseRows.value = packageGroups.flat().sort((a, b) => b.versionCode - a.versionCode)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-async function selectApp(item: ApplicationDetail) {
|
|
|
|
|
|
|
+async function selectApp(item: ApplicationDetail, closeModal = true) {
|
|
|
selectedApp.value = item
|
|
selectedApp.value = item
|
|
|
- selectedPlugin.value = null
|
|
|
|
|
|
|
+ selectedUsers.value = []
|
|
|
|
|
+ grayReleaseId.value = ''
|
|
|
tab.value = 'packages'
|
|
tab.value = 'packages'
|
|
|
- Object.assign(appEdit, item.application)
|
|
|
|
|
- appPackageForm.packageName = item.application.packageName
|
|
|
|
|
|
|
+ persistSelectedApp(item.application.id)
|
|
|
appPackages.value = await api.listAppPackages(item.application.id)
|
|
appPackages.value = await api.listAppPackages(item.application.id)
|
|
|
plugins.value = await api.listPlugins(item.application.id)
|
|
plugins.value = await api.listPlugins(item.application.id)
|
|
|
|
|
+ await refreshPluginReleaseRows()
|
|
|
|
|
+ if (closeModal) {
|
|
|
|
|
+ selectorOpen.value = false
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-async function createApp() {
|
|
|
|
|
- await api.createApplication(appForm)
|
|
|
|
|
- showCreateApp.value = false
|
|
|
|
|
|
|
+async function submitAppModal() {
|
|
|
|
|
+ if (appModalMode.value === 'create') {
|
|
|
|
|
+ await api.createApplication(appForm)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ await api.updateApplication(editingAppId.value, appForm)
|
|
|
|
|
+ }
|
|
|
|
|
+ closeAppModal()
|
|
|
await loadAll()
|
|
await loadAll()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-async function saveApp() {
|
|
|
|
|
|
|
+async function submitAppRelease() {
|
|
|
if (!selectedApp.value) return
|
|
if (!selectedApp.value) return
|
|
|
- await api.updateApplication(selectedApp.value.application.id, appEdit)
|
|
|
|
|
- await loadAll()
|
|
|
|
|
|
|
+ if (!appPackageForm.downloadUrl) {
|
|
|
|
|
+ window.alert('请先选择并上传 APK 文件')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ if (releaseModalMode.value === 'create') {
|
|
|
|
|
+ await api.uploadAppPackage(selectedApp.value.application.id, appPackageForm)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ await api.updatePackage(editingReleaseId.value, {
|
|
|
|
|
+ versionCode: appPackageForm.versionCode,
|
|
|
|
|
+ versionName: appPackageForm.versionName,
|
|
|
|
|
+ title: appPackageForm.title,
|
|
|
|
|
+ changelog: appPackageForm.changelog,
|
|
|
|
|
+ downloadUrl: appPackageForm.downloadUrl,
|
|
|
|
|
+ uploadedFileName: appPackageForm.uploadedFileName,
|
|
|
|
|
+ entryActivity: appPackageForm.entryActivity,
|
|
|
|
|
+ forceUpdate: appPackageForm.forceUpdate,
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ closeReleaseModal()
|
|
|
|
|
+ appPackages.value = await api.listAppPackages(selectedApp.value.application.id)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-async function uploadAppPackage() {
|
|
|
|
|
|
|
+async function submitPluginRelease() {
|
|
|
if (!selectedApp.value) return
|
|
if (!selectedApp.value) return
|
|
|
- await api.uploadAppPackage(selectedApp.value.application.id, appPackageForm)
|
|
|
|
|
- appPackages.value = await api.listAppPackages(selectedApp.value.application.id)
|
|
|
|
|
|
|
+ if (!pluginReleaseForm.downloadUrl) {
|
|
|
|
|
+ window.alert('请先选择并上传 APK 文件')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ if (releaseModalMode.value === 'create') {
|
|
|
|
|
+ await api.uploadPluginPackage(pluginReleaseForm.pluginId, pluginReleaseForm)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ await api.updatePackage(editingReleaseId.value, {
|
|
|
|
|
+ packageName: '',
|
|
|
|
|
+ versionCode: pluginReleaseForm.versionCode,
|
|
|
|
|
+ versionName: pluginReleaseForm.versionName,
|
|
|
|
|
+ title: pluginReleaseForm.title,
|
|
|
|
|
+ changelog: pluginReleaseForm.changelog,
|
|
|
|
|
+ downloadUrl: pluginReleaseForm.downloadUrl,
|
|
|
|
|
+ uploadedFileName: pluginReleaseForm.uploadedFileName,
|
|
|
|
|
+ entryActivity: pluginReleaseForm.entryActivity,
|
|
|
|
|
+ minHostVersionCode: pluginReleaseForm.minHostVersionCode,
|
|
|
|
|
+ minHostVersionName: pluginReleaseForm.minHostVersionName,
|
|
|
|
|
+ forceUpdate: false,
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ closeReleaseModal()
|
|
|
|
|
+ plugins.value = await api.listPlugins(selectedApp.value.application.id)
|
|
|
|
|
+ await refreshPluginReleaseRows()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-async function createPlugin() {
|
|
|
|
|
|
|
+async function submitCreatePlugin() {
|
|
|
if (!selectedApp.value) return
|
|
if (!selectedApp.value) return
|
|
|
await api.createPlugin(selectedApp.value.application.id, pluginForm)
|
|
await api.createPlugin(selectedApp.value.application.id, pluginForm)
|
|
|
plugins.value = await api.listPlugins(selectedApp.value.application.id)
|
|
plugins.value = await api.listPlugins(selectedApp.value.application.id)
|
|
|
|
|
+ pluginCreateMode.value = 'package'
|
|
|
|
|
+ resetPluginForm()
|
|
|
|
|
+ resetPluginReleaseForm()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-async function selectPlugin(plugin: PluginConfig) {
|
|
|
|
|
- selectedPlugin.value = plugin
|
|
|
|
|
- pluginPackages.value = await api.listPluginPackages(plugin.id)
|
|
|
|
|
|
|
+async function onAppFileSelected(event: Event) {
|
|
|
|
|
+ if (!selectedApp.value) return
|
|
|
|
|
+ const input = event.target as HTMLInputElement
|
|
|
|
|
+ const file = input.files?.[0]
|
|
|
|
|
+ if (!file) return
|
|
|
|
|
+ try {
|
|
|
|
|
+ uploadingArtifact.value = true
|
|
|
|
|
+ const artifact = await api.uploadAppArtifact(selectedApp.value.application.id, file)
|
|
|
|
|
+ appPackageForm.packageName = artifact.packageName
|
|
|
|
|
+ appPackageForm.versionCode = artifact.versionCode
|
|
|
|
|
+ appPackageForm.versionName = artifact.versionName
|
|
|
|
|
+ appPackageForm.uploadedFileName = artifact.uploadedFileName
|
|
|
|
|
+ appPackageForm.downloadUrl = artifact.downloadUrl
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ window.alert(error instanceof Error ? error.message : 'APK 上传失败')
|
|
|
|
|
+ input.value = ''
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ uploadingArtifact.value = false
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-async function uploadPluginPackage() {
|
|
|
|
|
- if (!selectedPlugin.value) return
|
|
|
|
|
- await api.uploadPluginPackage(selectedPlugin.value.id, pluginPackageForm)
|
|
|
|
|
- pluginPackages.value = await api.listPluginPackages(selectedPlugin.value.id)
|
|
|
|
|
|
|
+async function onPluginFileSelected(event: Event) {
|
|
|
|
|
+ const input = event.target as HTMLInputElement
|
|
|
|
|
+ const file = input.files?.[0]
|
|
|
|
|
+ if (!file) return
|
|
|
|
|
+ const pluginId = pluginReleaseForm.pluginId || (releaseModalMode.value === 'edit' ? pluginReleaseForm.pluginId : '')
|
|
|
|
|
+ if (!pluginId) {
|
|
|
|
|
+ window.alert('请先选择插件,再上传安装包')
|
|
|
|
|
+ input.value = ''
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ uploadingArtifact.value = true
|
|
|
|
|
+ const artifact = await api.uploadPluginArtifact(pluginId, file)
|
|
|
|
|
+ pluginReleaseForm.versionCode = artifact.versionCode
|
|
|
|
|
+ pluginReleaseForm.versionName = artifact.versionName
|
|
|
|
|
+ pluginReleaseForm.uploadedFileName = artifact.uploadedFileName
|
|
|
|
|
+ pluginReleaseForm.downloadUrl = artifact.downloadUrl
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ window.alert(error instanceof Error ? error.message : '插件包上传失败')
|
|
|
|
|
+ input.value = ''
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ uploadingArtifact.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function downloadFile(url: string) {
|
|
|
|
|
+ window.open(url, '_blank', 'noopener')
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function publishFull(releaseId: string) {
|
|
async function publishFull(releaseId: string) {
|
|
|
await api.publishPackage(releaseId, { grayPublish: false })
|
|
await api.publishPackage(releaseId, { grayPublish: false })
|
|
|
- if (selectedApp.value) appPackages.value = await api.listAppPackages(selectedApp.value.application.id)
|
|
|
|
|
- if (selectedPlugin.value) pluginPackages.value = await api.listPluginPackages(selectedPlugin.value.id)
|
|
|
|
|
|
|
+ if (!selectedApp.value) return
|
|
|
|
|
+ if (tab.value === 'packages') {
|
|
|
|
|
+ appPackages.value = await api.listAppPackages(selectedApp.value.application.id)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ await refreshPluginReleaseRows()
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function prepareGray(releaseId: string) {
|
|
function prepareGray(releaseId: string) {
|
|
@@ -291,6 +622,22 @@ async function publishGray() {
|
|
|
userIds: selectedUsers.value,
|
|
userIds: selectedUsers.value,
|
|
|
})
|
|
})
|
|
|
grayReleaseId.value = ''
|
|
grayReleaseId.value = ''
|
|
|
|
|
+ if (tab.value === 'packages' && selectedApp.value) {
|
|
|
|
|
+ appPackages.value = await api.listAppPackages(selectedApp.value.application.id)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ await refreshPluginReleaseRows()
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function removeRelease(releaseId: string) {
|
|
|
|
|
+ const confirmed = window.confirm('删除后不可恢复,确认删除当前安装包吗?')
|
|
|
|
|
+ if (!confirmed || !selectedApp.value) return
|
|
|
|
|
+ await api.deletePackage(releaseId)
|
|
|
|
|
+ if (tab.value === 'packages') {
|
|
|
|
|
+ appPackages.value = await api.listAppPackages(selectedApp.value.application.id)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ await refreshPluginReleaseRows()
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
onMounted(() => { void loadAll() })
|
|
onMounted(() => { void loadAll() })
|