diff --git a/src/api/update/channel.ts b/src/api/update/channel.ts new file mode 100644 index 00000000..d6096cbd --- /dev/null +++ b/src/api/update/channel.ts @@ -0,0 +1,62 @@ +import request from "@/utils/request"; + +/** 更新渠道 */ +export interface UpdateChannel { + id: string; + code: string; + name: string; + description: string; + enabled: boolean; + sort: number; + createTime: string; + updateTime: string; +} + +/** 更新渠道表单 */ +export interface UpdateChannelForm { + code: string; + name: string; + description?: string; + enabled: boolean; + sort: number; +} + +const BASE_URL = "/api/v1/app/update/channels"; + +const UpdateChannelAPI = { + /** 渠道列表(按排序返回) */ + list() { + return request({ + url: `${BASE_URL}/list`, + method: "get", + }); + }, + + /** 新增渠道 */ + create(data: UpdateChannelForm) { + return request({ + url: BASE_URL, + method: "post", + data, + }); + }, + + /** 更新渠道 */ + update(id: string, data: UpdateChannelForm) { + return request({ + url: `${BASE_URL}/${id}`, + method: "put", + data, + }); + }, + + /** 删除渠道 */ + delete(id: string) { + return request({ + url: `${BASE_URL}/${id}`, + method: "delete", + }); + }, +}; + +export default UpdateChannelAPI; diff --git a/src/api/update/global.ts b/src/api/update/global.ts new file mode 100644 index 00000000..636bb179 --- /dev/null +++ b/src/api/update/global.ts @@ -0,0 +1,171 @@ +import request, { uploadHttp } from "@/utils/request"; +import type { PageResult } from "@/api/common"; + +/** 更新类型 */ +export type UpdateType = "GLOBAL" | "GRAY" | "SAVE_FLOW" | "HOT"; + +/** 发布单状态 */ +export type UpdateReleaseStatus = "DRAFT" | "PUBLISHED" | "STOPPED"; + +/** 应用更新发布单 */ +export interface AppUpdateRelease { + id: string; + updateType: UpdateType; + channelCode: string; + channelName: string; + packageName: string; + appLabel: string; + versionName: string; + versionCode: number; + fileUrl: string; + fileName: string; + fileSize: number; + fileSizeText: string; + md5: string; + sha1: string; + sha256: string; + iconUrl: string; + forceUpdate: boolean; + listed: boolean; + releaseNotes: string; + status: UpdateReleaseStatus; + publishedAt: string | null; + createTime: string; + updateTime: string; +} + +/** 全局更新安装包上传结果(后端自动解析包名/版本/图标) */ +export interface AppUpdateFile { + fileUrl: string; + fileName: string; + fileSize: number | string; + fileSizeText: string; + md5: string; + sha1: string; + sha256: string; + iconUrl: string; + packageName: string; + appLabel: string; + versionName: string; + versionCode: number | string | null; + parsed: boolean; +} + +/** 发布单表单 */ +export interface AppUpdateReleaseForm { + fileUrl: string; + fileName: string; + fileSize?: number | string; + fileSizeText?: string; + md5?: string; + sha1?: string; + sha256?: string; + iconUrl?: string; + channelCode: string; + packageName: string; + appLabel?: string; + versionName: string; + versionCode?: number; + forceUpdate: boolean; + listed: boolean; + releaseNotes?: string; +} + +/** 发布单分页查询参数 */ +export interface AppUpdateReleasePageQuery { + pageNum: number; + pageSize: number; + keywords?: string; + status?: UpdateReleaseStatus | ""; +} + +const BASE_URL = "/api/v1/app/update/global"; + +const AppUpdateReleaseAPI = { + /** 上传全局更新安装包(独立存储目录,自动解析包信息) */ + upload(file: File) { + const formData = new FormData(); + formData.append("file", file); + return uploadHttp({ + url: `${BASE_URL}/upload`, + method: "post", + data: formData, + headers: { "Content-Type": "multipart/form-data" }, + }); + }, + + /** 分页查询全局更新发布单 */ + page(query: AppUpdateReleasePageQuery) { + return request>({ + url: `${BASE_URL}/page`, + method: "get", + params: { + pageNum: query.pageNum, + pageSize: query.pageSize, + keywords: query.keywords, + status: query.status || undefined, + }, + }); + }, + + /** 发布单详情 */ + detail(id: string) { + return request({ + url: `${BASE_URL}/${id}`, + method: "get", + }); + }, + + /** 创建全局更新草稿 */ + create(data: AppUpdateReleaseForm) { + return request({ + url: BASE_URL, + method: "post", + data, + }); + }, + + /** 编辑全局更新草稿 */ + update(id: string, data: AppUpdateReleaseForm) { + return request({ + url: `${BASE_URL}/${id}`, + method: "put", + data, + }); + }, + + /** 发布 */ + publish(id: string) { + return request({ + url: `${BASE_URL}/${id}/publish`, + method: "post", + }); + }, + + /** 停止发布 */ + stop(id: string) { + return request({ + url: `${BASE_URL}/${id}/stop`, + method: "post", + }); + }, + + /** 上架 / 下架 */ + updateListed(id: string, listed: boolean) { + return request({ + url: `${BASE_URL}/${id}/listed`, + method: "put", + params: { listed }, + }); + }, + + /** 删除发布单 */ + delete(id: string) { + return request({ + url: `${BASE_URL}/${id}`, + method: "delete", + }); + }, +}; + +export default AppUpdateReleaseAPI; diff --git a/src/api/update/gray.ts b/src/api/update/gray.ts new file mode 100644 index 00000000..06142db1 --- /dev/null +++ b/src/api/update/gray.ts @@ -0,0 +1,138 @@ +import request, { uploadHttp } from "@/utils/request"; +import type { PageResult } from "@/api/common"; +import type { AppUpdateFile, AppUpdateRelease, UpdateReleaseStatus } from "./global"; + +/** 灰度下发目标 */ +export type GrayTargetType = "CHANNEL" | "SN"; + +/** 灰度更新发布单(与全局发布单同集合,按 updateType=GRAY 区分) */ +export interface AppUpdateGrayRelease extends AppUpdateRelease { + targetType: GrayTargetType; + /** 目标设备数量(按 SN 下发时) */ + snCount: number | null; + /** 目标设备序列号(仅详情返回) */ + snList: string[] | null; +} + +/** 灰度更新发布单表单 */ +export interface AppUpdateGrayForm { + fileUrl: string; + fileName: string; + fileSize?: number | string; + fileSizeText?: string; + md5?: string; + sha1?: string; + sha256?: string; + iconUrl?: string; + targetType: GrayTargetType; + /** 按渠道下发时的渠道编码 */ + channelCode?: string; + /** 按设备下发时的设备序列号列表 */ + snList: string[]; + packageName: string; + appLabel?: string; + versionName: string; + versionCode?: number; + forceUpdate: boolean; + listed: boolean; + releaseNotes?: string; +} + +/** 灰度发布单分页查询参数 */ +export interface AppUpdateGrayPageQuery { + pageNum: number; + pageSize: number; + keywords?: string; + status?: UpdateReleaseStatus | ""; +} + +const BASE_URL = "/api/v1/app/update/gray"; + +const AppUpdateGrayAPI = { + /** 上传灰度更新安装包(独立存储目录,自动解析包信息) */ + upload(file: File) { + const formData = new FormData(); + formData.append("file", file); + return uploadHttp({ + url: `${BASE_URL}/upload`, + method: "post", + data: formData, + headers: { "Content-Type": "multipart/form-data" }, + }); + }, + + /** 分页查询灰度发布单 */ + page(query: AppUpdateGrayPageQuery) { + return request>({ + url: `${BASE_URL}/page`, + method: "get", + params: { + pageNum: query.pageNum, + pageSize: query.pageSize, + keywords: query.keywords, + status: query.status || undefined, + }, + }); + }, + + /** 灰度发布单详情(含目标设备序列号) */ + detail(id: string) { + return request({ + url: `${BASE_URL}/${id}`, + method: "get", + }); + }, + + /** 创建灰度更新草稿 */ + create(data: AppUpdateGrayForm) { + return request({ + url: BASE_URL, + method: "post", + data, + }); + }, + + /** 编辑灰度更新草稿 */ + update(id: string, data: AppUpdateGrayForm) { + return request({ + url: `${BASE_URL}/${id}`, + method: "put", + data, + }); + }, + + /** 发布 */ + publish(id: string) { + return request({ + url: `${BASE_URL}/${id}/publish`, + method: "post", + }); + }, + + /** 停止发布 */ + stop(id: string) { + return request({ + url: `${BASE_URL}/${id}/stop`, + method: "post", + }); + }, + + /** 上架 / 下架 */ + updateListed(id: string, listed: boolean) { + return request({ + url: `${BASE_URL}/${id}/listed`, + method: "put", + params: { listed }, + }); + }, + + /** 删除发布单 */ + delete(id: string) { + return request({ + url: `${BASE_URL}/${id}`, + method: "delete", + }); + }, +}; + +export default AppUpdateGrayAPI; diff --git a/src/api/update/save-flow.ts b/src/api/update/save-flow.ts new file mode 100644 index 00000000..c432c150 --- /dev/null +++ b/src/api/update/save-flow.ts @@ -0,0 +1,174 @@ +import request, { uploadHttp } from "@/utils/request"; +import type { PageResult } from "@/api/common"; +import type { AppUpdateFile, AppUpdateRelease, UpdateReleaseStatus } from "./global"; + +/** 差分补丁生成状态 */ +export type PatchStatus = "GENERATING" | "READY" | "FAILED"; + +/** 差分源包来源 */ +export type PatchBaseSource = "RELEASE" | "UPLOAD"; + +/** 省流更新差分补丁 */ +export interface AppUpdatePatch { + id: string; + targetReleaseId: string; + channelCode: string; + channelName: string; + packageName: string; + appLabel: string; + iconUrl: string; + targetVersionName: string; + targetVersionCode: number; + targetFileSize: number; + targetFileSizeText: string; + targetSha256: string; + baseVersionName: string; + baseVersionCode: number; + baseSource: PatchBaseSource; + baseReleaseId: string | null; + baseFileUrl: string; + baseFileName: string; + baseSha256: string; + patchUrl: string | null; + patchFileName: string; + patchSize: number | null; + patchSizeText: string | null; + patchRatio: number | null; + md5: string | null; + sha1: string | null; + sha256: string | null; + patchAlgorithm: string; + sizeTier: string | null; + status: PatchStatus; + errorMessage: string | null; + /** 生成成功但运营层面给出的建议(如补丁率过高建议改用全量),非空表示默认未上架 */ + advice: string | null; + listed: boolean; + forceUpdate: boolean; + targetReleaseStatus: UpdateReleaseStatus | null; + targetListed: boolean | null; + generatedAt: string | null; + createTime: string; + updateTime: string; +} + +/** 补录的差分源包 */ +export interface PatchBasePackage { + fileUrl: string; + fileName: string; + packageName: string; + appLabel?: string; + versionName: string; + versionCode: number; + md5: string; + sha256: string; +} + +/** 差分补丁创建表单 */ +export interface AppUpdatePatchForm { + targetReleaseId: string; + baseReleaseIds: string[]; + basePackages: PatchBasePackage[]; +} + +/** 差分补丁分页查询参数 */ +export interface AppUpdatePatchPageQuery { + pageNum: number; + pageSize: number; + status?: PatchStatus | ""; + channelCode?: string; + keywords?: string; +} + +const BASE_URL = "/api/v1/app/update/save-flow"; + +const AppUpdatePatchAPI = { + /** 上传补录的旧安装包(差分源包) */ + uploadBase(file: File) { + const formData = new FormData(); + formData.append("file", file); + return uploadHttp({ + url: `${BASE_URL}/upload-base`, + method: "post", + data: formData, + headers: { "Content-Type": "multipart/form-data" }, + }); + }, + + /** 目标发布单可勾选的历史源版本 */ + baseVersions(targetReleaseId: string) { + return request({ + url: `${BASE_URL}/base-versions`, + params: { targetReleaseId }, + method: "get", + }); + }, + + /** 分页查询差分补丁 */ + page(query: AppUpdatePatchPageQuery) { + return request>({ + url: `${BASE_URL}/page`, + method: "get", + params: { + pageNum: query.pageNum, + pageSize: query.pageSize, + status: query.status || undefined, + channelCode: query.channelCode || undefined, + keywords: query.keywords || undefined, + }, + }); + }, + + /** 差分补丁详情 */ + detail(id: string) { + return request({ + url: `${BASE_URL}/${id}`, + method: "get", + }); + }, + + /** 提交差分生成任务 */ + create(data: AppUpdatePatchForm) { + return request({ + url: BASE_URL, + method: "post", + data, + }); + }, + + /** 上架 / 下架补丁 */ + updateListed(id: string, listed: boolean) { + return request({ + url: `${BASE_URL}/${id}/listed`, + method: "put", + params: { listed }, + }); + }, + + /** 开启 / 关闭补丁的强制更新 */ + updateForceUpdate(id: string, forceUpdate: boolean) { + return request({ + url: `${BASE_URL}/${id}/force-update`, + method: "put", + params: { forceUpdate }, + }); + }, + + /** 重新生成补丁 */ + regenerate(id: string) { + return request({ + url: `${BASE_URL}/${id}/regenerate`, + method: "post", + }); + }, + + /** 删除补丁 */ + delete(id: string) { + return request({ + url: `${BASE_URL}/${id}`, + method: "delete", + }); + }, +}; + +export default AppUpdatePatchAPI; diff --git a/src/views/update/channel/index.vue b/src/views/update/channel/index.vue new file mode 100644 index 00000000..d70e055b --- /dev/null +++ b/src/views/update/channel/index.vue @@ -0,0 +1,235 @@ + + + + + diff --git a/src/views/update/components/PatchCreateDrawer.vue b/src/views/update/components/PatchCreateDrawer.vue new file mode 100644 index 00000000..9adfd56c --- /dev/null +++ b/src/views/update/components/PatchCreateDrawer.vue @@ -0,0 +1,326 @@ + + + + + diff --git a/src/views/update/global/index.vue b/src/views/update/global/index.vue new file mode 100644 index 00000000..f425cd1d --- /dev/null +++ b/src/views/update/global/index.vue @@ -0,0 +1,918 @@ + + + + + diff --git a/src/views/update/gray/index.vue b/src/views/update/gray/index.vue new file mode 100644 index 00000000..be3c4dc3 --- /dev/null +++ b/src/views/update/gray/index.vue @@ -0,0 +1,1094 @@ + + + + + diff --git a/src/views/update/hot/index.vue b/src/views/update/hot/index.vue new file mode 100644 index 00000000..c6fbbe38 --- /dev/null +++ b/src/views/update/hot/index.vue @@ -0,0 +1,30 @@ + + + + + diff --git a/src/views/update/save-flow/index.vue b/src/views/update/save-flow/index.vue new file mode 100644 index 00000000..c3ec28b1 --- /dev/null +++ b/src/views/update/save-flow/index.vue @@ -0,0 +1,698 @@ + + + + +