feat: 编辑器增加「重置」和「丢弃预览版」按钮,支持回退预览版快照
This commit is contained in:
@@ -462,6 +462,15 @@ public class AdminController {
|
|||||||
return ApiResponse.success(result);
|
return ApiResponse.success(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 丢弃预览版快照(删除所有 draft),站点配置回退到正式版 */
|
||||||
|
@PostMapping("/sites/{siteId}/discard-draft")
|
||||||
|
public ApiResponse<Map<String, Object>> discardDraft(@PathVariable("siteId") String siteId,
|
||||||
|
HttpServletRequest request) {
|
||||||
|
checkAdmin(request);
|
||||||
|
int deleted = snapshotService.discardDraft(siteId);
|
||||||
|
return ApiResponse.success(Map.of("siteId", siteId, "deleted", deleted));
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/sites")
|
@GetMapping("/sites")
|
||||||
public ApiResponse<List<com.par.core.dto.SiteDTO>> listAllSites(HttpServletRequest request) {
|
public ApiResponse<List<com.par.core.dto.SiteDTO>> listAllSites(HttpServletRequest request) {
|
||||||
checkAdmin(request);
|
checkAdmin(request);
|
||||||
|
|||||||
@@ -23,4 +23,8 @@ public interface SiteSnapshotMapper extends BaseMapper<SiteSnapshot> {
|
|||||||
/** 查询站点最新快照:draft 优先,无 draft 则取 published */
|
/** 查询站点最新快照:draft 优先,无 draft 则取 published */
|
||||||
@Select("SELECT * FROM site_snapshots WHERE site_id = #{siteId} AND status IN ('draft','published') ORDER BY CASE WHEN status = 'draft' THEN 0 ELSE 1 END, version DESC LIMIT 1")
|
@Select("SELECT * FROM site_snapshots WHERE site_id = #{siteId} AND status IN ('draft','published') ORDER BY CASE WHEN status = 'draft' THEN 0 ELSE 1 END, version DESC LIMIT 1")
|
||||||
SiteSnapshot selectLatestWithDraft(@Param("siteId") String siteId);
|
SiteSnapshot selectLatestWithDraft(@Param("siteId") String siteId);
|
||||||
|
|
||||||
|
/** 删除站点指定状态的快照 */
|
||||||
|
@org.apache.ibatis.annotations.Delete("DELETE FROM site_snapshots WHERE site_id = #{siteId} AND status = #{status}")
|
||||||
|
int deleteBySiteIdAndStatus(@Param("siteId") String siteId, @Param("status") String status);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,4 +71,10 @@ public interface SiteSnapshotService {
|
|||||||
* @return 成功发布的站点数
|
* @return 成功发布的站点数
|
||||||
*/
|
*/
|
||||||
int publishChanged();
|
int publishChanged();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 丢弃预览版快照(删除所有 draft 记录),回退到正式版
|
||||||
|
* @return 删除的 draft 记录数
|
||||||
|
*/
|
||||||
|
int discardDraft(String siteId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -333,6 +333,19 @@ public class SiteSnapshotServiceImpl implements SiteSnapshotService {
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public int discardDraft(String siteId) {
|
||||||
|
int deleted = snapshotMapper.deleteBySiteIdAndStatus(siteId, "draft");
|
||||||
|
if (deleted > 0) {
|
||||||
|
jsonCache.invalidate(draftKey(siteId));
|
||||||
|
log.info("预览版已丢弃: siteId={}, 删除 {} 条记录", siteId, deleted);
|
||||||
|
} else {
|
||||||
|
log.info("站点 {} 无预览版可丢弃", siteId);
|
||||||
|
}
|
||||||
|
return deleted;
|
||||||
|
}
|
||||||
|
|
||||||
/** 将快照转为 mediabot 可用的 JSON 字符串 */
|
/** 将快照转为 mediabot 可用的 JSON 字符串 */
|
||||||
private String toBundleJson(SiteSnapshot snapshot) {
|
private String toBundleJson(SiteSnapshot snapshot) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -48,6 +48,7 @@
|
|||||||
<el-option v-for="v in allConfigVersions" :key="v.id" :label="'v'+v.version+' '+v.status" :value="v.id" />
|
<el-option v-for="v in allConfigVersions" :key="v.id" :label="'v'+v.version+' '+v.status" :value="v.id" />
|
||||||
</el-select>
|
</el-select>
|
||||||
<el-button size="small" @click="revertScript">回滚</el-button>
|
<el-button size="small" @click="revertScript">回滚</el-button>
|
||||||
|
<el-button v-if="hasUnsavedChanges" size="small" @click="resetScript" type="warning">重置</el-button>
|
||||||
<el-button size="small" type="primary" @click="saveScript" :loading="saving">保存</el-button>
|
<el-button size="small" type="primary" @click="saveScript" :loading="saving">保存</el-button>
|
||||||
<el-button size="small" type="danger" @click="deleteScript" :loading="deleting">删除</el-button>
|
<el-button size="small" type="danger" @click="deleteScript" :loading="deleting">删除</el-button>
|
||||||
<template v-if="selectedConfig?.status==='pending'">
|
<template v-if="selectedConfig?.status==='pending'">
|
||||||
@@ -58,6 +59,7 @@
|
|||||||
<!-- 快照发布 -->
|
<!-- 快照发布 -->
|
||||||
<el-button size="small" @click="savePreview" :loading="savingPreview" v-if="snapshotStatus">保存预览版</el-button>
|
<el-button size="small" @click="savePreview" :loading="savingPreview" v-if="snapshotStatus">保存预览版</el-button>
|
||||||
<el-button size="small" type="success" @click="publishSnapshot" :loading="publishing" v-if="snapshotStatus?.draft.exists || snapshotStatus?.hasPendingPublish">发布正式版</el-button>
|
<el-button size="small" type="success" @click="publishSnapshot" :loading="publishing" v-if="snapshotStatus?.draft.exists || snapshotStatus?.hasPendingPublish">发布正式版</el-button>
|
||||||
|
<el-button size="small" type="danger" @click="discardDraftSnapshot" :loading="discardingDraft" v-if="snapshotStatus?.draft.exists">丢弃预览版</el-button>
|
||||||
<el-tag v-if="hasUnsavedChanges" size="small" type="danger" effect="dark">未保存</el-tag>
|
<el-tag v-if="hasUnsavedChanges" size="small" type="danger" effect="dark">未保存</el-tag>
|
||||||
</template>
|
</template>
|
||||||
<el-button v-if="!scriptExpanded && selectedConfigId" size="small" type="primary" @click="saveScript" :loading="saving">保存</el-button>
|
<el-button v-if="!scriptExpanded && selectedConfigId" size="small" type="primary" @click="saveScript" :loading="saving">保存</el-button>
|
||||||
@@ -161,7 +163,7 @@ const siteId = route.params.siteId
|
|||||||
const loading = ref(false), parseLoading = ref(false), scriptExpanded = ref(true), historyVersion = ref(null), urlFilter = ref(''), deleting = ref(false)
|
const loading = ref(false), parseLoading = ref(false), scriptExpanded = ref(true), historyVersion = ref(null), urlFilter = ref(''), deleting = ref(false)
|
||||||
const selectedConfigId = ref(null), selectedConfig = ref(null)
|
const selectedConfigId = ref(null), selectedConfig = ref(null)
|
||||||
const scriptContent = ref(''), originalScript = ref(''), saving = ref(false), approving = ref(false), rejecting = ref(false)
|
const scriptContent = ref(''), originalScript = ref(''), saving = ref(false), approving = ref(false), rejecting = ref(false)
|
||||||
const savingPreview = ref(false), publishing = ref(false)
|
const savingPreview = ref(false), publishing = ref(false), discardingDraft = ref(false)
|
||||||
const snapshotStatus = ref(null)
|
const snapshotStatus = ref(null)
|
||||||
const allConfigVersions = ref([]), allResults = ref([])
|
const allConfigVersions = ref([]), allResults = ref([])
|
||||||
const pageParsed = ref({}), reparsingUrls = reactive(new Set()), expanded = reactive(new Set())
|
const pageParsed = ref({}), reparsingUrls = reactive(new Set()), expanded = reactive(new Set())
|
||||||
@@ -254,6 +256,22 @@ const publishSnapshot = async () => {
|
|||||||
finally { publishing.value = false }
|
finally { publishing.value = false }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const resetScript = () => {
|
||||||
|
scriptContent.value = originalScript.value
|
||||||
|
ElMessage.success('已重置到上次保存的状态')
|
||||||
|
}
|
||||||
|
|
||||||
|
const discardDraftSnapshot = async () => {
|
||||||
|
try { await ElMessageBox.confirm('确认丢弃预览版?将回退到正式版。', '丢弃预览版', {confirmButtonText:'确认丢弃',type:'warning'}) } catch { return }
|
||||||
|
discardingDraft.value = true
|
||||||
|
try {
|
||||||
|
await api.raw.post('/admin/sites/' + siteId + '/discard-draft')
|
||||||
|
ElMessage.success('预览版已丢弃,当前为正式版')
|
||||||
|
fetchSnapshotStatus()
|
||||||
|
} catch { ElMessage.error('丢弃预览版失败') }
|
||||||
|
finally { discardingDraft.value = false }
|
||||||
|
}
|
||||||
|
|
||||||
watch(selectedConfig, (v) => { if (v) loadScript() })
|
watch(selectedConfig, (v) => { if (v) loadScript() })
|
||||||
|
|
||||||
const loadHistoryScript = async (configId) => {
|
const loadHistoryScript = async (configId) => {
|
||||||
|
|||||||
@@ -41,9 +41,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div style="display:flex;gap:8px">
|
<div style="display:flex;gap:8px">
|
||||||
<el-button size="small" type="primary" @click="saveRules" :loading="saving">保存</el-button>
|
<el-button size="small" type="primary" @click="saveRules" :loading="saving">保存</el-button>
|
||||||
|
<el-button v-if="hasUnsavedChanges" size="small" @click="resetEditor" type="warning">重置</el-button>
|
||||||
<el-button size="small" @click="uploadRules" :loading="uploading">上传</el-button>
|
<el-button size="small" @click="uploadRules" :loading="uploading">上传</el-button>
|
||||||
<el-button v-if="snapshotStatus" size="small" @click="savePreview" :loading="savingPreview">保存预览版</el-button>
|
<el-button v-if="snapshotStatus" size="small" @click="savePreview" :loading="savingPreview">保存预览版</el-button>
|
||||||
<el-button v-if="snapshotStatus?.draft.exists" size="small" type="success" @click="publishSnapshot" :loading="publishing">发布正式版</el-button>
|
<el-button v-if="snapshotStatus?.draft.exists" size="small" type="success" @click="publishSnapshot" :loading="publishing">发布正式版</el-button>
|
||||||
|
<el-button v-if="snapshotStatus?.draft.exists" size="small" type="danger" @click="discardDraft" :loading="discardingDraft">丢弃预览版</el-button>
|
||||||
<el-button size="small" @click="editType=''">返回列表</el-button>
|
<el-button size="small" @click="editType=''">返回列表</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -279,7 +281,7 @@ const presets = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
// ============ 状态 ============
|
// ============ 状态 ============
|
||||||
const loading = ref(false), saving = ref(false), uploading = ref(false), savingPreview = ref(false), publishing = ref(false)
|
const loading = ref(false), saving = ref(false), uploading = ref(false), savingPreview = ref(false), publishing = ref(false), discardingDraft = ref(false)
|
||||||
const sites = ref([]), rulesMap = ref({})
|
const sites = ref([]), rulesMap = ref({})
|
||||||
const editType = ref(''), editorJson = ref('[]'), originalJson = ref('')
|
const editType = ref(''), editorJson = ref('[]'), originalJson = ref('')
|
||||||
const snapshotStatus = ref(null)
|
const snapshotStatus = ref(null)
|
||||||
@@ -403,6 +405,22 @@ const publishSnapshot = async () => {
|
|||||||
finally { publishing.value = false }
|
finally { publishing.value = false }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const resetEditor = () => {
|
||||||
|
editorJson.value = originalJson.value
|
||||||
|
ElMessage.success('已重置到上次保存的状态')
|
||||||
|
}
|
||||||
|
|
||||||
|
const discardDraft = async () => {
|
||||||
|
try { await ElMessageBox.confirm('确认丢弃预览版?将回退到正式版。', '丢弃预览版', {confirmButtonText:'确认丢弃',type:'warning'}) } catch { return }
|
||||||
|
discardingDraft.value = true
|
||||||
|
try {
|
||||||
|
await api.raw.post('/admin/sites/' + editType.value + '/discard-draft')
|
||||||
|
ElMessage.success('预览版已丢弃,当前为正式版')
|
||||||
|
fetchSnapshotStatus()
|
||||||
|
} catch { ElMessage.error('丢弃预览版失败') }
|
||||||
|
finally { discardingDraft.value = false }
|
||||||
|
}
|
||||||
|
|
||||||
const sync = () => { editorJson.value = JSON.stringify(parsed.value, null, 2) }
|
const sync = () => { editorJson.value = JSON.stringify(parsed.value, null, 2) }
|
||||||
|
|
||||||
const confirmRemove = async (idx) => {
|
const confirmRemove = async (idx) => {
|
||||||
|
|||||||
@@ -53,9 +53,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div style="display:flex;gap:8px">
|
<div style="display:flex;gap:8px">
|
||||||
<el-button size="small" type="primary" @click="savePages" :loading="saving">保存</el-button>
|
<el-button size="small" type="primary" @click="savePages" :loading="saving">保存</el-button>
|
||||||
|
<el-button v-if="hasUnsavedChanges" size="small" @click="resetEditor" type="warning">重置</el-button>
|
||||||
<el-button size="small" @click="uploadPages" :loading="uploading">上传</el-button>
|
<el-button size="small" @click="uploadPages" :loading="uploading">上传</el-button>
|
||||||
<el-button v-if="!isDefaultEditor && snapshotStatus" size="small" @click="savePreview" :loading="savingPreview">保存预览版</el-button>
|
<el-button v-if="!isDefaultEditor && snapshotStatus" size="small" @click="savePreview" :loading="savingPreview">保存预览版</el-button>
|
||||||
<el-button v-if="!isDefaultEditor && snapshotStatus?.draft.exists" size="small" type="success" @click="publishSnapshot" :loading="publishing">发布正式版</el-button>
|
<el-button v-if="!isDefaultEditor && snapshotStatus?.draft.exists" size="small" type="success" @click="publishSnapshot" :loading="publishing">发布正式版</el-button>
|
||||||
|
<el-button v-if="!isDefaultEditor && snapshotStatus?.draft.exists" size="small" type="danger" @click="discardDraft" :loading="discardingDraft">丢弃预览版</el-button>
|
||||||
<el-button size="small" type="danger" @click="deletePages" :loading="deleting">删除</el-button>
|
<el-button size="small" type="danger" @click="deletePages" :loading="deleting">删除</el-button>
|
||||||
<el-button size="small" @click="editSiteId=''">返回列表</el-button>
|
<el-button size="small" @click="editSiteId=''">返回列表</el-button>
|
||||||
</div>
|
</div>
|
||||||
@@ -122,7 +124,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import api from '../api'; import { Refresh } from '@element-plus/icons-vue'
|
import api from '../api'; import { Refresh } from '@element-plus/icons-vue'
|
||||||
|
|
||||||
const loading = ref(false), saving = ref(false), uploading = ref(false), deleting = ref(false), savingPreview = ref(false), publishing = ref(false)
|
const loading = ref(false), saving = ref(false), uploading = ref(false), deleting = ref(false), savingPreview = ref(false), publishing = ref(false), discardingDraft = ref(false)
|
||||||
const sites = ref([]), pagesMap = ref({}), sitesList = ref([])
|
const sites = ref([]), pagesMap = ref({}), sitesList = ref([])
|
||||||
const editSiteId = ref(''), editorJson = ref('{}'), originalJson = ref('')
|
const editSiteId = ref(''), editorJson = ref('{}'), originalJson = ref('')
|
||||||
const snapshotStatus = ref(null)
|
const snapshotStatus = ref(null)
|
||||||
@@ -247,6 +249,22 @@ const publishSnapshot = async () => {
|
|||||||
finally { publishing.value = false }
|
finally { publishing.value = false }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const resetEditor = () => {
|
||||||
|
editorJson.value = originalJson.value
|
||||||
|
ElMessage.success('已重置到上次保存的状态')
|
||||||
|
}
|
||||||
|
|
||||||
|
const discardDraft = async () => {
|
||||||
|
try { await ElMessageBox.confirm('确认丢弃预览版?将回退到正式版。', '丢弃预览版', {confirmButtonText:'确认丢弃',type:'warning'}) } catch { return }
|
||||||
|
discardingDraft.value = true
|
||||||
|
try {
|
||||||
|
await api.raw.post('/admin/sites/' + editSiteId.value + '/discard-draft')
|
||||||
|
ElMessage.success('预览版已丢弃,当前为正式版')
|
||||||
|
fetchSnapshotStatus()
|
||||||
|
} catch { ElMessage.error('丢弃预览版失败') }
|
||||||
|
finally { discardingDraft.value = false }
|
||||||
|
}
|
||||||
|
|
||||||
const remove = (idx) => { parsed.value.splice(idx,1); syncToJson() }
|
const remove = (idx) => { parsed.value.splice(idx,1); syncToJson() }
|
||||||
const addEntry = () => { parsed.value.push({key:'',method:'GET',url:'{base}'}); syncToJson() }
|
const addEntry = () => { parsed.value.push({key:'',method:'GET',url:'{base}'}); syncToJson() }
|
||||||
const deletePages = async () => {
|
const deletePages = async () => {
|
||||||
|
|||||||
@@ -62,9 +62,11 @@
|
|||||||
<el-tag v-if="hasUnsavedChanges" size="small" type="danger" effect="dark">未保存</el-tag>
|
<el-tag v-if="hasUnsavedChanges" size="small" type="danger" effect="dark">未保存</el-tag>
|
||||||
</div>
|
</div>
|
||||||
<div style="display:flex;gap:8px">
|
<div style="display:flex;gap:8px">
|
||||||
|
<el-button size="small" @click="resetEditor" v-if="hasUnsavedChanges" type="warning">重置</el-button>
|
||||||
<el-button size="small" @click="editSiteId=''">返回列表</el-button>
|
<el-button size="small" @click="editSiteId=''">返回列表</el-button>
|
||||||
<el-button size="small" @click="saveSite" :loading="saving">保存站点</el-button>
|
<el-button size="small" @click="saveSite" :loading="saving">保存站点</el-button>
|
||||||
<el-button size="small" @click="savePreview" :loading="savingPreview">保存预览版</el-button>
|
<el-button size="small" @click="savePreview" :loading="savingPreview">保存预览版</el-button>
|
||||||
|
<el-button size="small" @click="discardDraft" :loading="discarding" v-if="snapshotStatus?.draft.exists" type="danger">丢弃预览版</el-button>
|
||||||
<el-button size="small" type="success" @click="publishSite" :loading="publishing">发布正式版</el-button>
|
<el-button size="small" type="success" @click="publishSite" :loading="publishing">发布正式版</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -115,7 +117,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import api from '../api'
|
import api from '../api'
|
||||||
|
|
||||||
const loading = ref(false), saving = ref(false), savingPreview = ref(false), publishing = ref(false), pendingCount = ref(0)
|
const loading = ref(false), saving = ref(false), savingPreview = ref(false), publishing = ref(false), discarding = ref(false), pendingCount = ref(0)
|
||||||
const sites = ref([]), editSiteId = ref(''), editorJson = ref('{}'), jsonError = ref(false), originalJson = ref('')
|
const sites = ref([]), editSiteId = ref(''), editorJson = ref('{}'), jsonError = ref(false), originalJson = ref('')
|
||||||
const snapshotStatus = ref(null)
|
const snapshotStatus = ref(null)
|
||||||
const form = reactive({ siteId:'', namesStr:'', urlsStr:'', tagsStr:'', desc:'', siteType:'nexusphp', status:'active' })
|
const form = reactive({ siteId:'', namesStr:'', urlsStr:'', tagsStr:'', desc:'', siteType:'nexusphp', status:'active' })
|
||||||
@@ -197,6 +199,22 @@ const publishSite = async () => {
|
|||||||
finally { publishing.value = false }
|
finally { publishing.value = false }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const resetEditor = () => {
|
||||||
|
editorJson.value = originalJson.value
|
||||||
|
ElMessage.success('已重置到上次保存的状态')
|
||||||
|
}
|
||||||
|
|
||||||
|
const discardDraft = async () => {
|
||||||
|
try { await ElMessageBox.confirm('确认丢弃预览版?将回退到正式版。', '丢弃预览版', {confirmButtonText:'确认丢弃',type:'warning'}) } catch { return }
|
||||||
|
discarding.value = true
|
||||||
|
try {
|
||||||
|
await api.raw.post('/admin/sites/' + editSiteId.value + '/discard-draft')
|
||||||
|
ElMessage.success('预览版已丢弃,当前为正式版')
|
||||||
|
fetchSnapshotStatus()
|
||||||
|
} catch { ElMessage.error('丢弃预览版失败') }
|
||||||
|
finally { discarding.value = false }
|
||||||
|
}
|
||||||
|
|
||||||
const saveSite = async () => {
|
const saveSite = async () => {
|
||||||
saving.value = true
|
saving.value = true
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user