feat(embed-slots): soft-delete slots — admin entry, ov shutdown via enabled=false snapshot

- 契约 deleteEmbedSlot(DELETE /admin/embed-slots/{id} → 204 幂等;admin 专属)
- cn schema:embed_slots.deletedAt(迁移 20260923120000,本地已应用)
- softDelete:乐观锁 claim + outbox slot.updated(payload enabled 强制 false
  ——ov 副本无墓碑,enabled=false 快照即公开端 404 下线;priority=high)+ audit
- 列表/加载/更新过滤 deletedAt → 同码 404
- Admin 列表操作「删除」(确认:已接入网站将停止展示、不可恢复)
- API 56/56、admin 87/87 + build 全绿

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-09-23 13:37:39 +08:00
parent aa366c4268
commit 903962a46b
12 changed files with 205 additions and 142 deletions
+5
View File
@@ -23,3 +23,8 @@ export function fetchCreateEmbedSlot(data: Api.EmbedSlot.CreatePayload) {
export function fetchUpdateEmbedSlot(id: string, data: Api.EmbedSlot.UpdatePayload) {
return request.patch<Api.EmbedSlot.Item>({ url: `/v1/admin/embed-slots/${id}`, data })
}
/** 软删除展示位(deletedAt 标记:列表隐藏、ov 快照下线;幂等 204) */
export function fetchDeleteEmbedSlot(id: string) {
return request.del<void>({ url: `/v1/admin/embed-slots/${id}` })
}
+4 -1
View File
@@ -820,7 +820,10 @@
"actionBind": "Bind Album",
"actionCode": "Embed Code",
"subtitle": "Manage floating photo widgets: album binding, toggles and embed code.",
"tableTitle": "Slots"
"tableTitle": "Slots",
"actionDelete": "Delete",
"deleteConfirm": "Delete slot \"{name}\"? Sites using it will stop showing it; this cannot be undone.",
"deleted": "Slot deleted"
},
"form": {
"nameLabel": "Name",
+4 -1
View File
@@ -820,7 +820,10 @@
"actionBind": "绑定相册",
"actionCode": "嵌入代码",
"subtitle": "管理官网悬浮照片展示位:绑定相册、启停与接入代码。",
"tableTitle": "展示位列表"
"tableTitle": "展示位列表",
"actionDelete": "删除",
"deleteConfirm": "确定删除展示位「{name}」?删除后已接入的网站将停止展示,且不可恢复。",
"deleted": "展示位已删除"
},
"form": {
"nameLabel": "名称",
@@ -89,6 +89,9 @@
<ElButton link type="primary" data-test="slot-code-btn" @click="openCode(row)">
{{ $t('embedSlotsManage.list.actionCode') }}
</ElButton>
<ElButton link type="danger" data-test="slot-delete-btn" @click="remove(row)">
{{ $t('embedSlotsManage.list.actionDelete') }}
</ElButton>
</template>
</ElTableColumn>
@@ -124,11 +127,11 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
import { ElMessage, ElMessageBox } from 'element-plus'
import SlotFormDialog from './SlotFormDialog.vue'
import SlotBindDialog from './SlotBindDialog.vue'
import EmbedCodeDialog from './EmbedCodeDialog.vue'
import { fetchEmbedSlots, fetchUpdateEmbedSlot } from '@/api/embedSlots'
import { fetchDeleteEmbedSlot, fetchEmbedSlots, fetchUpdateEmbedSlot } from '@/api/embedSlots'
import { useCursorList } from '@/views/albums/composables/useCursorList'
import { formatDateTime } from '@/utils/albums/format'
@@ -207,6 +210,26 @@
codeVisible.value = true
}
/** 软删除:确认(已接入网站将停止展示)→ DELETE → 重载列表 */
async function remove(row: Api.EmbedSlot.Item): Promise<void> {
try {
await ElMessageBox.confirm(
t('embedSlotsManage.list.deleteConfirm', { name: row.name }),
t('common.tips'),
{
type: 'warning',
confirmButtonText: t('common.confirm'),
cancelButtonText: t('common.cancel')
}
)
} catch {
return // 用户取消
}
await fetchDeleteEmbedSlot(row.id)
ElMessage.success(t('embedSlotsManage.list.deleted'))
loadFirst()
}
onMounted(() => {
void loadFirst()
})