fix(admin): restore Vite admin source files
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
import { ref, watch, computed } from 'vue'
|
||||
|
||||
/**
|
||||
* useDirtyState — tracks whether a form has unsaved changes.
|
||||
*
|
||||
* Usage:
|
||||
* const { isDirty, markDirty, markClean, confirmLeave } = useDirtyState()
|
||||
*
|
||||
* Call markDirty() whenever form data changes from its initial state.
|
||||
* Call markClean() after a successful save.
|
||||
* confirmLeave() returns a boolean — false means the user cancelled navigation.
|
||||
*/
|
||||
export function useDirtyState() {
|
||||
const dirty = ref(false)
|
||||
const saveCount = ref(0)
|
||||
|
||||
function markDirty() {
|
||||
dirty.value = true
|
||||
}
|
||||
|
||||
function markClean() {
|
||||
dirty.value = false
|
||||
saveCount.value++
|
||||
}
|
||||
|
||||
/** Reset dirty without incrementing save count (e.g. on initial load) */
|
||||
function reset() {
|
||||
dirty.value = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm navigation away from dirty form.
|
||||
* Returns true if safe to leave, false if user cancelled.
|
||||
*/
|
||||
function confirmLeave() {
|
||||
if (!dirty.value) return true
|
||||
return window.confirm('您有未保存的更改,确定要离开此页面吗?')
|
||||
}
|
||||
|
||||
const isDirty = computed(() => dirty.value)
|
||||
|
||||
return {
|
||||
isDirty,
|
||||
markDirty,
|
||||
markClean,
|
||||
reset,
|
||||
confirmLeave,
|
||||
saveCount,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
import { ref, watch, onMounted, onBeforeUnmount, computed } from 'vue'
|
||||
import { useDirtyState } from './useDirtyState'
|
||||
|
||||
/**
|
||||
* useSectionForm — standard composable for schema-driven admin settings pages.
|
||||
*
|
||||
* Encapsulates: data loading, dirty tracking, save/reload logic, and loading states.
|
||||
*
|
||||
* Usage:
|
||||
* const {
|
||||
* formData, loading, saving, loadError,
|
||||
* dirty, load, save, reset,
|
||||
* } = useSectionForm({
|
||||
* fetch: () => api.getBasicSettings(),
|
||||
* submit: (data) => api.saveBasicSettings(data),
|
||||
* defaults: { comment_enabled: true, lazy_load: true },
|
||||
* })
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {Function} options.fetch — async function returning current settings
|
||||
* @param {Function} options.submit — async function accepting form data to save
|
||||
* @param {Object} options.defaults — default values merged under fetched data
|
||||
* @param {Function} [options.onLoaded] — callback after data is loaded
|
||||
* @param {Function} [options.onSaved] — callback after successful save
|
||||
* @param {Function} [options.transform] — transform fetched data before applying
|
||||
*/
|
||||
export function useSectionForm(options) {
|
||||
const {
|
||||
fetch: fetchData,
|
||||
submit: submitData,
|
||||
defaults = {},
|
||||
onLoaded,
|
||||
onSaved,
|
||||
transform,
|
||||
} = options
|
||||
|
||||
const formData = ref({ ...defaults })
|
||||
const initialSnapshot = ref('')
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const loadError = ref(null)
|
||||
|
||||
const { isDirty, markDirty, markClean, reset: resetDirty, confirmLeave } = useDirtyState()
|
||||
|
||||
/** Snapshot the current form state for dirty comparison */
|
||||
function snapshot() {
|
||||
initialSnapshot.value = JSON.stringify(formData.value)
|
||||
}
|
||||
|
||||
/** Check if form data differs from snapshot */
|
||||
function checkDirty() {
|
||||
const current = JSON.stringify(formData.value)
|
||||
if (current !== initialSnapshot.value) {
|
||||
markDirty()
|
||||
} else {
|
||||
resetDirty()
|
||||
}
|
||||
}
|
||||
|
||||
/** Deep watch form data for dirty tracking */
|
||||
let watcherHandle = null
|
||||
|
||||
function startWatching() {
|
||||
if (watcherHandle) watcherHandle()
|
||||
watcherHandle = watch(formData, checkDirty, { deep: true })
|
||||
}
|
||||
|
||||
/** Load settings from API */
|
||||
async function load() {
|
||||
loading.value = true
|
||||
loadError.value = null
|
||||
try {
|
||||
const data = await fetchData()
|
||||
const raw = data?.data ?? data ?? {}
|
||||
const merged = { ...defaults, ...(transform ? transform(raw) : raw) }
|
||||
formData.value = merged
|
||||
snapshot()
|
||||
startWatching()
|
||||
onLoaded?.(merged)
|
||||
} catch (err) {
|
||||
loadError.value = err?.message || '加载失败'
|
||||
console.error('[useSectionForm] load error:', err)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** Save current form data */
|
||||
async function save() {
|
||||
saving.value = true
|
||||
try {
|
||||
await submitData(formData.value)
|
||||
snapshot()
|
||||
markClean()
|
||||
onSaved?.(formData.value)
|
||||
} catch (err) {
|
||||
console.error('[useSectionForm] save error:', err)
|
||||
throw err // let caller handle the error display
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** Reload form data from API (discards changes) */
|
||||
async function reload() {
|
||||
if (watcherHandle) watcherHandle()
|
||||
resetDirty()
|
||||
await load()
|
||||
}
|
||||
|
||||
/** Reset form to defaults */
|
||||
function resetToDefaults() {
|
||||
if (watcherHandle) watcherHandle()
|
||||
formData.value = { ...defaults }
|
||||
snapshot()
|
||||
startWatching()
|
||||
resetDirty()
|
||||
}
|
||||
|
||||
// Auto-load on mount
|
||||
onMounted(load)
|
||||
|
||||
// Cleanup watcher
|
||||
onBeforeUnmount(() => {
|
||||
if (watcherHandle) watcherHandle()
|
||||
})
|
||||
|
||||
return {
|
||||
formData,
|
||||
loading,
|
||||
saving,
|
||||
loadError,
|
||||
dirty: isDirty,
|
||||
load,
|
||||
save,
|
||||
reload,
|
||||
resetToDefaults,
|
||||
confirmLeave,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user