feat: 基础数据前端缓存 - 使用 Pinia Store 实现

- 新建 baseData Store,缓存年级/学科/区域/班级数据
- 支持 localStorage 持久化,24小时过期
- 编辑/删除操作自动清除对应缓存
- 修改四个管理页面(grade/subject/region/class)使用 Store
This commit is contained in:
神码-方晓辉 2026-02-03 17:14:58 +08:00
parent bf67282825
commit 124013703f
5 changed files with 220 additions and 0 deletions

View File

@ -0,0 +1,200 @@
/**
* 基础数据 Store
* 缓存年级学科区域班级等基础数据
*
* @author 湖北新华业务中台研发团队
*/
import { defineStore } from 'pinia'
import request from '@/utils/request'
const STORAGE_KEY = 'pguser:baseData'
const CACHE_EXPIRE = 24 * 60 * 60 * 1000 // 24小时
/**
* localStorage 恢复数据
*/
const loadFromStorage = () => {
try {
const data = localStorage.getItem(STORAGE_KEY)
if (data) {
const parsed = JSON.parse(data)
// 检查是否过期
if (parsed.expire && Date.now() < parsed.expire) {
return parsed
}
}
} catch (e) {
console.error('加载缓存失败:', e)
}
return null
}
/**
* 保存到 localStorage
*/
const saveToStorage = (state) => {
try {
const data = {
grades: state.grades,
subjects: state.subjects,
regionTree: state.regionTree,
classes: state.classes,
expire: Date.now() + CACHE_EXPIRE,
updateTime: new Date().toISOString()
}
localStorage.setItem(STORAGE_KEY, JSON.stringify(data))
} catch (e) {
console.error('保存缓存失败:', e)
}
}
const useBaseDataStore = defineStore('baseData', {
state: () => {
const cached = loadFromStorage()
return {
grades: cached?.grades || [], // 年级列表
subjects: cached?.subjects || [], // 学科列表
regionTree: cached?.regionTree || [], // 区域树
classes: cached?.classes || [], // 班级列表
loading: {
grades: false,
subjects: false,
regionTree: false,
classes: false
}
}
},
getters: {
// 年级选项(用于下拉)
gradeOptions: (state) => state.grades.map(g => ({ label: g.gradeName, value: g.gradeId })),
// 学科选项(用于下拉)
subjectOptions: (state) => state.subjects.map(s => ({ label: s.subjectName, value: s.subjectId })),
// 班级选项(用于下拉)
classOptions: (state) => state.classes.map(c => ({ label: c.className, value: c.classId }))
},
actions: {
// ==================== 年级 ====================
/**
* 获取年级列表带缓存
* @param {boolean} force 是否强制刷新
*/
async fetchGrades(force = false) {
if (!force && this.grades.length > 0) {
return this.grades
}
this.loading.grades = true
try {
const res = await request.get('/business/grade/listAll')
if (res.code === 200) {
this.grades = res.data || []
saveToStorage(this)
}
return this.grades
} finally {
this.loading.grades = false
}
},
/**
* 清除年级缓存
*/
clearGrades() {
this.grades = []
saveToStorage(this)
},
// ==================== 学科 ====================
async fetchSubjects(force = false) {
if (!force && this.subjects.length > 0) {
return this.subjects
}
this.loading.subjects = true
try {
const res = await request.get('/business/subject/listAll')
if (res.code === 200) {
this.subjects = res.data || []
saveToStorage(this)
}
return this.subjects
} finally {
this.loading.subjects = false
}
},
clearSubjects() {
this.subjects = []
saveToStorage(this)
},
// ==================== 区域 ====================
async fetchRegionTree(force = false) {
if (!force && this.regionTree.length > 0) {
return this.regionTree
}
this.loading.regionTree = true
try {
const res = await request.get('/business/region/tree')
if (res.code === 200) {
this.regionTree = res.data || []
saveToStorage(this)
}
return this.regionTree
} finally {
this.loading.regionTree = false
}
},
clearRegionTree() {
this.regionTree = []
saveToStorage(this)
},
// ==================== 班级 ====================
async fetchClasses(force = false) {
if (!force && this.classes.length > 0) {
return this.classes
}
this.loading.classes = true
try {
const res = await request.get('/business/class/listAll')
if (res.code === 200) {
this.classes = res.data || []
saveToStorage(this)
}
return this.classes
} finally {
this.loading.classes = false
}
},
clearClasses() {
this.classes = []
saveToStorage(this)
},
// ==================== 通用 ====================
/**
* 清除所有缓存
*/
clearAll() {
this.grades = []
this.subjects = []
this.regionTree = []
this.classes = []
localStorage.removeItem(STORAGE_KEY)
}
}
})
export default useBaseDataStore

