diff --git a/apps/admin/app/components/AppReleasePanel.vue b/apps/admin/app/components/AppReleasePanel.vue
new file mode 100644
index 00000000..f23e9b55
--- /dev/null
+++ b/apps/admin/app/components/AppReleasePanel.vue
@@ -0,0 +1,352 @@
+
+
+
+
发版配置
+
+ 线上版本、强制更新、送审版本与更新日志。Android / iOS 各一套版本号(iOS 审核慢,两端线上版本经常不同步),
+ 其余整个应用一套、不分渠道;下面的「渠道分发」只覆盖各商店各自不同的那部分。
+
+
+
+
+
+
+
+ 线上版本(按平台)
+
+
+
Android
+
当前线上版本
+
+
最低支持版本(可留空)
+
+
+
+
iOS
+
当前线上版本
+
+
最低支持版本(可留空)
+
+
+
+
+ 客户端低于本平台「线上版本」时提示推荐更新(可点「稍后提醒」,同一版本 24 小时内不再弹)。
+ 填客户端上报的净版本号,不含构建号(1.0.1 而不是 1.0.1+5)。
+ iOS 那一栏留空 = iOS 不提示任何更新,不会拿 Android 的版本去判 iOS。
+
+
+
送审版本号
+
+
客户端上报的版本正好等于它时,会隐藏未过审的功能。
+
+
+
+
+ 强制更新
+
+
+ 开启后,低于本平台「最低支持版本」的客户端会弹出不可关闭的升级框,无法继续使用。
+ 最低支持版本留空 = 线上版本,即所有低版本都强更;填了则只强更低于它的,介于两者之间的仍是推荐更新。
+ 强更不受渠道上架状态限制:渠道商店里还没有新包时,下载地址会自动换成官方渠道的包。
+
+ ⚠ {{ versionWarn }}
+
+
+
+
+
+
+
+
+
+
判定预览
+
输入一个客户端版本号,看它会被判成什么。与服务端 comm.CompareVersion 同一套逐段整数比较。
+
+
+
+
+
+
+
+ 有更新可用
+ {{ probeResult.need ? '是' : '否' }}
+
+
+ 必须更新(阻断使用)
+ {{ probeResult.force ? '是' : '否' }}
+
+
+ 处于审核期
+ {{ probeResult.reviewing ? '是' : '否' }}
+
+
+
+ 客户端没上报版本号时三项一律为否——拿不到版本就判强更会把所有老客户端挡在门外。
+
+
+ 这里不含两条与渠道有关的规则:渠道未上架(审核中/已下架)时不推送推荐更新;
+ 算不出任何下载地址时两种更新都不提示。
+
+
+
+
+
+
+
+
+
diff --git a/apps/admin/app/components/ChannelAppPanel.vue b/apps/admin/app/components/ChannelAppPanel.vue
new file mode 100644
index 00000000..cc6edc6e
--- /dev/null
+++ b/apps/admin/app/components/ChannelAppPanel.vue
@@ -0,0 +1,422 @@
+
+
+
+
+
渠道分发
+
+ 因渠道而异的东西:本渠道下载地址、上架状态、支付渠道、是否允许跳过设备绑定。
+ 下载地址留空就回退上面的「默认下载地址」,所以只有需要直达某家商店时才填。
+ 支付渠道在这里控制「开放哪几种支付入口」,商户凭据在应用的业务功能配置里填。
+
+
+
+
+
+
+
+
+
⚠
+
+
以下支付方式已在渠道里开放,但商户凭据还没配:
+
+ {{ w.label }}({{ w.channels.join('、') }})
+
+
+
+
+
+
+
+
+
+ {{ g.icon }} {{ g.label }}
+ {{ g.items.length }} / {{ g.channels.length }} 已配置
+
+
+
+
+
+
{{ channelName(c.channel) }}
+
渠道号 {{ c.channel }}
+
+
{{ statusLabel(c.status) }}
+
+
+
+ 可跳过绑定
+ 已停用
+
+
+
+ {{ payLabel(p) }}{{ payMissing(p) ? ' ⚠' : '' }}
+
+
+
+ {{ c.address || '未填本渠道地址 · 用应用默认' }}
+
+
+
+
+
+
{{ channelName(ch) }}
+
未配置 · 点击添加
+
+
+
+
+
+
+
+
+
+
{{ isEditing ? '编辑渠道' : '添加渠道' }}
+ {{ scopeApp || '全局默认' }}
+
+
+
+
+
+ 渠道
+
+
+ 分发渠道 *
+
+
+
+ 上架状态
+
+
+
+
+ 描述
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/admin/app/pages/appdist.vue b/apps/admin/app/pages/appdist.vue
new file mode 100644
index 00000000..aa2d4189
--- /dev/null
+++ b/apps/admin/app/pages/appdist.vue
@@ -0,0 +1,94 @@
+
+
+
+
+
+
版本与分发
+
+ 发一次版要配的东西都在这一页:上半区是整个应用一套的版本号 / 强更 / 更新日志 / 默认下载地址,
+ 下半区是因渠道而异的各商店链接与上架状态。两者存在不同的表里,
+ 提交也各管各的——原因见下方说明。
+
+
+
+ 应用
+
+
+
+
+
+ 当前是全局默认作用域:这里配的对所有应用生效,某个应用单独配了则以应用的为准。
+ 上传安装包必须先选具体应用——安装包按应用分目录存放。
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/admin/app/pages/appversion.vue b/apps/admin/app/pages/appversion.vue
index 0752c276..9c69f1af 100644
--- a/apps/admin/app/pages/appversion.vue
+++ b/apps/admin/app/pages/appversion.vue
@@ -1,254 +1,12 @@
-
-
-
-
-
版本控制
-
- 按应用配置线上版本、强制更新与送审版本。整个应用一套,不分渠道——各商店的下载地址可在「渠道分发」里单独覆盖。
-
-
-
- 应用
-
-
-
-
-
- 当前是全局默认作用域:这里配的对所有应用生效;某个应用单独配了,则以应用的为准。
-
-
-
-
-
-
- 线上版本
-
-
-
当前线上版本
-
-
高于客户端本机版本时提示「有新版本」。填客户端上报的净版本号,不含构建号(1.0.1 而不是 1.0.1+5)。
-
-
-
送审版本号
-
-
客户端上报的版本正好等于它时,会隐藏未过审的功能。
-
-
-
-
-
- 强制更新
-
-
-
最低支持版本(必填)
-
-
- 低于这个版本的客户端会弹出不可关闭的升级框,无法继续使用。
- 不填等于开了强更却挡不住任何人。
-
-
- ⚠ {{ versionWarn }}
-
-
-
- 下载与说明
- 默认下载地址
-
-
- 各商店链接不一样,这里填的是兜底值;华为/小米/App Store 等请在
- 渠道分发
- 里给对应渠道填「本渠道下载地址」,渠道填了就以渠道的为准。
-
-
- 更新日志
-
-
-
-
-
-
-
-
-
-
判定预览
-
输入一个客户端版本号,看它会被判成什么。与服务端 comm.CompareVersion 同一套逐段整数比较。
-
-
-
- 有更新可用
- {{ probeResult.need ? '是' : '否' }}
-
-
- 必须更新(阻断使用)
- {{ probeResult.force ? '是' : '否' }}
-
-
- 处于审核期
- {{ probeResult.reviewing ? '是' : '否' }}
-
-
-
- 客户端没上报版本号时三项一律为否——拿不到版本就判强更会把所有老客户端挡在门外。
-
-
-
-
+ 正在跳转到「版本与分发」…
-
-
diff --git a/apps/admin/app/pages/channels.vue b/apps/admin/app/pages/channels.vue
index 11ebb8dd..9bdadf8a 100644
--- a/apps/admin/app/pages/channels.vue
+++ b/apps/admin/app/pages/channels.vue
@@ -1,443 +1,12 @@
-
-
-
-
-
渠道分发
-
- 管理各应用在各分发渠道上因渠道而异的东西:本渠道下载地址、上架状态、支付渠道、是否允许跳过设备绑定。
- 支付渠道在这里控制「开放哪几种支付入口」,商户凭据在应用的业务功能配置里填。
-
-
- 版本号 / 强制更新 / 送审版本去
- 服务配置 → 版本控制,
- 游客登录开关去
- 服务配置 → 游客显隐控制
- —— 那两组是整个应用一套的,不分渠道。
-
-
-
- 应用
-
-
-
-
-
-
-
-
-
⚠
-
-
以下支付方式已在渠道里开放,但商户凭据还没配:
-
- {{ w.label }}({{ w.channels.join('、') }})
-
-
-
-
-
-
-
- 当前是全局默认作用域:这里配的渠道对所有应用生效,应用自己配了同渠道则以应用的为准。
-
-
-
-
-
- {{ g.icon }} {{ g.label }}
- {{ g.items.length }} / {{ g.channels.length }} 已配置
-
-
-
-
-
-
{{ channelName(c.channel) }}
-
渠道号 {{ c.channel }}
-
-
{{ statusLabel(c.status) }}
-
-
-
- 可跳过绑定
- 已停用
-
-
-
- {{ payLabel(p) }}{{ payMissing(p) ? ' ⚠' : '' }}
-
-
-
- {{ c.address || '未填本渠道地址 · 用应用默认' }}
-
-
-
-
-
-
{{ channelName(ch) }}
-
未配置 · 点击添加
-
-
-
-
-
-
-
-
-
-
{{ isEditing ? '编辑渠道' : '添加渠道' }}
- {{ scopeApp || '全局默认' }}
-
-
-
-
-
- 渠道
-
-
- 分发渠道 *
-
-
-
- 上架状态
-
-
-
-
- 描述
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ 正在跳转到「版本与分发」…
-
-
diff --git a/apps/admin/app/pages/environments.vue b/apps/admin/app/pages/environments.vue
index 3c34c45c..30dd9cf6 100644
--- a/apps/admin/app/pages/environments.vue
+++ b/apps/admin/app/pages/environments.vue
@@ -97,7 +97,7 @@
-
+
diff --git a/apps/admin/app/pages/systemconfig.vue b/apps/admin/app/pages/systemconfig.vue
index 3ba24734..a030e5e0 100644
--- a/apps/admin/app/pages/systemconfig.vue
+++ b/apps/admin/app/pages/systemconfig.vue
@@ -164,16 +164,10 @@ const CATALOG = reactive([
{ key: 'bucket', label: 'Bucket', value: '', placeholder: 'yunyan-firmware', mono: true, required: true },
{ key: 'access_key_id', label: 'AccessKey ID', value: '', mono: true, encrypted: true, required: true },
{ key: 'access_key_secret', label: 'AccessKey Secret', value: '', mono: true, encrypted: true, required: true },
- { key: 'domain', label: '自定义域名 (CDN)', value: '', placeholder: 'cdn.yunyan.com', wide: true, hint: '可选,配置后文件下载 URL 走该域名' },
- ]),
- mk('storage_tencent_cos', 'tencent_cos', '腾讯云 COS', 'COS', 'Tencent', '#00a4ff', [
- { key: 'region', label: 'Region', value: '', placeholder: 'ap-guangzhou', mono: true, required: true },
- { key: 'appid', label: 'AppId', value: '', mono: true, required: true },
- { key: 'bucket', label: 'Bucket', value: '', mono: true, required: true },
- { key: 'secret_id', label: 'SecretId', value: '', mono: true, encrypted: true, required: true },
- { key: 'secret_key', label: 'SecretKey', value: '', mono: true, encrypted: true, required: true },
- { key: 'domain', label: '自定义域名 (CDN)', value: '', wide: true, hint: '可选' },
+ { key: 'domain', label: '自定义域名 (CDN)', value: '', placeholder: 'oss.ymaikj.com', wide: true, hint: '可选,配置后文件下载 URL 走该域名' },
]),
+ // 2026-09-23:腾讯云 COS 卡片已删除。服务端读存储配置只认 provider=aliyun_oss
+ // (comm.LoadAliyunOSSConf),配了 COS 也不会有任何地方读它。
],
},
{
diff --git a/apps/admin/app/stores/auth.ts b/apps/admin/app/stores/auth.ts
index dde8c5da..4495e492 100644
--- a/apps/admin/app/stores/auth.ts
+++ b/apps/admin/app/stores/auth.ts
@@ -183,6 +183,10 @@ export const useAuthStore = defineStore('auth', {
// 2026-09-12「应用环境配置」并入「服务与环境配置」(svcconfig):只勾过旧 id 的存量账号
// 不该因为一次合并就失去入口。反向不成立——勾了 svcconfig 本来就包含这一页。
if (pageId === 'svcconfig' && this.access.includes('appconfig')) return true
+ // 2026-09-23「渠道分发」(channels) 并入「版本与分发」(appversion):只勾过 channels
+ // 的存量账号不该因为一次合并就失去入口。菜单 id 沿用 appversion 未变,
+ // 所以勾过 appversion 的账号本来就有权限,不需要反向兼容。
+ if (pageId === 'appversion' && this.access.includes('channels')) return true
// 管理员未显式配置权限时,按默认权限集放行(除 SaaS 管理 / 系统管理 外全部)。
if (this.identity === IDENTITY_MANAGER && this.access.length === 0) {
return DEFAULT_ADMIN_ACCESS.includes(pageId)
diff --git a/apps/admin/app/utils/menus.ts b/apps/admin/app/utils/menus.ts
index 540515ac..204dc243 100644
--- a/apps/admin/app/utils/menus.ts
+++ b/apps/admin/app/utils/menus.ts
@@ -81,7 +81,11 @@ export const MENU_GROUPS: MenuGroup[] = [
// 版本控制 / 游客显隐控制:按应用配、不分渠道(存 app_release 表)。
// 这两组开关原先埋在「SaaS 管理 → 渠道分发」里按渠道各存一份,同一个开关有 8 处能改,
// 客户端只读一处,出过「后台开了游客登录但 App 里没有」的事故,故拆到这里单独管。
- { id: 'appversion', label: '版本控制', to: '/appversion', icon: 'M12 3v12m0 0l-4-4m4 4l4-4M4 17v2a2 2 0 002 2h12a2 2 0 002-2v-2' },
+ // 2026-09-23:「渠道分发」并入本页,标签与路由改了,**菜单 id 仍是 appversion** ——
+ // 存量后台账号的 access 清单里存的是这个串,改 id 等于让这些账号丢入口
+ // (同「会议模板 → 会议配置」那次的处理)。只勾过旧 id 'channels' 的账号
+ // 由 auth.ts 的 hasAccess 兼容放行。
+ { id: 'appversion', label: '版本与分发', to: '/appdist', icon: 'M12 3v12m0 0l-4-4m4 4l4-4M4 17v2a2 2 0 002 2h12a2 2 0 002-2v-2' },
{ id: 'guestaccess', label: '游客显隐控制', to: '/guestaccess', icon: 'M15 12a3 3 0 11-6 0 3 3 0 016 0z M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z' },
],
},
@@ -91,7 +95,6 @@ export const MENU_GROUPS: MenuGroup[] = [
items: [
{ id: 'apps', label: '应用中心', to: '/apps', icon: 'M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10' },
{ id: 'environments', label: '环境节点', to: '/environments', icon: 'M5 3h14a2 2 0 012 2v4a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2zM5 13h14a2 2 0 012 2v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4a2 2 0 012-2zM7 7h.01M7 17h.01' },
- { id: 'channels', label: '渠道分发', to: '/channels', icon: 'M12 3v12m0 0l-4-4m4 4l4-4M4 17v2a2 2 0 002 2h12a2 2 0 002-2v-2' },
],
},
{