fix(admin): restore Vite admin source files

This commit is contained in:
2026-08-17 20:02:36 +08:00
parent 8fe3a89c36
commit ac5c0b2dd7
114 changed files with 17272 additions and 0 deletions
@@ -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,
}
}