View File

@ -93,6 +93,9 @@ import { Delete, Edit, Plus, Refresh, Search } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { onMounted, ref } from 'vue'
import request from '@/utils/request'
import useBaseDataStore from '@/store/modules/baseData'
const baseDataStore = useBaseDataStore()
const loading = ref(false)
const tableData = ref([])
@ -169,6 +172,7 @@ const handleSubmit = async () => {
? await request.put('/business/class', form.value)
: await request.post('/business/class', form.value)
if (res.code === 200) {
baseDataStore.clearClasses() //
ElMessage.success(isEdit ? '修改成功' : '新增成功')
dialogVisible.value = false
getList()
@ -184,6 +188,7 @@ const handleDelete = (row) => {
}).then(async () => {
const res = await request.delete(`/business/class/${row.classId}`)
if (res.code === 200) {
baseDataStore.clearClasses() //
ElMessage.success('删除成功')
getList()
}

View File

@ -93,6 +93,9 @@ import { Delete, Edit, Plus, Refresh, Search } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { onMounted, ref } from 'vue'
import request from '@/utils/request'
import useBaseDataStore from '@/store/modules/baseData'
const baseDataStore = useBaseDataStore()
const loading = ref(false)
const tableData = ref([])
@ -169,6 +172,7 @@ const handleSubmit = async () => {
? await request.put('/business/grade', form.value)
: await request.post('/business/grade', form.value)
if (res.code === 200) {
baseDataStore.clearGrades() //
ElMessage.success(isEdit ? '修改成功' : '新增成功')
dialogVisible.value = false
getList()
@ -184,6 +188,7 @@ const handleDelete = (row) => {
}).then(async () => {
const res = await request.delete(`/business/grade/${row.gradeId}`)
if (res.code === 200) {
baseDataStore.clearGrades() //
ElMessage.success('删除成功')
getList()
}

View File

@ -110,6 +110,9 @@ import { Delete, Edit, Plus, Refresh, Search, Sort } from '@element-plus/icons-v
import { ElMessage, ElMessageBox } from 'element-plus'
import { nextTick, onMounted, ref } from 'vue'
import request from '@/utils/request'
import useBaseDataStore from '@/store/modules/baseData'
const baseDataStore = useBaseDataStore()
const loading = ref(false)
const tableData = ref([])
@ -220,6 +223,7 @@ const handleSubmit = async () => {
? await request.put('/business/region', form.value)
: await request.post('/business/region', form.value)
if (res.code === 200) {
baseDataStore.clearRegionTree() //
ElMessage.success(isEdit ? '修改成功' : '新增成功')
dialogVisible.value = false
getList()
@ -235,6 +239,7 @@ const handleDelete = (row) => {
}).then(async () => {
const res = await request.delete(`/business/region/${row.regionId}`)
if (res.code === 200) {
baseDataStore.clearRegionTree() //
ElMessage.success('删除成功')
getList()
}

View File

@ -93,6 +93,9 @@ import { Delete, Edit, Plus, Refresh, Search } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { onMounted, ref } from 'vue'
import request from '@/utils/request'
import useBaseDataStore from '@/store/modules/baseData'
const baseDataStore = useBaseDataStore()
const loading = ref(false)
const tableData = ref([])
@ -169,6 +172,7 @@ const handleSubmit = async () => {
? await request.put('/business/subject', form.value)
: await request.post('/business/subject', form.value)
if (res.code === 200) {
baseDataStore.clearSubjects() //
ElMessage.success(isEdit ? '修改成功' : '新增成功')
dialogVisible.value = false
getList()
@ -184,6 +188,7 @@ const handleDelete = (row) => {
}).then(async () => {
const res = await request.delete(`/business/subject/${row.subjectId}`)
if (res.code === 200) {
baseDataStore.clearSubjects() //
ElMessage.success('删除成功')
getList()
}