feat(config): 新增配置回收站与还原功能
- 配置列表支持 deleted=1 查询已删除配置 - 新增 POST /admin/config/restore/:id,还原配置并写入 restore 历史 - 前端新增回收站页面,配置列表「新建配置」右侧增加红色入口按钮
This commit is contained in:
@@ -66,6 +66,7 @@ func main() {
|
||||
admin.POST("/config/add", configHandler.Add)
|
||||
admin.POST("/config/update", configHandler.Update)
|
||||
admin.POST("/config/delete/:id", configHandler.Delete)
|
||||
admin.POST("/config/restore/:id", configHandler.Restore)
|
||||
admin.GET("/config/configHistories/:id", configHandler.GetHistory)
|
||||
admin.POST("/config/history/delete/:id", configHandler.DeleteHistory)
|
||||
admin.POST("/config/rollback/:id", configHandler.Rollback)
|
||||
|
||||
@@ -28,6 +28,7 @@ type ConfigListRequest struct {
|
||||
PageSize int `form:"pageSize" binding:"required"`
|
||||
Env string `form:"env"`
|
||||
AppId *int `form:"appId"`
|
||||
Deleted bool `form:"deleted"`
|
||||
}
|
||||
|
||||
func (h *ConfigHandler) List(c *gin.Context) {
|
||||
@@ -48,7 +49,11 @@ func (h *ConfigHandler) List(c *gin.Context) {
|
||||
}
|
||||
|
||||
// 构建查询条件
|
||||
conditions := []string{"enabled = 1"}
|
||||
enabledCond := "enabled = 1"
|
||||
if req.Deleted {
|
||||
enabledCond = "enabled = 0"
|
||||
}
|
||||
conditions := []string{enabledCond}
|
||||
args := make([]interface{}, 0, 4)
|
||||
if req.Env != "" {
|
||||
conditions = append(conditions, "env = ?")
|
||||
@@ -320,6 +325,37 @@ func (h *ConfigHandler) Delete(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, model.WrapperResponse{Code: 0})
|
||||
}
|
||||
|
||||
// Restore 从回收站还原配置
|
||||
func (h *ConfigHandler) Restore(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
var config model.AppConfig
|
||||
has, _ := dao.Engine.ID(id).Get(&config)
|
||||
if !has {
|
||||
c.JSON(http.StatusOK, model.WrapperResponse{Code: 404, Msg: "配置不存在"})
|
||||
return
|
||||
}
|
||||
|
||||
userInfo := middleware.GetUserInfo(c)
|
||||
if err := h.permService.RequirePermission(userInfo, config.GroupId, config.AppId); err != nil {
|
||||
c.JSON(http.StatusForbidden, model.WrapperResponse{Code: 403, Msg: err.Error()})
|
||||
return
|
||||
}
|
||||
if config.Enabled {
|
||||
c.JSON(http.StatusOK, model.WrapperResponse{Code: 400, Msg: "配置未被删除"})
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
h.saveHistory(&config, userInfo.Name, "restore", now)
|
||||
|
||||
config.Enabled = true
|
||||
config.UpdatedBy = userInfo.Name
|
||||
config.UpdatedAt = &now
|
||||
dao.Engine.ID(config.Id).Cols("enabled", "updated_by", "updated_at").Update(&config)
|
||||
|
||||
c.JSON(http.StatusOK, model.WrapperResponse{Code: 0, Data: true})
|
||||
}
|
||||
|
||||
func (h *ConfigHandler) GetHistory(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
|
||||
@@ -38,6 +38,13 @@ export function deleteConfig(id) {
|
||||
})
|
||||
}
|
||||
|
||||
export function restoreConfig(id) {
|
||||
return request({
|
||||
url: `/admin/config/restore/${id}`,
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
export function getConfigHistory(id, params) {
|
||||
return request({
|
||||
url: `/admin/config/configHistories/${id}`,
|
||||
|
||||
@@ -35,6 +35,11 @@ const routes = [
|
||||
name: 'ConfigEdit',
|
||||
component: () => import('../views/config/ConfigEdit.vue')
|
||||
},
|
||||
{
|
||||
path: '/config/recycle',
|
||||
name: 'RecycleBin',
|
||||
component: () => import('../views/config/RecycleBin.vue')
|
||||
},
|
||||
{
|
||||
path: '/app/list',
|
||||
name: 'AppList',
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
<el-card>
|
||||
<div slot="header" style="display: flex; justify-content: flex-end">
|
||||
<el-button type="primary" @click="$router.push('/config/add')">新建配置</el-button>
|
||||
<el-button type="danger" style="margin-left: 10px" @click="$router.push('/config/recycle')">回收站</el-button>
|
||||
</div>
|
||||
<el-table :data="list" v-loading="loading" border stripe>
|
||||
<el-table-column prop="id" label="ID" width="60"></el-table-column>
|
||||
@@ -70,6 +71,7 @@
|
||||
<span v-if="scope.row.operate === 'delete'" style="color: #F56C6C">删除</span>
|
||||
<span v-else-if="scope.row.operate === 'rollback'">回滚</span>
|
||||
<span v-else-if="scope.row.operate === 'update'">更新</span>
|
||||
<span v-else-if="scope.row.operate === 'restore'">还原</span>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
<el-card>
|
||||
<div slot="header" style="display: flex; justify-content: flex-end">
|
||||
<el-button type="primary" @click="$router.push('/config/add')">新建配置</el-button>
|
||||
<el-button type="danger" style="margin-left: 10px" @click="$router.push('/config/recycle')">回收站</el-button>
|
||||
</div>
|
||||
<el-table :data="list" v-loading="loading" border stripe>
|
||||
<el-table-column prop="id" label="ID" width="60"></el-table-column>
|
||||
@@ -65,6 +66,7 @@
|
||||
<span v-if="scope.row.operate === 'delete'" style="color: #F56C6C">删除</span>
|
||||
<span v-else-if="scope.row.operate === 'rollback'">回滚</span>
|
||||
<span v-else-if="scope.row.operate === 'update'">更新</span>
|
||||
<span v-else-if="scope.row.operate === 'restore'">还原</span>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-card style="margin-bottom: 16px">
|
||||
<el-form :inline="true" @submit.native.prevent="handleSearch">
|
||||
<el-form-item label="环境">
|
||||
<el-select v-model="query.env" placeholder="请选择" clearable style="width: 160px">
|
||||
<el-option v-for="p in profiles" :key="p.value" :label="p.label" :value="p.value"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="应用">
|
||||
<el-select v-model="query.appId" placeholder="请选择" clearable filterable style="width: 200px">
|
||||
<el-option v-for="app in apps" :key="app.value" :label="app.label" :value="parseInt(app.value)"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||||
<el-button @click="handleReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<el-card>
|
||||
<div slot="header">回收站(仅显示已删除配置)</div>
|
||||
<el-table :data="list" v-loading="loading" border stripe>
|
||||
<el-table-column prop="id" label="ID" width="60"></el-table-column>
|
||||
<el-table-column prop="name" label="配置名"></el-table-column>
|
||||
<el-table-column prop="env" label="环境" width="80"></el-table-column>
|
||||
<el-table-column prop="appNames" label="所属应用"></el-table-column>
|
||||
<el-table-column prop="version" label="版本" width="100"></el-table-column>
|
||||
<el-table-column prop="updatedBy" label="删除人" width="120"></el-table-column>
|
||||
<el-table-column label="删除时间" width="180">
|
||||
<template slot-scope="scope">
|
||||
{{ formatTime(scope.row.updatedAt) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="120">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" @click="handleRestore(scope.row)">还原</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
style="margin-top: 15px; text-align: right"
|
||||
@current-change="handlePageChange"
|
||||
:current-page="query.pageNo"
|
||||
:page-size="query.pageSize"
|
||||
:total="total"
|
||||
layout="total, prev, pager, next"
|
||||
></el-pagination>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getConfigList, restoreConfig } from '../../api/config'
|
||||
import request from '../../api/request'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
list: [],
|
||||
loading: false,
|
||||
total: 0,
|
||||
query: { pageNo: 1, pageSize: 10, env: '', appId: null },
|
||||
profiles: [],
|
||||
apps: []
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadData()
|
||||
this.loadProfiles()
|
||||
this.loadApps()
|
||||
},
|
||||
methods: {
|
||||
handleSearch() {
|
||||
this.query.pageNo = 1
|
||||
this.loadData()
|
||||
},
|
||||
handleReset() {
|
||||
this.query = { pageNo: 1, pageSize: 10, env: '', appId: null }
|
||||
this.loadData()
|
||||
},
|
||||
async loadData() {
|
||||
this.loading = true
|
||||
try {
|
||||
const params = {
|
||||
pageNo: this.query.pageNo,
|
||||
pageSize: this.query.pageSize,
|
||||
deleted: 1
|
||||
}
|
||||
if (this.query.env) {
|
||||
params.env = this.query.env
|
||||
}
|
||||
if (this.query.appId) {
|
||||
params.appId = this.query.appId
|
||||
}
|
||||
const res = await getConfigList(params)
|
||||
this.list = res.data || []
|
||||
this.total = res.total || 0
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
async loadProfiles() {
|
||||
try {
|
||||
const res = await request({ url: '/admin/profile/options', method: 'get' })
|
||||
this.profiles = res.data || []
|
||||
} catch (e) {}
|
||||
},
|
||||
async loadApps() {
|
||||
try {
|
||||
const res = await request({ url: '/admin/app/options', method: 'get' })
|
||||
this.apps = res.data || []
|
||||
} catch (e) {}
|
||||
},
|
||||
handlePageChange(page) {
|
||||
this.query.pageNo = page
|
||||
this.loadData()
|
||||
},
|
||||
async handleRestore(row) {
|
||||
try {
|
||||
await this.$confirm(`确定还原配置「${row.name}」?`)
|
||||
await restoreConfig(row.id)
|
||||
this.$message.success('还原成功')
|
||||
this.loadData()
|
||||
} catch (e) {}
|
||||
},
|
||||
formatTime(time) {
|
||||
if (!time) return ''
|
||||
const d = new Date(time)
|
||||
const pad = n => String(n).padStart(2, '0')
|
||||
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user