You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
94 lines
4.2 KiB
94 lines
4.2 KiB
<template>
|
|
<div>
|
|
<!-- 页头 + 应用选择:上下两块配置共用一个作用域 -->
|
|
<div class="flex flex-wrap items-start justify-between gap-3 mb-4">
|
|
<div>
|
|
<h2 class="text-lg font-semibold text-slate-800">版本与分发</h2>
|
|
<p class="text-xs text-slate-400 mt-1 max-w-3xl">
|
|
发一次版要配的东西都在这一页:上半区是<b>整个应用一套</b>的版本号 / 强更 / 更新日志 / 默认下载地址,
|
|
下半区是<b>因渠道而异</b>的各商店链接与上架状态。两者存在不同的表里,
|
|
<b>提交也各管各的</b>——原因见下方说明。
|
|
</p>
|
|
</div>
|
|
<div class="flex items-center gap-2">
|
|
<span class="text-sm text-slate-500">应用</span>
|
|
<select v-model="scopeApp" class="select select-bordered select-sm min-w-40">
|
|
<option value="">全局默认(所有应用兜底)</option>
|
|
<option v-for="a in scopeApps" :key="a" :value="a">{{ a }}</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="!scopeApp" class="scope-tip">
|
|
当前是<b>全局默认</b>作用域:这里配的对所有应用生效,某个应用单独配了则以应用的为准。
|
|
<b>上传安装包必须先选具体应用</b>——安装包按应用分目录存放。
|
|
</div>
|
|
|
|
<section class="panel p-5 mb-4">
|
|
<AppReleasePanel :app-name="scopeApp" @uploaded="channelPanel?.reload()" />
|
|
</section>
|
|
|
|
<section class="panel p-5">
|
|
<ChannelAppPanel ref="channelPanel" :app-name="scopeApp" />
|
|
</section>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, onMounted } from 'vue'
|
|
import { useApi } from '~/composables/useApi'
|
|
import AppReleasePanel from '~/components/AppReleasePanel.vue'
|
|
import ChannelAppPanel from '~/components/ChannelAppPanel.vue'
|
|
|
|
definePageMeta({ title: '版本与分发' })
|
|
|
|
/*
|
|
「版本控制」与「渠道分发」2026-09-23 合并成本页。
|
|
|
|
⚠️ **合的是入口,不是表。** app_release(一应用一行) 与 channel_app((应用,渠道) 一行)
|
|
维度不同,2026-08-31 正是从一张表拆开的:版本号和游客开关本来就整个应用一套,
|
|
按渠道各存一份意味着同一个开关有 8 处能改而客户端只读一处,出过
|
|
「后台明明开了游客登录、App 就是不显示」的事故。合回去等于把那个坑重新挖开。
|
|
|
|
⚠️ **两块各自提交,不要做成一个大表单整包保存。** app_release 那一行还被
|
|
「游客显隐控制」页共用,后端是「读整行 → 只覆盖本组字段 → 整行写回」;
|
|
做成整行提交的话,在本页点一次保存就会把游客登录配置清空。
|
|
*/
|
|
|
|
const { webApi, consoleApi } = useApi()
|
|
|
|
const scopeApp = ref('')
|
|
// 上面的面板传完安装包后会改写官方渠道地址,这里拿到下方面板的句柄让它重拉。
|
|
const channelPanel = ref<{ reload: () => Promise<void> } | null>(null)
|
|
const scopeApps = ref<string[]>([])
|
|
|
|
// 应用名来源:应用中心的应用表 + 部署注册表(兜底老数据);无权读这两张表时改走 api_getmyapps。
|
|
async function loadScopeApps() {
|
|
const names = new Set<string>()
|
|
const [products, deploys] = await Promise.all([
|
|
consoleApi<Array<{ name: string }>>('products/list', {}).catch(() => [] as Array<{ name: string }>),
|
|
consoleApi<Array<{ app_name?: string; name: string }>>('apps/list', {}).catch(() => [] as Array<{ app_name?: string; name: string }>),
|
|
])
|
|
;(products ?? []).forEach((p) => p.name && names.add(p.name))
|
|
;(deploys ?? []).forEach((d) => (d.app_name || d.name) && names.add(d.app_name || d.name))
|
|
if (!names.size) {
|
|
try {
|
|
const d = await webApi<{ apps: Array<{ name: string; app_name?: string }> }>('getmyapps', {})
|
|
;(d?.apps ?? []).forEach((a) => {
|
|
const n = a.app_name || a.name
|
|
if (n) names.add(n)
|
|
})
|
|
} catch {
|
|
// 拿不到就只剩「全局默认」,不阻断页面
|
|
}
|
|
}
|
|
scopeApps.value = [...names].sort()
|
|
}
|
|
|
|
onMounted(loadScopeApps)
|
|
</script>
|
|
|
|
<style scoped>
|
|
.panel { background: #fff; border: 1px solid #e2e8f0; border-radius: 14px; }
|
|
.scope-tip { background: #f8fafc; border: 1px solid #e2e8f0; color: #475569; font-size: 12px; padding: 8px 12px; border-radius: 10px; margin-bottom: 16px; }
|
|
</style>
|
|
|