feat: 新增禾圃集应用版本更新管理功能

新增禾圃集应用(appCode=garden)的版本策略管理功能,包括后端接口、前端API、页面路由、数据库迁移及相关常量配置,支持对不同平台和渠道的版本策略进行增删改查操作,与现有讯工捷使(appCode=im)版本管理功能保持一致的架构和交互逻辑。
This commit is contained in:
Austin
2026-10-12 15:31:11 +08:00
parent 315d97895b
commit 2c5995073f
15 changed files with 526 additions and 28 deletions
@@ -0,0 +1,25 @@
package cultivateapprelease
import (
"hotgo/internal/model/input/form"
"hotgo/internal/model/input/sysin"
"github.com/gogf/gf/v2/frame/g"
)
type ListReq struct {
g.Meta `path:"/cultivate/app-release/list" method:"get" tags:"养成版本更新" summary:"获取养成客户端版本策略列表"`
sysin.ImAppReleaseListInp
}
type ListRes struct {
form.PageRes
List []*sysin.ImAppReleaseModel `json:"list" dc:"数据列表"`
}
type EditReq struct {
g.Meta `path:"/cultivate/app-release/edit" method:"post" tags:"养成版本更新" summary:"新增/编辑养成客户端版本策略"`
sysin.ImAppReleaseEditInp
}
type EditRes struct{}
@@ -223,12 +223,14 @@ type TenantLoginIPWhitelistRes struct {
type AppReleaseLatestReq struct {
g.Meta `path:"/internal/im-config/app/releases/latest" method:"get" tags:"IM内部配置" summary:"获取客户端最新版本策略"`
AppCode string `json:"app_code" dc:"应用标识,缺省 im"`
Platform string `json:"platform" v:"required#平台不能为空" dc:"平台"`
Channel string `json:"channel" dc:"渠道"`
}
type AppReleaseLatestRes struct {
ID int64 `json:"id"`
AppCode string `json:"app_code"`
Platform string `json:"platform"`
Channel string `json:"channel"`
LatestVersion string `json:"latest_version"`
@@ -8,6 +8,7 @@ import (
func init() {
dict.RegisterEnums("ImAppPlatformOptions", "IM客户端平台选项", ImAppPlatformOptions)
dict.RegisterEnums("ImAppUpdateTypeOptions", "IM客户端更新类型选项", ImAppUpdateTypeOptions)
dict.RegisterEnums("ImAppCodeOptions", "IM客户端应用标识选项", ImAppCodeOptions)
dict.RegisterEnums("ImAbuseTargetTypeOptions", "IM举报对象类型选项", ImAbuseTargetTypeOptions)
dict.RegisterEnums("ImAbuseReasonEnabledOptions", "IM举报原因启用状态", ImAbuseReasonEnabledOptions)
}
@@ -17,6 +18,11 @@ const (
ImAppPlatformAndroid = "android"
)
const (
ImAppCodeIm = "im"
ImAppCodeGarden = "garden"
)
const (
ImAppUpdateTypeNone = "none"
ImAppUpdateTypeRecommended = "recommended"
@@ -34,6 +40,11 @@ var ImAppPlatformOptions = []*model.Option{
dict.GenSuccessOption(ImAppPlatformAndroid, "Android"),
}
var ImAppCodeOptions = []*model.Option{
dict.GenPrimaryOption(ImAppCodeIm, "讯工捷使"),
dict.GenSuccessOption(ImAppCodeGarden, "禾圃集"),
}
var ImAppUpdateTypeOptions = []*model.Option{
dict.GenDefaultOption(ImAppUpdateTypeNone, "无需更新"),
dict.GenWarningOption(ImAppUpdateTypeRecommended, "建议更新"),
@@ -0,0 +1,31 @@
package sys
import (
"context"
"hotgo/api/admin/cultivateapprelease"
"hotgo/internal/model/input/sysin"
"hotgo/internal/service"
)
var CultivateAppRelease = cCultivateAppRelease{}
type cCultivateAppRelease struct{}
func (c *cCultivateAppRelease) List(ctx context.Context, req *cultivateapprelease.ListReq) (res *cultivateapprelease.ListRes, err error) {
list, totalCount, err := service.SysImAppRelease().List(ctx, &req.ImAppReleaseListInp)
if err != nil {
return
}
if list == nil {
list = []*sysin.ImAppReleaseModel{}
}
res = new(cultivateapprelease.ListRes)
res.List = list
res.PageRes.Pack(req, totalCount)
return
}
func (c *cCultivateAppRelease) Edit(ctx context.Context, req *cultivateapprelease.EditReq) (res *cultivateapprelease.EditRes, err error) {
err = service.SysImAppRelease().Edit(ctx, &req.ImAppReleaseEditInp)
return
}
@@ -468,8 +468,15 @@ func (c *cConfig) TenantLoginIPWhitelist(ctx context.Context, req *api.TenantLog
}
func (c *cConfig) AppReleaseLatest(ctx context.Context, req *api.AppReleaseLatestReq) (res *api.AppReleaseLatestRes, err error) {
appCode := strings.ToLower(strings.TrimSpace(req.AppCode))
platform := strings.ToLower(strings.TrimSpace(req.Platform))
channel := strings.ToLower(strings.TrimSpace(req.Channel))
if appCode == "" {
appCode = "im"
}
if appCode != "im" && appCode != "garden" {
return nil, gerror.New("应用标识不合法")
}
if platform != "ios" && platform != "android" {
return nil, gerror.New("客户端平台不合法")
}
@@ -481,6 +488,7 @@ func (c *cConfig) AppReleaseLatest(ctx context.Context, req *api.AppReleaseLates
}
var row struct {
ID int64 `json:"id"`
AppCode string `json:"app_code"`
Platform string `json:"platform"`
Channel string `json:"channel"`
LatestVersion string `json:"latest_version"`
@@ -496,6 +504,7 @@ func (c *cConfig) AppReleaseLatest(ctx context.Context, req *api.AppReleaseLates
UpdatedAt *gtime.Time `json:"updated_at"`
}
err = imtenant.RegistryDB().Model("im_app_releases").Safe().Ctx(ctx).
Where("app_code", appCode).
Where("platform", platform).Where("channel", channel).Limit(1).Scan(&row)
if err != nil {
return nil, gerror.Wrap(err, "获取IM客户端版本策略失败")
@@ -504,7 +513,7 @@ func (c *cConfig) AppReleaseLatest(ctx context.Context, req *api.AppReleaseLates
return nil, gerror.New("IM客户端版本策略不存在")
}
return &api.AppReleaseLatestRes{
ID: row.ID, Platform: row.Platform, Channel: row.Channel,
ID: row.ID, AppCode: row.AppCode, Platform: row.Platform, Channel: row.Channel,
LatestVersion: row.LatestVersion, MinSupportedVersion: row.MinSupportedVersion,
UpdateType: row.UpdateType, ReleaseNotes: row.ReleaseNotes, StoreURL: row.StoreURL,
PackageURL: row.PackageURL, Checksum: row.Checksum, RemindAfterSeconds: row.RemindAfterSeconds,
+40 -21
View File
@@ -36,13 +36,16 @@ func (s *sSysImAppRelease) List(ctx context.Context, in *sysin.ImAppReleaseListI
return
}
mod := s.model(ctx, db)
if in.AppCode != "" {
mod = mod.Where("app_code", in.AppCode)
}
if in.Platform != "" {
mod = mod.Where("platform", in.Platform)
}
if in.Channel != "" {
mod = mod.WhereLike("channel", "%"+in.Channel+"%")
}
mod = mod.Page(in.Page, in.PerPage).OrderAsc("platform").OrderAsc("channel")
mod = mod.Page(in.Page, in.PerPage).OrderAsc("app_code").OrderAsc("platform").OrderAsc("channel")
if err = mod.ScanAndCount(&list, &totalCount, false); err != nil {
err = gerror.Wrap(err, "获取IM客户端版本策略失败,请稍后重试!")
return
@@ -59,6 +62,7 @@ func (s *sSysImAppRelease) Edit(ctx context.Context, in *sysin.ImAppReleaseEditI
return
}
data := g.Map{
"app_code": in.AppCode,
"platform": in.Platform,
"channel": in.Channel,
"latest_version": in.LatestVersion,
@@ -85,18 +89,18 @@ func (s *sSysImAppRelease) Edit(ctx context.Context, in *sysin.ImAppReleaseEditI
return gerror.Wrap(err, "新增IM客户端版本策略失败,请稍后重试!")
}
idValue, err := db.Model(imAppReleasesTable).Safe().Ctx(ctx).Fields("id").
Where("platform", in.Platform).Where("channel", in.Channel).Value()
Where("app_code", in.AppCode).Where("platform", in.Platform).Where("channel", in.Channel).Value()
if err != nil {
return gerror.Wrap(err, "获取IM客户端版本策略ID失败,请稍后重试!")
}
targetId = idValue.Int64()
}
_ = writeImAdminAudit(ctx, 0, action, "im_app_release", targetId, "success", g.Map{
"platform": in.Platform, "channel": in.Channel, "update_type": in.UpdateType,
"app_code": in.AppCode, "platform": in.Platform, "channel": in.Channel, "update_type": in.UpdateType,
})
if err = imkafka.PublishConfigEvent(ctx, imkafka.ConfigEventInput{
TenantCode: "*", EventType: "app.release.updated", Group: "app_release",
Keys: []string{in.Platform, in.Channel}, BizID: in.Platform + ":" + in.Channel,
Keys: []string{in.AppCode, in.Platform, in.Channel}, BizID: in.AppCode + ":" + in.Platform + ":" + in.Channel,
Actor: &imkafka.Actor{Type: "admin", ID: fmt.Sprint(contexts.GetUserId(ctx))},
CreatedBy: contexts.GetUserId(ctx),
}); err != nil {
@@ -108,6 +112,7 @@ func (s *sSysImAppRelease) Edit(ctx context.Context, in *sysin.ImAppReleaseEditI
func (s *sSysImAppRelease) model(ctx context.Context, db gdb.DB) *gdb.Model {
return db.Model(imAppReleasesTable).Safe().Ctx(ctx).Fields(
"id",
"app_code",
"platform",
"channel",
"latest_version",
@@ -125,28 +130,42 @@ func (s *sSysImAppRelease) model(ctx context.Context, db gdb.DB) *gdb.Model {
}
func (s *sSysImAppRelease) ensureDefaults(ctx context.Context, db gdb.DB) (err error) {
apps := []string{consts.ImAppCodeIm, consts.ImAppCodeGarden}
defaults := []g.Map{
{"platform": consts.ImAppPlatformIos, "channel": "stable", "latest_version": "1.0.0", "min_supported_version": "1.0.0", "update_type": consts.ImAppUpdateTypeNone, "release_notes": "当前已是最新版本。", "remind_after_seconds": 0},
{"platform": consts.ImAppPlatformAndroid, "channel": "stable", "latest_version": "1.0.0", "min_supported_version": "1.0.0", "update_type": consts.ImAppUpdateTypeNone, "release_notes": "当前已是最新版本。", "remind_after_seconds": 0},
}
now := gtime.Now()
for _, item := range defaults {
var count int
if count, err = db.Model(imAppReleasesTable).Safe().Ctx(ctx).
Where("platform", item["platform"]).Where("channel", item["channel"]).Count(); err != nil {
return gerror.Wrap(err, "检查IM客户端默认版本策略失败,请稍后重试!")
}
if count > 0 {
continue
}
item["store_url"] = ""
item["package_url"] = ""
item["checksum"] = ""
item["published_at"] = now
item["created_at"] = now
item["updated_at"] = now
if _, err = db.Model(imAppReleasesTable).Safe().Ctx(ctx).Data(item).Insert(); err != nil {
return gerror.Wrap(err, "初始化IM客户端默认版本策略失败,请稍后重试!")
for _, appCode := range apps {
for _, item := range defaults {
var count int
if count, err = db.Model(imAppReleasesTable).Safe().Ctx(ctx).
Where("app_code", appCode).
Where("platform", item["platform"]).Where("channel", item["channel"]).Count(); err != nil {
return gerror.Wrap(err, "检查IM客户端默认版本策略失败,请稍后重试!")
}
if count > 0 {
continue
}
row := g.Map{
"app_code": appCode,
"platform": item["platform"],
"channel": item["channel"],
"latest_version": item["latest_version"],
"min_supported_version": item["min_supported_version"],
"update_type": item["update_type"],
"release_notes": item["release_notes"],
"remind_after_seconds": item["remind_after_seconds"],
"store_url": "",
"package_url": "",
"checksum": "",
"published_at": now,
"created_at": now,
"updated_at": now,
}
if _, err = db.Model(imAppReleasesTable).Safe().Ctx(ctx).Data(row).Insert(); err != nil {
return gerror.Wrap(err, "初始化IM客户端默认版本策略失败,请稍后重试!")
}
}
}
return nil
@@ -16,13 +16,18 @@ import (
type ImAppReleaseListInp struct {
form.PageReq
AppCode string `json:"appCode" dc:"应用标识"`
Platform string `json:"platform" dc:"平台"`
Channel string `json:"channel" dc:"渠道"`
}
func (in *ImAppReleaseListInp) Filter(ctx context.Context) (err error) {
in.AppCode = strings.TrimSpace(in.AppCode)
in.Platform = strings.TrimSpace(in.Platform)
in.Channel = strings.TrimSpace(in.Channel)
if in.AppCode != "" && !dict.HasOptionKey(consts.ImAppCodeOptions, in.AppCode) {
return gerror.New("应用标识不合法")
}
if in.Platform != "" && !dict.HasOptionKey(consts.ImAppPlatformOptions, in.Platform) {
return gerror.New("客户端平台不合法")
}
@@ -31,6 +36,7 @@ func (in *ImAppReleaseListInp) Filter(ctx context.Context) (err error) {
type ImAppReleaseModel struct {
Id int64 `json:"id" dc:"ID"`
AppCode string `json:"appCode" dc:"应用标识"`
Platform string `json:"platform" dc:"平台"`
Channel string `json:"channel" dc:"渠道"`
LatestVersion string `json:"latestVersion" dc:"最新版本"`
@@ -48,6 +54,7 @@ type ImAppReleaseModel struct {
type ImAppReleaseEditInp struct {
Id int64 `json:"id" dc:"ID"`
AppCode string `json:"appCode" dc:"应用标识"`
Platform string `json:"platform" v:"required#平台不能为空" dc:"平台"`
Channel string `json:"channel" v:"required#渠道不能为空" dc:"渠道"`
LatestVersion string `json:"latestVersion" v:"required#最新版本不能为空" dc:"最新版本"`
@@ -61,6 +68,7 @@ type ImAppReleaseEditInp struct {
}
func (in *ImAppReleaseEditInp) Filter(ctx context.Context) (err error) {
in.AppCode = strings.TrimSpace(in.AppCode)
in.Platform = strings.TrimSpace(in.Platform)
in.Channel = strings.TrimSpace(in.Channel)
in.LatestVersion = strings.TrimSpace(in.LatestVersion)
@@ -69,6 +77,12 @@ func (in *ImAppReleaseEditInp) Filter(ctx context.Context) (err error) {
in.StoreUrl = strings.TrimSpace(in.StoreUrl)
in.PackageUrl = strings.TrimSpace(in.PackageUrl)
in.Checksum = strings.TrimSpace(in.Checksum)
if in.AppCode == "" {
in.AppCode = consts.ImAppCodeIm
}
if !dict.HasOptionKey(consts.ImAppCodeOptions, in.AppCode) {
return gerror.New("应用标识不合法")
}
if !dict.HasOptionKey(consts.ImAppPlatformOptions, in.Platform) {
return gerror.New("客户端平台不合法")
}
@@ -0,0 +1,7 @@
package genrouter
import "hotgo/internal/controller/admin/sys"
func init() {
LoginRequiredRouter = append(LoginRequiredRouter, sys.CultivateAppRelease) // 养成版本更新
}
@@ -0,0 +1,12 @@
SET @now := NOW();
SET @cultivateRootId := (SELECT `id` FROM `hg_admin_menu` WHERE `name` = 'cultivateManager' LIMIT 1);
INSERT INTO `hg_admin_menu` (`id`, `pid`, `title`, `name`, `path`, `icon`, `type`, `redirect`, `permissions`, `permission_name`, `component`, `always_show`, `active_menu`, `is_root`, `is_frame`, `frame_src`, `keep_alive`, `hidden`, `affix`, `level`, `tree`, `sort`, `remark`, `status`, `created_at`, `updated_at`)
SELECT NULL, @cultivateRootId, '版本更新', 'cultivateAppReleaseIndex', 'app-release', '', '2', '', '/cultivate/app-release/list', '', '/cultivate/appRelease/index', '1', 'cultivateAppReleaseIndex', '0', '0', '', '0', '0', '0', '2', CONCAT('tr_', @cultivateRootId, ' '), '50', '禾圃集应用版本策略(app_code=garden)', '1', @now, @now
WHERE NOT EXISTS (SELECT 1 FROM `hg_admin_menu` WHERE `name` = 'cultivateAppReleaseIndex');
SET @cultivateAppReleaseId := (SELECT `id` FROM `hg_admin_menu` WHERE `name` = 'cultivateAppReleaseIndex' LIMIT 1);
INSERT INTO `hg_admin_menu` (`id`, `pid`, `title`, `name`, `path`, `icon`, `type`, `redirect`, `permissions`, `permission_name`, `component`, `always_show`, `active_menu`, `is_root`, `is_frame`, `frame_src`, `keep_alive`, `hidden`, `affix`, `level`, `tree`, `sort`, `remark`, `status`, `created_at`, `updated_at`)
SELECT NULL, @cultivateAppReleaseId, '版本编辑', 'cultivateAppReleaseEdit', '', '', '3', '', '/cultivate/app-release/edit', '', '', '1', '', '0', '0', '', '0', '0', '0', '3', CONCAT('tr_', @cultivateRootId, ' tr_', @cultivateAppReleaseId, ' '), '10', '', '1', @now, @now
WHERE NOT EXISTS (SELECT 1 FROM `hg_admin_menu` WHERE `name` = 'cultivateAppReleaseEdit');
@@ -0,0 +1,26 @@
-- 存量库升级:im_app_releases 增加 app_code 维度(im=讯工捷使, garden=禾圃集)
-- 幂等:已存在 app_code 列时跳过 ALTER(通过 information_schema 判断由执行方保证)
ALTER TABLE `im_app_releases`
ADD COLUMN `app_code` VARCHAR(32) NOT NULL DEFAULT 'im' AFTER `id`;
ALTER TABLE `im_app_releases`
DROP INDEX `uk_im_app_releases_platform_channel`,
ADD UNIQUE KEY `uk_im_app_releases_app_platform_channel` (`app_code`, `platform`, `channel`);
INSERT INTO `im_app_releases`
(`app_code`, `platform`, `channel`, `latest_version`, `min_supported_version`, `update_type`, `release_notes`)
VALUES
('garden', 'ios', 'stable', '1.0.0', '1.0.0', 'none', '当前已是最新版本。'),
('garden', 'android', 'stable', '1.0.0', '1.0.0', 'none', '当前已是最新版本。')
ON DUPLICATE KEY UPDATE `platform` = VALUES(`platform`);
-- 菜单
SOURCE cultivate_app_release_menu.sql;
-- 超管角色绑定新菜单(role_id 按实际环境调整)
SET @cultivateAppReleaseMenuId := (SELECT `id` FROM `hg_admin_menu` WHERE `name` = 'cultivateAppReleaseIndex' LIMIT 1);
SET @cultivateAppReleaseEditId := (SELECT `id` FROM `hg_admin_menu` WHERE `name` = 'cultivateAppReleaseEdit' LIMIT 1);
INSERT IGNORE INTO `hg_admin_role_menu` (`role_id`, `menu_id`)
SELECT r.`id`, m.`menu_id` FROM (SELECT 1 AS id) r
CROSS JOIN (SELECT @cultivateAppReleaseMenuId AS menu_id UNION SELECT @cultivateAppReleaseEditId) m;
@@ -1,6 +1,8 @@
-- Global MeetingIM client release policy. This table belongs to hotgo and is not tenant-scoped.
-- app_code: im=讯工捷使主应用, garden=禾圃集(后续新增 app 在此扩展)
CREATE TABLE IF NOT EXISTS `im_app_releases` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`app_code` VARCHAR(32) NOT NULL DEFAULT 'im',
`platform` VARCHAR(32) NOT NULL,
`channel` VARCHAR(32) NOT NULL DEFAULT 'stable',
`latest_version` VARCHAR(64) NOT NULL,
@@ -15,12 +17,14 @@ CREATE TABLE IF NOT EXISTS `im_app_releases` (
`created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`updated_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (`id`),
UNIQUE KEY `uk_im_app_releases_platform_channel` (`platform`, `channel`)
UNIQUE KEY `uk_im_app_releases_app_platform_channel` (`app_code`, `platform`, `channel`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='MeetingIM全局客户端版本策略';
INSERT INTO `im_app_releases`
(`platform`, `channel`, `latest_version`, `min_supported_version`, `update_type`, `release_notes`)
(`app_code`, `platform`, `channel`, `latest_version`, `min_supported_version`, `update_type`, `release_notes`)
VALUES
('ios', 'stable', '1.0.0', '1.0.0', 'none', '当前已是最新版本。'),
('android', 'stable', '1.0.0', '1.0.0', 'none', '当前已是最新版本。')
('im', 'ios', 'stable', '1.0.0', '1.0.0', 'none', '当前已是最新版本。'),
('im', 'android', 'stable', '1.0.0', '1.0.0', 'none', '当前已是最新版本。'),
('garden', 'ios', 'stable', '1.0.0', '1.0.0', 'none', '当前已是最新版本。'),
('garden', 'android', 'stable', '1.0.0', '1.0.0', 'none', '当前已是最新版本。')
ON DUPLICATE KEY UPDATE `platform` = VALUES(`platform`);
+10
View File
@@ -0,0 +1,10 @@
import { http } from '@/utils/http/axios';
// 本页仅管理禾圃集应用(appCode=garden)的版本策略
export function List(params) {
return http.request({ url: '/cultivate/app-release/list', method: 'GET', params: { ...params, appCode: 'garden' } });
}
export function Edit(params) {
return http.request({ url: '/cultivate/app-release/edit', method: 'POST', params: { ...params, appCode: 'garden' } });
}
+3 -2
View File
@@ -1,9 +1,10 @@
import { http } from '@/utils/http/axios';
// 本页仅管理讯工捷使主应用(appCode=im)的版本策略
export function List(params) {
return http.request({ url: '/imAppRelease/list', method: 'GET', params });
return http.request({ url: '/imAppRelease/list', method: 'GET', params: { ...params, appCode: 'im' } });
}
export function Edit(params) {
return http.request({ url: '/imAppRelease/edit', method: 'POST', params });
return http.request({ url: '/imAppRelease/edit', method: 'POST', params: { ...params, appCode: 'im' } });
}
@@ -0,0 +1,215 @@
<template>
<div>
<div class="n-layout-page-header">
<n-card :bordered="false" title="养成应用版本更新" />
</div>
<n-card :bordered="false" class="proCard">
<BasicForm
ref="searchFormRef"
@register="register"
@submit="reloadTable"
@reset="handleReset"
@keyup.enter="reloadTable"
/>
<BasicTable
ref="actionRef"
:columns="columns"
:request="loadDataTable"
:row-key="(row) => row.platform + '-' + row.channel"
:actionColumn="actionColumn"
:scroll-x="scrollX"
:resizeHeightOffset="-10000"
>
<template #tableTitle>
<n-button
type="primary"
:disabled="!hasPermission(['/cultivate/app-release/edit'])"
@click="handleCreate"
>
新增策略
</n-button>
</template>
</BasicTable>
</n-card>
<n-modal
v-model:show="showModal"
preset="dialog"
:show-icon="false"
:title="formValue.id > 0 ? '编辑版本策略' : '新增版本策略'"
:style="{ width: dialogWidth }"
>
<n-form
ref="formRef"
:model="formValue"
:rules="rules"
label-placement="left"
label-width="112"
>
<n-form-item label="平台" path="platform">
<n-select
v-model:value="formValue.platform"
:disabled="formValue.id > 0"
:options="platformOptions"
placeholder="请选择平台"
/>
</n-form-item>
<n-form-item label="渠道" path="channel">
<n-input
v-model:value="formValue.channel"
:disabled="formValue.id > 0"
placeholder="请输入渠道"
/>
</n-form-item>
<n-form-item label="最新版本" path="latestVersion">
<n-input v-model:value="formValue.latestVersion" placeholder="请输入最新版本" />
</n-form-item>
<n-form-item label="最低支持版本" path="minSupportedVersion">
<n-input v-model:value="formValue.minSupportedVersion" placeholder="请输入最低支持版本" />
</n-form-item>
<n-form-item label="更新类型" path="updateType">
<n-select
v-model:value="formValue.updateType"
:options="updateTypeOptions"
placeholder="请选择更新类型"
/>
</n-form-item>
<n-form-item label="再次提醒秒数">
<n-input-number v-model:value="formValue.remindAfterSeconds" :min="0" />
</n-form-item>
<n-form-item label="商店地址">
<n-input v-model:value="formValue.storeUrl" placeholder="请输入商店地址" />
</n-form-item>
<n-form-item label="安装包地址">
<n-input v-model:value="formValue.packageUrl" placeholder="请输入安装包地址" />
</n-form-item>
<n-form-item label="校验值">
<n-input v-model:value="formValue.checksum" placeholder="请输入安装包校验值" />
</n-form-item>
<n-form-item label="更新说明">
<n-input
v-model:value="formValue.releaseNotes"
type="textarea"
:autosize="{ minRows: 4, maxRows: 8 }"
placeholder="请输入更新说明"
/>
</n-form-item>
</n-form>
<template #action>
<n-space>
<n-button @click="showModal = false">取消</n-button>
<n-button type="primary" :loading="submitLoading" @click="handleSubmit">保存</n-button>
</n-space>
</template>
</n-modal>
</div>
</template>
<script lang="ts" setup>
import { computed, h, onMounted, reactive, ref } from 'vue';
import { useMessage } from 'naive-ui';
import { useDictStore } from '@/store/modules/dict';
import { BasicForm, useForm } from '@/components/Form/index';
import { BasicTable, TableAction } from '@/components/Table';
import { usePermission } from '@/hooks/web/usePermission';
import { Edit, List } from '@/api/cultivate/appRelease';
import { adaModalWidth, adaTableScrollX } from '@/utils/hotgo';
import {
columns,
loadOptions,
newState,
rules,
schemas,
State,
} from './model';
const dict = useDictStore();
const message = useMessage();
const { hasPermission } = usePermission();
const actionRef = ref();
const searchFormRef = ref<any>({});
const formRef = ref();
const showModal = ref(false);
const submitLoading = ref(false);
const formValue = ref<State>(newState(null));
const dialogWidth = computed(() => adaModalWidth(720));
const platformOptions = dict.getOption('ImAppPlatformOptions');
const updateTypeOptions = dict.getOption('ImAppUpdateTypeOptions');
const actionColumn = reactive({
width: 100,
title: '操作',
key: 'action',
fixed: 'right',
render(record: State) {
return h(TableAction as any, {
style: 'button',
actions: [
{
label: '编辑',
auth: ['/cultivate/app-release/edit'],
onClick: () => handleEdit(record),
},
],
});
},
});
const scrollX = computed(() => adaTableScrollX(columns, actionColumn.width));
const [register, { setFieldsValue }] = useForm({
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
labelWidth: 90,
schemas,
});
const loadDataTable = async (res) => {
const params = { ...searchFormRef.value?.formModel, ...res };
return await List(params);
};
function reloadTable() {
actionRef.value?.reload();
}
async function handleReset() {
await setFieldsValue({});
reloadTable();
}
function handleCreate() {
formValue.value = newState({
platform: 'android',
channel: 'stable',
latestVersion: '1.0.0',
minSupportedVersion: '1.0.0',
updateType: 'none',
remindAfterSeconds: 0,
});
showModal.value = true;
}
function handleEdit(record: State) {
formValue.value = newState(record);
showModal.value = true;
}
async function handleSubmit() {
submitLoading.value = true;
try {
await formRef.value?.validate();
await Edit({ ...formValue.value });
message.success('操作成功');
showModal.value = false;
reloadTable();
} catch (e: any) {
message.error(e.message || '操作失败');
} finally {
submitLoading.value = false;
}
}
onMounted(async () => {
loadOptions();
reloadTable();
});
</script>
+112
View File
@@ -0,0 +1,112 @@
import { ref } from 'vue';
import { cloneDeep } from 'lodash-es';
import { FormSchema } from '@/components/Form';
import { useDictStore } from '@/store/modules/dict';
const dict = useDictStore();
export class State {
public id = 0;
public platform = 'android';
public channel = 'stable';
public latestVersion = '1.0.0';
public minSupportedVersion = '1.0.0';
public updateType = 'none';
public releaseNotes = '';
public storeUrl = '';
public packageUrl = '';
public checksum = '';
public remindAfterSeconds = 0;
public publishedAt = '';
public updatedAt = '';
constructor(state?: Partial<State>) {
if (state) Object.assign(this, state);
}
}
export function newState(state: State | Record<string, any> | null): State {
if (state !== null) {
if (state instanceof State) return cloneDeep(state);
return new State(state);
}
return new State();
}
export const schemas = ref<FormSchema[]>([
{
field: 'platform',
component: 'NSelect',
label: '平台',
componentProps: {
clearable: true,
options: dict.getOption('ImAppPlatformOptions'),
placeholder: '请选择平台',
},
},
{
field: 'channel',
component: 'NInput',
label: '渠道',
componentProps: {
clearable: true,
placeholder: '请输入渠道',
},
},
]);
export const columns = [
{
title: '平台',
key: 'platform',
align: 'left',
width: 100,
render(row: State) {
return dict.getLabel('ImAppPlatformOptions', row.platform) || row.platform;
},
},
{ title: '渠道', key: 'channel', align: 'left', width: 100 },
{ title: '最新版本', key: 'latestVersion', align: 'left', width: 120 },
{ title: '最低支持', key: 'minSupportedVersion', align: 'left', width: 120 },
{
title: '更新类型',
key: 'updateType',
align: 'left',
width: 110,
render(row: State) {
return dict.getLabel('ImAppUpdateTypeOptions', row.updateType) || row.updateType;
},
},
{ title: '再次提醒', key: 'remindAfterSeconds', align: 'left', width: 110 },
{ title: '商店地址', key: 'storeUrl', align: 'left', width: 220 },
{ title: '安装包地址', key: 'packageUrl', align: 'left', width: 220 },
{ title: '发布时间', key: 'publishedAt', align: 'left', width: 180 },
{ title: '更新时间', key: 'updatedAt', align: 'left', width: 180 },
];
export const rules = {
platform: { required: true, trigger: ['blur', 'change'], type: 'string', message: '请选择平台' },
channel: { required: true, trigger: ['blur', 'input'], type: 'string', message: '请输入渠道' },
latestVersion: {
required: true,
trigger: ['blur', 'input'],
type: 'string',
message: '请输入最新版本',
},
minSupportedVersion: {
required: true,
trigger: ['blur', 'input'],
type: 'string',
message: '请输入最低支持版本',
},
updateType: {
required: true,
trigger: ['blur', 'change'],
type: 'string',
message: '请选择更新类型',
},
};
export function loadOptions() {
dict.loadOptions(['ImAppPlatformOptions', 'ImAppUpdateTypeOptions']);
}