chore: initial commit

This commit is contained in:
2026-07-30 17:59:43 +08:00
commit dd0d365b03
73 changed files with 9898 additions and 0 deletions
@@ -0,0 +1,101 @@
import { computed, defineComponent, nextTick, reactive, ref } from 'vue'
import { mount } from '@vue/test-utils'
import { beforeEach, describe, expect, it } from 'vitest'
import { createPinia, setActivePinia } from 'pinia'
import { useUnsavedWorkRegistration } from '@/composables/useUnsavedWorkRegistration'
import { useUnsavedWorkStore } from '@/stores/unsavedWork'
interface HarnessModel {
name: string
}
function mountHarness() {
const isOpen = ref(false)
const label = ref('客户资料')
const requiredPermission = ref<string | null>('customer:create')
const model = reactive<HarnessModel>({ name: '' })
let captureBaseline: () => void = () => undefined
const wrapper = mount(defineComponent({
setup() {
const registration = useUnsavedWorkRegistration({
key: 'customer-form',
label: computed(() => label.value),
isOpen,
model,
requiredPermission: computed(() => requiredPermission.value),
})
captureBaseline = registration.captureBaseline
return () => null
},
}))
return { wrapper, isOpen, label, requiredPermission, model, captureBaseline }
}
describe('useUnsavedWorkRegistration', () => {
beforeEach(() => {
setActivePinia(createPinia())
})
it('tracks open model changes against a captured baseline and removes closed work', async () => {
const store = useUnsavedWorkStore()
const harness = mountHarness()
harness.captureBaseline()
expect(store.entries).toEqual([])
harness.isOpen.value = true
await nextTick()
expect(store.entries[0]?.dirty).toBe(false)
harness.model.name = '柏悦物流'
await nextTick()
expect(store.entries[0]?.dirty).toBe(true)
harness.isOpen.value = false
await nextTick()
expect(store.entries).toEqual([])
harness.wrapper.unmount()
})
it('updates label and permission reactively and becomes clean after restoring the baseline', async () => {
const store = useUnsavedWorkStore()
const harness = mountHarness()
harness.captureBaseline()
harness.isOpen.value = true
await nextTick()
harness.label.value = '新增客户'
harness.requiredPermission.value = 'customer:update'
harness.model.name = '柏悦物流'
await nextTick()
expect(store.entries[0]).toEqual({
key: 'customer-form',
label: '新增客户',
dirty: true,
requiredPermission: 'customer:update',
})
harness.model.name = ''
await nextTick()
expect(store.entries[0]?.dirty).toBe(false)
harness.wrapper.unmount()
})
it('removes registered work when its component scope is disposed', async () => {
const store = useUnsavedWorkStore()
const harness = mountHarness()
harness.captureBaseline()
harness.isOpen.value = true
harness.model.name = '柏悦物流'
await nextTick()
expect(store.entries).toHaveLength(1)
harness.wrapper.unmount()
expect(store.entries).toEqual([])
})
})
@@ -0,0 +1,47 @@
import { computed, onScopeDispose, ref, watch, type ComputedRef, type Ref } from 'vue'
import { useUnsavedWorkStore } from '@/stores/unsavedWork'
interface UnsavedWorkRegistrationOptions<T extends object> {
key: string
label: ComputedRef<string>
isOpen: Ref<boolean>
model: T
requiredPermission: ComputedRef<string | null>
}
export function useUnsavedWorkRegistration<T extends object>(
options: UnsavedWorkRegistrationOptions<T>,
) {
const store = useUnsavedWorkStore()
const snapshot = computed(() => JSON.stringify(options.model))
const baseline = ref(snapshot.value)
function sync() {
if (!options.isOpen.value) {
store.remove(options.key)
return
}
store.upsert({
key: options.key,
label: options.label.value,
dirty: snapshot.value !== baseline.value,
requiredPermission: options.requiredPermission.value,
})
}
function captureBaseline() {
baseline.value = snapshot.value
sync()
}
watch(
[options.isOpen, snapshot, options.label, options.requiredPermission],
sync,
{ flush: 'sync' },
)
sync()
onScopeDispose(() => store.remove(options.key))
return { captureBaseline }
}