更新完善字典相关功能
This commit is contained in:
@@ -28,7 +28,7 @@ export default {
|
||||
post: async function (file) {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
return await request.post('upload', formData, {
|
||||
return await request.post('system/upload', formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
})
|
||||
},
|
||||
@@ -38,64 +38,64 @@ export default {
|
||||
users: {
|
||||
list: {
|
||||
get: async function (params) {
|
||||
return await request.get('users', { params })
|
||||
return await request.get('auth/users', { params })
|
||||
},
|
||||
},
|
||||
detail: {
|
||||
get: async function (id) {
|
||||
return await request.get(`users/${id}`)
|
||||
return await request.get(`auth/users/${id}`)
|
||||
},
|
||||
},
|
||||
add: {
|
||||
post: async function (params) {
|
||||
return await request.post('users', params)
|
||||
return await request.post('auth/users', params)
|
||||
},
|
||||
},
|
||||
edit: {
|
||||
put: async function (id, params) {
|
||||
return await request.put(`users/${id}`, params)
|
||||
return await request.put(`auth/users/${id}`, params)
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
delete: async function (id) {
|
||||
return await request.delete(`users/${id}`)
|
||||
return await request.delete(`auth/users/${id}`)
|
||||
},
|
||||
},
|
||||
batchDelete: {
|
||||
post: async function (params) {
|
||||
return await request.post('users/batch-delete', params)
|
||||
return await request.post('auth/users/batch-delete', params)
|
||||
},
|
||||
},
|
||||
batchStatus: {
|
||||
post: async function (params) {
|
||||
return await request.post('users/batch-status', params)
|
||||
return await request.post('auth/users/batch-status', params)
|
||||
},
|
||||
},
|
||||
batchDepartment: {
|
||||
post: async function (params) {
|
||||
return await request.post('users/batch-department', params)
|
||||
return await request.post('auth/users/batch-department', params)
|
||||
},
|
||||
},
|
||||
batchRoles: {
|
||||
post: async function (params) {
|
||||
return await request.post('users/batch-roles', params)
|
||||
return await request.post('auth/users/batch-roles', params)
|
||||
},
|
||||
},
|
||||
export: {
|
||||
post: async function (params) {
|
||||
return await request.post('users/export', params, { responseType: 'blob' })
|
||||
return await request.post('auth/users/export', params, { responseType: 'blob' })
|
||||
},
|
||||
},
|
||||
import: {
|
||||
post: async function (formData) {
|
||||
return await request.post('users/import', formData, {
|
||||
return await request.post('auth/users/import', formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
})
|
||||
},
|
||||
},
|
||||
downloadTemplate: {
|
||||
get: async function () {
|
||||
return await request.get('users/download-template', { responseType: 'blob' })
|
||||
return await request.get('auth/users/download-template', { responseType: 'blob' })
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -104,27 +104,27 @@ export default {
|
||||
onlineUsers: {
|
||||
count: {
|
||||
get: async function () {
|
||||
return await request.get('online-users/count')
|
||||
return await request.get('auth/online-users/count')
|
||||
},
|
||||
},
|
||||
list: {
|
||||
get: async function (params) {
|
||||
return await request.get('online-users', { params })
|
||||
return await request.get('auth/online-users', { params })
|
||||
},
|
||||
},
|
||||
sessions: {
|
||||
get: async function (userId) {
|
||||
return await request.get(`online-users/${userId}/sessions`)
|
||||
return await request.get(`auth/online-users/${userId}/sessions`)
|
||||
},
|
||||
},
|
||||
offline: {
|
||||
post: async function (userId, params) {
|
||||
return await request.post(`online-users/${userId}/offline`, params)
|
||||
return await request.post(`auth/online-users/${userId}/offline`, params)
|
||||
},
|
||||
},
|
||||
offlineAll: {
|
||||
post: async function (userId) {
|
||||
return await request.post(`online-users/${userId}/offline-all`)
|
||||
return await request.post(`auth/online-users/${userId}/offline-all`)
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -133,77 +133,77 @@ export default {
|
||||
roles: {
|
||||
list: {
|
||||
get: async function (params) {
|
||||
return await request.get('roles', { params })
|
||||
return await request.get('auth/roles', { params })
|
||||
},
|
||||
},
|
||||
all: {
|
||||
get: async function () {
|
||||
return await request.get('roles/all')
|
||||
return await request.get('auth/roles/all')
|
||||
},
|
||||
},
|
||||
detail: {
|
||||
get: async function (id) {
|
||||
return await request.get(`roles/${id}`)
|
||||
return await request.get(`auth/roles/${id}`)
|
||||
},
|
||||
},
|
||||
add: {
|
||||
post: async function (params) {
|
||||
return await request.post('roles', params)
|
||||
return await request.post('auth/roles', params)
|
||||
},
|
||||
},
|
||||
edit: {
|
||||
put: async function (id, params) {
|
||||
return await request.put(`roles/${id}`, params)
|
||||
return await request.put(`auth/roles/${id}`, params)
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
delete: async function (id) {
|
||||
return await request.delete(`roles/${id}`)
|
||||
return await request.delete(`auth/roles/${id}`)
|
||||
},
|
||||
},
|
||||
batchDelete: {
|
||||
post: async function (params) {
|
||||
return await request.post('roles/batch-delete', params)
|
||||
return await request.post('auth/roles/batch-delete', params)
|
||||
},
|
||||
},
|
||||
batchStatus: {
|
||||
post: async function (params) {
|
||||
return await request.post('roles/batch-status', params)
|
||||
return await request.post('auth/roles/batch-status', params)
|
||||
},
|
||||
},
|
||||
permissions: {
|
||||
get: async function (id) {
|
||||
return await request.get(`roles/${id}/permissions`)
|
||||
return await request.get(`auth/roles/${id}/permissions`)
|
||||
},
|
||||
post: async function (id, params) {
|
||||
return await request.post(`roles/${id}/permissions`, params)
|
||||
return await request.post(`auth/roles/${id}/permissions`, params)
|
||||
},
|
||||
},
|
||||
copy: {
|
||||
post: async function (id, params) {
|
||||
return await request.post(`roles/${id}/copy`, params)
|
||||
return await request.post(`auth/roles/${id}/copy`, params)
|
||||
},
|
||||
},
|
||||
batchCopy: {
|
||||
post: async function (params) {
|
||||
return await request.post('roles/batch-copy', params)
|
||||
return await request.post('auth/roles/batch-copy', params)
|
||||
},
|
||||
},
|
||||
export: {
|
||||
post: async function (params) {
|
||||
return await request.post('roles/export', params, { responseType: 'blob' })
|
||||
return await request.post('auth/roles/export', params, { responseType: 'blob' })
|
||||
},
|
||||
},
|
||||
import: {
|
||||
post: async function (formData) {
|
||||
return await request.post('roles/import', formData, {
|
||||
return await request.post('auth/roles/import', formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
})
|
||||
},
|
||||
},
|
||||
downloadTemplate: {
|
||||
get: async function () {
|
||||
return await request.get('roles/download-template', { responseType: 'blob' })
|
||||
return await request.get('auth/roles/download-template', { responseType: 'blob' })
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -212,64 +212,64 @@ export default {
|
||||
permissions: {
|
||||
list: {
|
||||
get: async function (params) {
|
||||
return await request.get('permissions', { params })
|
||||
return await request.get('auth/permissions', { params })
|
||||
},
|
||||
},
|
||||
tree: {
|
||||
get: async function () {
|
||||
return await request.get('permissions/tree')
|
||||
return await request.get('auth/permissions/tree')
|
||||
},
|
||||
},
|
||||
menu: {
|
||||
get: async function () {
|
||||
return await request.get('permissions/menu')
|
||||
return await request.get('auth/permissions/menu')
|
||||
},
|
||||
},
|
||||
detail: {
|
||||
get: async function (id) {
|
||||
return await request.get(`permissions/${id}`)
|
||||
return await request.get(`auth/permissions/${id}`)
|
||||
},
|
||||
},
|
||||
add: {
|
||||
post: async function (params) {
|
||||
return await request.post('permissions', params)
|
||||
return await request.post('auth/permissions', params)
|
||||
},
|
||||
},
|
||||
edit: {
|
||||
put: async function (id, params) {
|
||||
return await request.put(`permissions/${id}`, params)
|
||||
return await request.put(`auth/permissions/${id}`, params)
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
delete: async function (id) {
|
||||
return await request.delete(`permissions/${id}`)
|
||||
return await request.delete(`auth/permissions/${id}`)
|
||||
},
|
||||
},
|
||||
batchDelete: {
|
||||
post: async function (params) {
|
||||
return await request.post('permissions/batch-delete', params)
|
||||
return await request.post('auth/permissions/batch-delete', params)
|
||||
},
|
||||
},
|
||||
batchStatus: {
|
||||
post: async function (params) {
|
||||
return await request.post('permissions/batch-status', params)
|
||||
return await request.post('auth/permissions/batch-status', params)
|
||||
},
|
||||
},
|
||||
export: {
|
||||
post: async function (params) {
|
||||
return await request.post('permissions/export', params, { responseType: 'blob' })
|
||||
return await request.post('auth/permissions/export', params, { responseType: 'blob' })
|
||||
},
|
||||
},
|
||||
import: {
|
||||
post: async function (formData) {
|
||||
return await request.post('permissions/import', formData, {
|
||||
return await request.post('auth/permissions/import', formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
})
|
||||
},
|
||||
},
|
||||
downloadTemplate: {
|
||||
get: async function () {
|
||||
return await request.get('permissions/download-template', { responseType: 'blob' })
|
||||
return await request.get('auth/permissions/download-template', { responseType: 'blob' })
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -278,64 +278,64 @@ export default {
|
||||
departments: {
|
||||
list: {
|
||||
get: async function (params) {
|
||||
return await request.get('departments', { params })
|
||||
return await request.get('auth/departments', { params })
|
||||
},
|
||||
},
|
||||
tree: {
|
||||
get: async function (params) {
|
||||
return await request.get('departments/tree', { params })
|
||||
return await request.get('auth/departments/tree', { params })
|
||||
},
|
||||
},
|
||||
all: {
|
||||
get: async function () {
|
||||
return await request.get('departments/all')
|
||||
return await request.get('auth/departments/all')
|
||||
},
|
||||
},
|
||||
detail: {
|
||||
get: async function (id) {
|
||||
return await request.get(`departments/${id}`)
|
||||
return await request.get(`auth/departments/${id}`)
|
||||
},
|
||||
},
|
||||
add: {
|
||||
post: async function (params) {
|
||||
return await request.post('departments', params)
|
||||
return await request.post('auth/departments', params)
|
||||
},
|
||||
},
|
||||
edit: {
|
||||
put: async function (id, params) {
|
||||
return await request.put(`departments/${id}`, params)
|
||||
return await request.put(`auth/departments/${id}`, params)
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
delete: async function (id) {
|
||||
return await request.delete(`departments/${id}`)
|
||||
return await request.delete(`auth/departments/${id}`)
|
||||
},
|
||||
},
|
||||
batchDelete: {
|
||||
post: async function (params) {
|
||||
return await request.post('departments/batch-delete', params)
|
||||
return await request.post('auth/departments/batch-delete', params)
|
||||
},
|
||||
},
|
||||
batchStatus: {
|
||||
post: async function (params) {
|
||||
return await request.post('departments/batch-status', params)
|
||||
return await request.post('auth/departments/batch-status', params)
|
||||
},
|
||||
},
|
||||
export: {
|
||||
post: async function (params) {
|
||||
return await request.post('departments/export', params, { responseType: 'blob' })
|
||||
return await request.post('auth/departments/export', params, { responseType: 'blob' })
|
||||
},
|
||||
},
|
||||
import: {
|
||||
post: async function (formData) {
|
||||
return await request.post('departments/import', formData, {
|
||||
return await request.post('auth/departments/import', formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
})
|
||||
},
|
||||
},
|
||||
downloadTemplate: {
|
||||
get: async function () {
|
||||
return await request.get('departments/download-template', { responseType: 'blob' })
|
||||
return await request.get('auth/departments/download-template', { responseType: 'blob' })
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -5,47 +5,47 @@ export default {
|
||||
configs: {
|
||||
list: {
|
||||
get: async function (params) {
|
||||
return await request.get('configs', { params })
|
||||
return await request.get('system/configs', { params })
|
||||
},
|
||||
},
|
||||
groups: {
|
||||
get: async function () {
|
||||
return await request.get('configs/groups')
|
||||
return await request.get('system/configs/groups')
|
||||
},
|
||||
},
|
||||
all: {
|
||||
get: async function (params) {
|
||||
return await request.get('configs/all', { params })
|
||||
return await request.get('system/configs/all', { params })
|
||||
},
|
||||
},
|
||||
detail: {
|
||||
get: async function (id) {
|
||||
return await request.get(`configs/${id}`)
|
||||
return await request.get(`system/configs/${id}`)
|
||||
},
|
||||
},
|
||||
add: {
|
||||
post: async function (params) {
|
||||
return await request.post('configs', params)
|
||||
return await request.post('system/configs', params)
|
||||
},
|
||||
},
|
||||
edit: {
|
||||
put: async function (id, params) {
|
||||
return await request.put(`configs/${id}`, params)
|
||||
return await request.put(`system/configs/${id}`, params)
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
delete: async function (id) {
|
||||
return await request.delete(`configs/${id}`)
|
||||
return await request.delete(`system/configs/${id}`)
|
||||
},
|
||||
},
|
||||
batchDelete: {
|
||||
post: async function (params) {
|
||||
return await request.post('configs/batch-delete', params)
|
||||
return await request.post('system/configs/batch-delete', params)
|
||||
},
|
||||
},
|
||||
batchStatus: {
|
||||
post: async function (params) {
|
||||
return await request.post('configs/batch-status', params)
|
||||
return await request.post('system/configs/batch-status', params)
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -54,32 +54,32 @@ export default {
|
||||
logs: {
|
||||
list: {
|
||||
get: async function (params) {
|
||||
return await request.get('logs', { params })
|
||||
return await request.get('system/logs', { params })
|
||||
},
|
||||
},
|
||||
detail: {
|
||||
get: async function (id) {
|
||||
return await request.get(`logs/${id}`)
|
||||
return await request.get(`system/logs/${id}`)
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
delete: async function (id) {
|
||||
return await request.delete(`logs/${id}`)
|
||||
return await request.delete(`system/logs/${id}`)
|
||||
},
|
||||
},
|
||||
batchDelete: {
|
||||
post: async function (params) {
|
||||
return await request.post('logs/batch-delete', params)
|
||||
return await request.post('system/logs/batch-delete', params)
|
||||
},
|
||||
},
|
||||
clear: {
|
||||
post: async function (params) {
|
||||
return await request.post('logs/clear', params)
|
||||
return await request.post('system/logs/clear', params)
|
||||
},
|
||||
},
|
||||
export: {
|
||||
get: async function (params) {
|
||||
return await request.get('logs/export', {
|
||||
return await request.get('system/logs/export', {
|
||||
params,
|
||||
responseType: 'blob'
|
||||
})
|
||||
@@ -87,90 +87,102 @@ export default {
|
||||
},
|
||||
statistics: {
|
||||
get: async function (params) {
|
||||
return await request.get('logs/statistics', { params })
|
||||
return await request.get('system/logs/statistics', { params })
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// 数据字典管理
|
||||
dictionaries: {
|
||||
list: {
|
||||
get: async function (params) {
|
||||
return await request.get('dictionaries', { params })
|
||||
// 数据字典管理
|
||||
dictionaries: {
|
||||
list: {
|
||||
get: async function (params) {
|
||||
return await request.get('system/dictionaries', { params })
|
||||
},
|
||||
},
|
||||
all: {
|
||||
get: async function () {
|
||||
return await request.get('system/dictionaries/all')
|
||||
},
|
||||
},
|
||||
detail: {
|
||||
get: async function (id) {
|
||||
return await request.get(`system/dictionaries/${id}`)
|
||||
},
|
||||
},
|
||||
add: {
|
||||
post: async function (params) {
|
||||
return await request.post('system/dictionaries', params)
|
||||
},
|
||||
},
|
||||
edit: {
|
||||
put: async function (id, params) {
|
||||
return await request.put(`system/dictionaries/${id}`, params)
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
delete: async function (id) {
|
||||
return await request.delete(`system/dictionaries/${id}`)
|
||||
},
|
||||
},
|
||||
batchDelete: {
|
||||
post: async function (params) {
|
||||
return await request.post('system/dictionaries/batch-delete', params)
|
||||
},
|
||||
},
|
||||
batchStatus: {
|
||||
post: async function (params) {
|
||||
return await request.post('system/dictionaries/batch-status', params)
|
||||
},
|
||||
},
|
||||
items: {
|
||||
all: {
|
||||
get: async function (code) {
|
||||
return await request.get(`system/dictionaries/code`, { params: { code } })
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
all: {
|
||||
get: async function () {
|
||||
return await request.get('dictionaries/all')
|
||||
},
|
||||
},
|
||||
detail: {
|
||||
get: async function (id) {
|
||||
return await request.get(`dictionaries/${id}`)
|
||||
},
|
||||
},
|
||||
add: {
|
||||
post: async function (params) {
|
||||
return await request.post('dictionaries', params)
|
||||
},
|
||||
},
|
||||
edit: {
|
||||
put: async function (id, params) {
|
||||
return await request.put(`dictionaries/${id}`, params)
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
delete: async function (id) {
|
||||
return await request.delete(`dictionaries/${id}`)
|
||||
},
|
||||
},
|
||||
batchDelete: {
|
||||
post: async function (params) {
|
||||
return await request.post('dictionaries/batch-delete', params)
|
||||
},
|
||||
},
|
||||
batchStatus: {
|
||||
post: async function (params) {
|
||||
return await request.post('dictionaries/batch-status', params)
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// 数据字典项管理
|
||||
dictionaryItems: {
|
||||
list: {
|
||||
get: async function (params) {
|
||||
return await request.get('dictionary-items', { params })
|
||||
return await request.get('system/dictionary-items', { params })
|
||||
},
|
||||
},
|
||||
all: {
|
||||
get: async function () {
|
||||
return await request.get('system/dictionary-items/all')
|
||||
},
|
||||
},
|
||||
detail: {
|
||||
get: async function (id) {
|
||||
return await request.get(`dictionary-items/${id}`)
|
||||
return await request.get(`system/dictionary-items/${id}`)
|
||||
},
|
||||
},
|
||||
add: {
|
||||
post: async function (params) {
|
||||
return await request.post('dictionary-items', params)
|
||||
return await request.post('system/dictionary-items', params)
|
||||
},
|
||||
},
|
||||
edit: {
|
||||
put: async function (id, params) {
|
||||
return await request.put(`dictionary-items/${id}`, params)
|
||||
return await request.put(`system/dictionary-items/${id}`, params)
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
delete: async function (id) {
|
||||
return await request.delete(`dictionary-items/${id}`)
|
||||
return await request.delete(`system/dictionary-items/${id}`)
|
||||
},
|
||||
},
|
||||
batchDelete: {
|
||||
post: async function (params) {
|
||||
return await request.post('dictionary-items/batch-delete', params)
|
||||
return await request.post('system/dictionary-items/batch-delete', params)
|
||||
},
|
||||
},
|
||||
batchStatus: {
|
||||
post: async function (params) {
|
||||
return await request.post('dictionary-items/batch-status', params)
|
||||
return await request.post('system/dictionary-items/batch-status', params)
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -179,52 +191,52 @@ export default {
|
||||
tasks: {
|
||||
list: {
|
||||
get: async function (params) {
|
||||
return await request.get('tasks', { params })
|
||||
return await request.get('system/tasks', { params })
|
||||
},
|
||||
},
|
||||
all: {
|
||||
get: async function () {
|
||||
return await request.get('tasks/all')
|
||||
return await request.get('system/tasks/all')
|
||||
},
|
||||
},
|
||||
detail: {
|
||||
get: async function (id) {
|
||||
return await request.get(`tasks/${id}`)
|
||||
return await request.get(`system/tasks/${id}`)
|
||||
},
|
||||
},
|
||||
add: {
|
||||
post: async function (params) {
|
||||
return await request.post('tasks', params)
|
||||
return await request.post('system/tasks', params)
|
||||
},
|
||||
},
|
||||
edit: {
|
||||
put: async function (id, params) {
|
||||
return await request.put(`tasks/${id}`, params)
|
||||
return await request.put(`system/tasks/${id}`, params)
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
delete: async function (id) {
|
||||
return await request.delete(`tasks/${id}`)
|
||||
return await request.delete(`system/tasks/${id}`)
|
||||
},
|
||||
},
|
||||
batchDelete: {
|
||||
post: async function (params) {
|
||||
return await request.post('tasks/batch-delete', params)
|
||||
return await request.post('system/tasks/batch-delete', params)
|
||||
},
|
||||
},
|
||||
batchStatus: {
|
||||
post: async function (params) {
|
||||
return await request.post('tasks/batch-status', params)
|
||||
return await request.post('system/tasks/batch-status', params)
|
||||
},
|
||||
},
|
||||
run: {
|
||||
post: async function (id) {
|
||||
return await request.post(`tasks/${id}/run`)
|
||||
return await request.post(`system/tasks/${id}/run`)
|
||||
},
|
||||
},
|
||||
statistics: {
|
||||
get: async function () {
|
||||
return await request.get('tasks/statistics')
|
||||
return await request.get('system/tasks/statistics')
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -233,62 +245,62 @@ export default {
|
||||
cities: {
|
||||
list: {
|
||||
get: async function (params) {
|
||||
return await request.get('cities', { params })
|
||||
return await request.get('system/cities', { params })
|
||||
},
|
||||
},
|
||||
tree: {
|
||||
get: async function () {
|
||||
return await request.get('cities/tree')
|
||||
return await request.get('system/cities/tree')
|
||||
},
|
||||
},
|
||||
detail: {
|
||||
get: async function (id) {
|
||||
return await request.get(`cities/${id}`)
|
||||
return await request.get(`system/cities/${id}`)
|
||||
},
|
||||
},
|
||||
children: {
|
||||
get: async function (id) {
|
||||
return await request.get(`cities/${id}/children`)
|
||||
return await request.get(`system/cities/${id}/children`)
|
||||
},
|
||||
},
|
||||
provinces: {
|
||||
get: async function () {
|
||||
return await request.get('cities/provinces')
|
||||
return await request.get('system/cities/provinces')
|
||||
},
|
||||
},
|
||||
cities: {
|
||||
get: async function (provinceId) {
|
||||
return await request.get(`cities/${provinceId}/cities`)
|
||||
return await request.get(`system/cities/${provinceId}/cities`)
|
||||
},
|
||||
},
|
||||
districts: {
|
||||
get: async function (cityId) {
|
||||
return await request.get(`cities/${cityId}/districts`)
|
||||
return await request.get(`system/cities/${cityId}/districts`)
|
||||
},
|
||||
},
|
||||
add: {
|
||||
post: async function (params) {
|
||||
return await request.post('cities', params)
|
||||
return await request.post('system/cities', params)
|
||||
},
|
||||
},
|
||||
edit: {
|
||||
put: async function (id, params) {
|
||||
return await request.put(`cities/${id}`, params)
|
||||
return await request.put(`system/cities/${id}`, params)
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
delete: async function (id) {
|
||||
return await request.delete(`cities/${id}`)
|
||||
return await request.delete(`system/cities/${id}`)
|
||||
},
|
||||
},
|
||||
batchDelete: {
|
||||
post: async function (params) {
|
||||
return await request.post('cities/batch-delete', params)
|
||||
return await request.post('system/cities/batch-delete', params)
|
||||
},
|
||||
},
|
||||
batchStatus: {
|
||||
post: async function (params) {
|
||||
return await request.post('cities/batch-status', params)
|
||||
return await request.post('system/cities/batch-status', params)
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -297,31 +309,31 @@ export default {
|
||||
upload: {
|
||||
single: {
|
||||
post: async function (formData) {
|
||||
return await request.post('upload', formData, {
|
||||
return await request.post('system/upload', formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
})
|
||||
},
|
||||
},
|
||||
multiple: {
|
||||
post: async function (formData) {
|
||||
return await request.post('upload/multiple', formData, {
|
||||
return await request.post('system/upload/multiple', formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
})
|
||||
},
|
||||
},
|
||||
base64: {
|
||||
post: async function (params) {
|
||||
return await request.post('upload/base64', params)
|
||||
return await request.post('system/upload/base64', params)
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
post: async function (params) {
|
||||
return await request.post('upload/delete', params)
|
||||
return await request.post('system/upload/delete', params)
|
||||
},
|
||||
},
|
||||
batchDelete: {
|
||||
post: async function (params) {
|
||||
return await request.post('upload/batch-delete', params)
|
||||
return await request.post('system/upload/batch-delete', params)
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
defineProps({
|
||||
msg: String,
|
||||
})
|
||||
|
||||
const count = ref(0)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1>{{ msg }}</h1>
|
||||
|
||||
<div class="card">
|
||||
<button type="button" @click="count++">count is {{ count }}</button>
|
||||
<p>
|
||||
Edit
|
||||
<code>components/HelloWorld.vue</code> to test HMR
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
Check out
|
||||
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
|
||||
>create-vue</a
|
||||
>, the official Vue + Vite starter
|
||||
</p>
|
||||
<p>
|
||||
Learn more about IDE Support for Vue in the
|
||||
<a
|
||||
href="https://vuejs.org/guide/scaling-up/tooling.html#ide-support"
|
||||
target="_blank"
|
||||
>Vue Docs Scaling up Guide</a
|
||||
>.
|
||||
</p>
|
||||
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.read-the-docs {
|
||||
color: #888;
|
||||
}
|
||||
</style>
|
||||
606
resources/admin/src/components/scSelect/README.md
Normal file
606
resources/admin/src/components/scSelect/README.md
Normal file
@@ -0,0 +1,606 @@
|
||||
# scSelect 组件使用文档
|
||||
|
||||
## 组件简介
|
||||
|
||||
`scSelect` 是一个基于 Ant Design Vue `a-select` 组件封装的增强型选择器组件,支持多种数据源(data、api、dictionary)和智能缓存机制。
|
||||
|
||||
## 特性
|
||||
|
||||
- ✅ 支持三种数据源:直接数据(data)、API 接口(api)、字典数据(dictionary)
|
||||
- ✅ API 数据源自动缓存,减少重复请求
|
||||
- ✅ 字典数据源自动从缓存读取,登录后自动预加载
|
||||
- ✅ 支持按需加载数据(focus 时触发)
|
||||
- ✅ 支持立即加载数据(组件挂载时)
|
||||
- ✅ 支持自定义字段映射
|
||||
- ✅ 支持自定义数据处理函数
|
||||
- ✅ 完全继承 `a-select` 的所有属性和方法
|
||||
- ✅ 支持插槽透传
|
||||
|
||||
## 安装
|
||||
|
||||
组件已自动注册,直接使用即可:
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<sc-select v-model:value="value" source-type="data" :data="options" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import scSelect from '@/components/scSelect/index.vue'
|
||||
|
||||
const value = ref('')
|
||||
const options = ref([
|
||||
{ label: '选项1', value: '1' },
|
||||
{ label: '选项2', value: '2' },
|
||||
])
|
||||
</script>
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
||||
|------|------|------|--------|--------|
|
||||
| sourceType | 数据源类型 | String | 'data' \| 'api' \| 'dictionary' | 'data' |
|
||||
| data | 直接数据(当 sourceType 为 data 时使用) | Array | - | [] |
|
||||
| api | API 接口地址(当 sourceType 为 api 时使用) | String | - | '' |
|
||||
| apiParams | API 请求参数(当 sourceType 为 api 时使用) | Object | - | {} |
|
||||
| dictionaryCode | 字典编码(当 sourceType 为 dictionary 时使用) | String | - | '' |
|
||||
| enableApiCache | 是否启用 API 数据缓存 | Boolean | - | true |
|
||||
| apiCacheTime | API 缓存时间(毫秒) | Number | - | 300000(5分钟) |
|
||||
| fieldNames | 字段映射配置 | Object | - | { label: 'label', value: 'value' } |
|
||||
| immediate | 是否在组件挂载时立即加载数据 | Boolean | - | false |
|
||||
| dataProcessor | 数据处理函数 | Function | - | null |
|
||||
|
||||
## Events
|
||||
|
||||
组件完全继承 `a-select` 的所有事件,包括但不限于:
|
||||
|
||||
- `@change` - 选中值变化时触发
|
||||
- `@focus` - 获得焦点时触发
|
||||
- `@blur` - 失去焦点时触发
|
||||
|
||||
## Methods
|
||||
|
||||
通过 ref 可以调用以下方法:
|
||||
|
||||
| 方法名 | 说明 | 参数 |
|
||||
|--------|------|------|
|
||||
| refresh | 强制刷新数据 | - |
|
||||
| loadApiData | 手动加载 API 数据 | - |
|
||||
| loadDictionaryData | 手动加载字典数据 | - |
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 1. 使用 data 数据源
|
||||
|
||||
最简单的使用方式,直接提供数据数组:
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<a-form>
|
||||
<a-form-item label="状态">
|
||||
<sc-select
|
||||
v-model:value="form.status"
|
||||
source-type="data"
|
||||
:data="statusOptions"
|
||||
placeholder="请选择状态"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive } from 'vue'
|
||||
import scSelect from '@/components/scSelect/index.vue'
|
||||
|
||||
const form = reactive({
|
||||
status: ''
|
||||
})
|
||||
|
||||
const statusOptions = [
|
||||
{ label: '启用', value: 1 },
|
||||
{ label: '禁用', value: 0 },
|
||||
]
|
||||
</script>
|
||||
```
|
||||
|
||||
### 2. 使用 api 数据源
|
||||
|
||||
从 API 接口获取数据,自动缓存:
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<a-form>
|
||||
<a-form-item label="用户">
|
||||
<sc-select
|
||||
v-model:value="form.userId"
|
||||
source-type="api"
|
||||
api="users"
|
||||
:api-params="{ page: 1, page_size: 100 }"
|
||||
:enable-api-cache="true"
|
||||
:api-cache-time="600000"
|
||||
placeholder="请选择用户"
|
||||
:field-names="{ label: 'username', value: 'id' }"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive } from 'vue'
|
||||
import scSelect from '@/components/scSelect/index.vue'
|
||||
|
||||
const form = reactive({
|
||||
userId: ''
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
### 3. 使用 dictionary 数据源(推荐)
|
||||
|
||||
从字典缓存获取数据,性能最佳:
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<a-form>
|
||||
<a-form-item label="性别">
|
||||
<sc-select
|
||||
v-model:value="form.gender"
|
||||
source-type="dictionary"
|
||||
dictionary-code="gender"
|
||||
placeholder="请选择性别"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="用户状态">
|
||||
<sc-select
|
||||
v-model:value="form.userStatus"
|
||||
source-type="dictionary"
|
||||
dictionary-code="user_status"
|
||||
placeholder="请选择用户状态"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive } from 'vue'
|
||||
import scSelect from '@/components/scSelect/index.vue'
|
||||
|
||||
const form = reactive({
|
||||
gender: '',
|
||||
userStatus: ''
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
### 4. 立即加载数据
|
||||
|
||||
在组件挂载时立即加载数据,而不是等到 focus 时:
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<sc-select
|
||||
v-model:value="value"
|
||||
source-type="api"
|
||||
api="roles/all"
|
||||
:immediate="true"
|
||||
placeholder="请选择角色"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import scSelect from '@/components/scSelect/index.vue'
|
||||
|
||||
const value = ref('')
|
||||
</script>
|
||||
```
|
||||
|
||||
### 5. 自定义数据处理
|
||||
|
||||
使用 `dataProcessor` 函数自定义数据格式:
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<sc-select
|
||||
v-model:value="value"
|
||||
source-type="api"
|
||||
api="roles"
|
||||
:data-processor="processData"
|
||||
placeholder="请选择角色"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import scSelect from '@/components/scSelect/index.vue'
|
||||
|
||||
const value = ref('')
|
||||
|
||||
// 自定义数据处理函数
|
||||
function processData(data) {
|
||||
return data.map(item => ({
|
||||
label: `${item.name} (${item.description})`,
|
||||
value: item.id,
|
||||
extra: item.description
|
||||
}))
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### 6. 使用 ref 调用方法
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div>
|
||||
<sc-select
|
||||
ref="selectRef"
|
||||
v-model:value="value"
|
||||
source-type="api"
|
||||
api="users"
|
||||
placeholder="请选择用户"
|
||||
/>
|
||||
<a-button @click="handleRefresh">刷新数据</a-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import scSelect from '@/components/scSelect/index.vue'
|
||||
|
||||
const selectRef = ref(null)
|
||||
const value = ref('')
|
||||
|
||||
// 刷新数据
|
||||
function handleRefresh() {
|
||||
if (selectRef.value) {
|
||||
selectRef.value.refresh()
|
||||
message.success('数据已刷新')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### 7. 禁用 API 缓存
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<sc-select
|
||||
v-model:value="value"
|
||||
source-type="api"
|
||||
api="users"
|
||||
:enable-api-cache="false"
|
||||
placeholder="请选择用户"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
|
||||
### 8. 多选模式
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<sc-select
|
||||
v-model:value="value"
|
||||
mode="multiple"
|
||||
source-type="dictionary"
|
||||
dictionary-code="tags"
|
||||
placeholder="请选择标签"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import scSelect from '@/components/scSelect/index.vue'
|
||||
|
||||
const value = ref([])
|
||||
</script>
|
||||
```
|
||||
|
||||
### 9. 自定义插槽
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<sc-select
|
||||
v-model:value="value"
|
||||
source-type="data"
|
||||
:data="options"
|
||||
placeholder="请选择"
|
||||
>
|
||||
<template #suffixIcon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
|
||||
<template #notFoundContent>
|
||||
<a-empty :image="Empty.PRESENTED_IMAGE_SIMPLE" description="暂无数据" />
|
||||
</template>
|
||||
</sc-select>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { SearchOutlined } from '@ant-design/icons-vue'
|
||||
import { Empty } from 'ant-design-vue'
|
||||
import scSelect from '@/components/scSelect/index.vue'
|
||||
|
||||
const value = ref('')
|
||||
const options = ref([
|
||||
{ label: '选项1', value: '1' },
|
||||
{ label: '选项2', value: '2' },
|
||||
])
|
||||
</script>
|
||||
```
|
||||
|
||||
## 数据格式要求
|
||||
|
||||
### data 数据源
|
||||
|
||||
支持以下格式:
|
||||
|
||||
1. 字符串/数字数组
|
||||
```javascript
|
||||
['选项1', '选项2', '选项3']
|
||||
[1, 2, 3]
|
||||
```
|
||||
|
||||
2. 对象数组(标准格式)
|
||||
```javascript
|
||||
[
|
||||
{ label: '选项1', value: '1' },
|
||||
{ label: '选项2', value: '2' },
|
||||
]
|
||||
```
|
||||
|
||||
3. 对象数组(自定义字段)
|
||||
```javascript
|
||||
[
|
||||
{ name: '选项1', id: 1 },
|
||||
{ name: '选项2', id: 2 },
|
||||
]
|
||||
// 使用 field-names 映射
|
||||
field-names="{ label: 'name', value: 'id' }"
|
||||
```
|
||||
|
||||
### api 数据源
|
||||
|
||||
API 接口需要返回以下格式:
|
||||
|
||||
```javascript
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": [
|
||||
{ label: '选项1', value: '1' },
|
||||
{ label: '选项2', value: '2' },
|
||||
]
|
||||
}
|
||||
|
||||
// 或者
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"list": [
|
||||
{ label: '选项1', value: '1' },
|
||||
{ label: '选项2', value: '2' },
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### dictionary 数据源
|
||||
|
||||
字典数据通过后台管理系统的"数据字典管理"模块配置,登录后会自动缓存到前端。
|
||||
|
||||
字典数据格式:
|
||||
```javascript
|
||||
{
|
||||
code: 'user_status',
|
||||
name: '用户状态',
|
||||
items: [
|
||||
{ name: '启用', value: '1', sort: 1, status: 1 },
|
||||
{ name: '禁用', value: '0', sort: 2, status: 1 },
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
组件会自动转换为:
|
||||
```javascript
|
||||
[
|
||||
{ label: '启用', value: '1', name: '启用', sort: 1, status: 1 },
|
||||
{ label: '禁用', value: '0', name: '禁用', sort: 2, status: 1 },
|
||||
]
|
||||
```
|
||||
|
||||
## 字典数据缓存机制
|
||||
|
||||
### 缓存流程
|
||||
|
||||
1. **登录时**:用户登录成功后,自动调用 `dictionaryStore.loadAllDictionaries()` 加载所有字典数据
|
||||
2. **存储**:字典数据存储在 Pinia Store 中,并持久化到 localStorage
|
||||
3. **使用**:组件通过 `dictionaryCode` 从 Store 获取数据,无需重复请求
|
||||
4. **刷新**:可以调用 `refresh()` 方法强制刷新字典数据
|
||||
|
||||
### 清空缓存
|
||||
|
||||
```javascript
|
||||
import { useDictionaryStore } from '@/stores/modules/dictionary'
|
||||
|
||||
const dictionaryStore = useDictionaryStore()
|
||||
|
||||
// 清空字典缓存
|
||||
dictionaryStore.clearCache()
|
||||
```
|
||||
|
||||
### 获取缓存信息
|
||||
|
||||
```javascript
|
||||
import { useDictionaryStore } from '@/stores/modules/dictionary'
|
||||
|
||||
const dictionaryStore = useDictionaryStore()
|
||||
|
||||
// 获取缓存信息
|
||||
const info = dictionaryStore.getCacheInfo()
|
||||
console.log('字典数量:', info.count)
|
||||
console.log('最后加载时间:', new Date(info.lastLoadTime))
|
||||
```
|
||||
|
||||
## API 数据缓存机制
|
||||
|
||||
### 缓存策略
|
||||
|
||||
- 默认启用缓存,缓存时间为 5 分钟
|
||||
- 缓存 key 由 `api` 地址和 `apiParams` 参数组成
|
||||
- 缓存过期后自动重新请求
|
||||
- 可以通过 `enableApiCache` 关闭缓存
|
||||
- 可以通过 `apiCacheTime` 自定义缓存时间
|
||||
|
||||
### 缓存 key 示例
|
||||
|
||||
```javascript
|
||||
// 同一个 API,不同参数会有不同的缓存
|
||||
api: 'users'
|
||||
apiParams: { page: 1, page_size: 20 }
|
||||
// 缓存 key: 'users{"page":1,"page_size":20}'
|
||||
|
||||
apiParams: { page: 2, page_size: 20 }
|
||||
// 缓存 key: 'users{"page":2,"page_size":20}'
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **字典数据源**:确保在后台管理系统中配置了对应的字典数据
|
||||
2. **API 数据源**:确保 API 接口返回的数据格式正确
|
||||
3. **缓存失效**:修改字典数据后,需要调用 `refresh()` 方法刷新缓存
|
||||
4. **字段映射**:如果后端返回的字段名不是 `label` 和 `value`,需要使用 `fieldNames` 映射
|
||||
5. **按需加载**:默认在 focus 时加载数据,如果需要立即加载,设置 `immediate=true`
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **优先使用字典数据源**:对于固定选项(如状态、类型等),优先使用字典数据源
|
||||
2. **合理使用缓存**:API 数据源建议启用缓存,减少服务器压力
|
||||
3. **字段映射**:明确指定 `fieldNames`,避免数据格式不一致
|
||||
4. **错误处理**:API 请求失败时,组件会自动处理错误并记录日志
|
||||
5. **性能优化**:对于大量数据,建议使用分页加载或虚拟滚动
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: 字典数据不显示?
|
||||
|
||||
A: 检查以下几点:
|
||||
1. 后台管理系统中是否配置了对应的字典
|
||||
2. 字典编码是否正确
|
||||
3. 字典状态是否为启用
|
||||
4. 字典项状态是否为启用
|
||||
|
||||
### Q: API 数据加载失败?
|
||||
|
||||
A: 检查以下几点:
|
||||
1. API 地址是否正确
|
||||
2. API 是否需要认证
|
||||
3. 返回数据格式是否符合要求
|
||||
4. 控制台是否有错误信息
|
||||
|
||||
### Q: 如何刷新字典数据?
|
||||
|
||||
A: 使用 `refresh()` 方法:
|
||||
```javascript
|
||||
const selectRef = ref(null)
|
||||
selectRef.value?.refresh()
|
||||
```
|
||||
|
||||
### Q: 如何禁用缓存?
|
||||
|
||||
A: 设置 `enableApiCache` 为 `false`:
|
||||
```vue
|
||||
<sc-select
|
||||
source-type="api"
|
||||
api="users"
|
||||
:enable-api-cache="false"
|
||||
/>
|
||||
```
|
||||
|
||||
## 完整示例
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<a-form :model="form" :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
|
||||
<!-- 使用数据源 -->
|
||||
<a-form-item label="数据源示例">
|
||||
<sc-select
|
||||
v-model:value="form.dataSource"
|
||||
source-type="data"
|
||||
:data="[
|
||||
{ label: '字典数据', value: 'dictionary' },
|
||||
{ label: 'API数据', value: 'api' },
|
||||
{ label: '直接数据', value: 'data' },
|
||||
]"
|
||||
placeholder="请选择数据源"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 使用字典数据 -->
|
||||
<a-form-item label="用户状态">
|
||||
<sc-select
|
||||
v-model:value="form.status"
|
||||
source-type="dictionary"
|
||||
dictionary-code="user_status"
|
||||
placeholder="请选择用户状态"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 使用 API 数据 -->
|
||||
<a-form-item label="所属角色">
|
||||
<sc-select
|
||||
v-model:value="form.roleId"
|
||||
source-type="api"
|
||||
api="roles/all"
|
||||
:enable-api-cache="true"
|
||||
:immediate="true"
|
||||
:field-names="{ label: 'name', value: 'id' }"
|
||||
placeholder="请选择角色"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 多选模式 -->
|
||||
<a-form-item label="标签">
|
||||
<sc-select
|
||||
v-model:value="form.tags"
|
||||
mode="multiple"
|
||||
source-type="dictionary"
|
||||
dictionary-code="tags"
|
||||
placeholder="请选择标签"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 带搜索 -->
|
||||
<a-form-item label="省份">
|
||||
<sc-select
|
||||
v-model:value="form.province"
|
||||
source-type="api"
|
||||
api="cities/provinces"
|
||||
show-search
|
||||
:filter-option="filterOption"
|
||||
placeholder="请选择省份"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive } from 'vue'
|
||||
import scSelect from '@/components/scSelect/index.vue'
|
||||
|
||||
const form = reactive({
|
||||
dataSource: '',
|
||||
status: '',
|
||||
roleId: '',
|
||||
tags: [],
|
||||
province: '',
|
||||
})
|
||||
|
||||
// 搜索过滤
|
||||
function filterOption(input, option) {
|
||||
return option.label.toLowerCase().includes(input.toLowerCase())
|
||||
}
|
||||
</script>
|
||||
308
resources/admin/src/components/scSelect/index.vue
Normal file
308
resources/admin/src/components/scSelect/index.vue
Normal file
@@ -0,0 +1,308 @@
|
||||
<template>
|
||||
<a-select
|
||||
v-bind="$attrs"
|
||||
:loading="loading"
|
||||
:options="options"
|
||||
:field-names="fieldNames"
|
||||
@focus="handleFocus"
|
||||
>
|
||||
<template v-for="(_, slot) of $slots" #[slot]="scope">
|
||||
<slot :name="slot" v-bind="scope || {}" />
|
||||
</template>
|
||||
</a-select>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { useDictionaryStore } from '@/stores/modules/dictionary'
|
||||
import request from '@/utils/request'
|
||||
|
||||
defineOptions({
|
||||
name: 'ScSelect',
|
||||
inheritAttrs: false,
|
||||
})
|
||||
|
||||
const props = defineProps({
|
||||
// 数据源类型:data(直接数据)、api(接口数据)、dictionary(字典数据)
|
||||
sourceType: {
|
||||
type: String,
|
||||
default: 'data',
|
||||
validator: (value) => ['data', 'api', 'dictionary'].includes(value),
|
||||
},
|
||||
// 直接数据(当 sourceType 为 data 时使用)
|
||||
data: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
// API 接口地址(当 sourceType 为 api 时使用)
|
||||
api: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
// API 请求参数(当 sourceType 为 api 时使用)
|
||||
apiParams: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
// 字典编码(当 sourceType 为 dictionary 时使用)
|
||||
dictionaryCode: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
// 是否启用 API 数据缓存(当 sourceType 为 api 时使用)
|
||||
enableApiCache: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
// API 缓存时间(毫秒,当 sourceType 为 api 时使用)
|
||||
apiCacheTime: {
|
||||
type: Number,
|
||||
default: 5 * 60 * 1000, // 默认 5 分钟
|
||||
},
|
||||
// 字段映射配置
|
||||
fieldNames: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
label: 'label',
|
||||
value: 'value',
|
||||
}),
|
||||
},
|
||||
// 是否在组件挂载时立即加载数据
|
||||
immediate: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
// 数据处理函数,用于自定义数据格式转换
|
||||
dataProcessor: {
|
||||
type: Function,
|
||||
default: null,
|
||||
},
|
||||
})
|
||||
|
||||
const dictionaryStore = useDictionaryStore()
|
||||
const loading = ref(false)
|
||||
const apiData = ref(null)
|
||||
const apiCacheTime = ref(null)
|
||||
const options = computed(() => {
|
||||
switch (props.sourceType) {
|
||||
case 'data':
|
||||
return processData(props.data)
|
||||
case 'api':
|
||||
return processData(apiData.value || [])
|
||||
case 'dictionary':
|
||||
return processData(getDictionaryData())
|
||||
default:
|
||||
return []
|
||||
}
|
||||
})
|
||||
|
||||
// API 数据缓存
|
||||
const apiCache = new Map()
|
||||
|
||||
/**
|
||||
* 处理数据格式
|
||||
*/
|
||||
function processData(data) {
|
||||
if (!data || !Array.isArray(data)) {
|
||||
return []
|
||||
}
|
||||
|
||||
// 如果有自定义处理函数,使用自定义处理
|
||||
if (props.dataProcessor && typeof props.dataProcessor === 'function') {
|
||||
return props.dataProcessor(data)
|
||||
}
|
||||
|
||||
// 默认处理:确保数据有 label 和 value 字段
|
||||
return data.map((item) => {
|
||||
if (typeof item === 'string' || typeof item === 'number') {
|
||||
return {
|
||||
label: item,
|
||||
value: item,
|
||||
}
|
||||
}
|
||||
// 如果已经有 label 和 value 字段,直接返回
|
||||
if (item.label !== undefined && item.value !== undefined) {
|
||||
return item
|
||||
}
|
||||
// 尝试使用 fieldNames 映射
|
||||
const labelKey = props.fieldNames.label || 'label'
|
||||
const valueKey = props.fieldNames.value || 'value'
|
||||
return {
|
||||
label: item[labelKey] || item.name || item.title || item.id,
|
||||
value: item[valueKey] !== undefined ? item[valueKey] : item.id,
|
||||
...item,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字典数据
|
||||
*/
|
||||
function getDictionaryData() {
|
||||
if (!props.dictionaryCode) {
|
||||
return []
|
||||
}
|
||||
return dictionaryStore.dictionaries[props.dictionaryCode] || []
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载 API 数据
|
||||
*/
|
||||
async function loadApiData() {
|
||||
if (!props.api) {
|
||||
return
|
||||
}
|
||||
|
||||
// 检查缓存
|
||||
if (props.enableApiCache) {
|
||||
const cacheKey = props.api + JSON.stringify(props.apiParams)
|
||||
const cached = apiCache.get(cacheKey)
|
||||
|
||||
if (cached && Date.now() - cached.time < props.apiCacheTime) {
|
||||
apiData.value = cached.data
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await request.get(props.api, { params: props.apiParams })
|
||||
|
||||
if (res.code === 200) {
|
||||
const data = res.data || res.list || []
|
||||
apiData.value = data
|
||||
|
||||
// 缓存数据
|
||||
if (props.enableApiCache) {
|
||||
const cacheKey = props.api + JSON.stringify(props.apiParams)
|
||||
apiCache.set(cacheKey, {
|
||||
data,
|
||||
time: Date.now(),
|
||||
})
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载 API 数据失败:', error)
|
||||
apiData.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载字典数据
|
||||
*/
|
||||
async function loadDictionaryData() {
|
||||
if (!props.dictionaryCode) {
|
||||
return
|
||||
}
|
||||
|
||||
// 检查缓存
|
||||
if (dictionaryStore.dictionaries[props.dictionaryCode]) {
|
||||
return
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
await dictionaryStore.getDictionary(props.dictionaryCode)
|
||||
} catch (error) {
|
||||
console.error('加载字典数据失败:', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 focus 事件,按需加载数据
|
||||
*/
|
||||
async function handleFocus() {
|
||||
switch (props.sourceType) {
|
||||
case 'api':
|
||||
if (!apiData.value || apiData.value.length === 0) {
|
||||
await loadApiData()
|
||||
}
|
||||
break
|
||||
case 'dictionary':
|
||||
if (!dictionaryStore.dictionaries[props.dictionaryCode]) {
|
||||
await loadDictionaryData()
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 强制刷新数据
|
||||
*/
|
||||
async function refresh() {
|
||||
switch (props.sourceType) {
|
||||
case 'api':
|
||||
await loadApiData()
|
||||
break
|
||||
case 'dictionary':
|
||||
await dictionaryStore.getDictionary(props.dictionaryCode, true)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 监听数据源变化
|
||||
watch(
|
||||
() => props.data,
|
||||
(newVal) => {
|
||||
if (props.sourceType === 'data') {
|
||||
// data 类型不需要特殊处理,计算属性会自动更新
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.api,
|
||||
() => {
|
||||
if (props.sourceType === 'api') {
|
||||
apiData.value = null
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.apiParams,
|
||||
() => {
|
||||
if (props.sourceType === 'api') {
|
||||
apiData.value = null
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.dictionaryCode,
|
||||
() => {
|
||||
if (props.sourceType === 'dictionary') {
|
||||
// dictionary 变化时,数据会自动从 store 获取
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// 组件挂载时立即加载数据
|
||||
if (props.immediate) {
|
||||
switch (props.sourceType) {
|
||||
case 'api':
|
||||
loadApiData()
|
||||
break
|
||||
case 'dictionary':
|
||||
loadDictionaryData()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 暴露方法供外部调用
|
||||
defineExpose({
|
||||
refresh,
|
||||
loadApiData,
|
||||
loadDictionaryData,
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
// 组件样式继承自 a-select
|
||||
</style>
|
||||
@@ -2,10 +2,10 @@
|
||||
<div class="sc-table" ref="tableWrapper">
|
||||
<!-- 表格内容 -->
|
||||
<div class="sc-table-content" ref="tableContent">
|
||||
<a-table :columns="tableColumns" :data-source="dataSource" :loading="loading" :pagination="false"
|
||||
<a-table v-if="dataSource.length > 0" :columns="tableColumns" :data-source="dataSource" :loading="loading" :pagination="false"
|
||||
:row-key="rowKey" :row-selection="rowSelection" :scroll="scroll" :bordered="tableSettings.bordered"
|
||||
:size="tableSettings.size" :show-header="showHeader" :locale="locale" @change="handleTableChange"
|
||||
@resizeColumn="handleResizeColumn">
|
||||
:size="tableSettings.size" :show-header="showHeader" :locale="locale" :expanded-row-keys="expandedRowKeys"
|
||||
:expand-row-by-click="expandRowByClick" @change="handleTableChange" @resizeColumn="handleResizeColumn">
|
||||
<!-- 自定义单元格内容 -->
|
||||
<template #bodyCell="{ text, record, index, column }">
|
||||
<!-- 序号列 -->
|
||||
@@ -216,8 +216,20 @@ const props = defineProps({
|
||||
type: String,
|
||||
default: '暂无数据',
|
||||
},
|
||||
// 树形表格配置
|
||||
defaultExpandAll: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
expandRowByClick: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
|
||||
// 展开的行keys
|
||||
const expandedRowKeys = ref([])
|
||||
|
||||
const tableContent = useTemplateRef('tableContent')
|
||||
const tableWrapper = useTemplateRef('tableWrapper')
|
||||
let scroll = ref({
|
||||
@@ -226,6 +238,36 @@ let scroll = ref({
|
||||
y: true,
|
||||
})
|
||||
|
||||
// 递归获取所有节点的key
|
||||
const getAllNodeKeys = (nodes) => {
|
||||
const keys = []
|
||||
const traverse = (list) => {
|
||||
list.forEach(node => {
|
||||
// 如果节点有children且不为空,则该节点需要展开
|
||||
if (node.children && node.children.length > 0) {
|
||||
const key = typeof props.rowKey === 'function' ? props.rowKey(node) : node[props.rowKey]
|
||||
keys.push(key)
|
||||
traverse(node.children)
|
||||
}
|
||||
})
|
||||
}
|
||||
traverse(nodes)
|
||||
return keys
|
||||
}
|
||||
|
||||
// 监听数据变化,自动展开所有节点
|
||||
watch(
|
||||
() => props.dataSource,
|
||||
(newData) => {
|
||||
if (props.defaultExpandAll && newData && newData.length > 0) {
|
||||
expandedRowKeys.value = getAllNodeKeys(newData)
|
||||
} else {
|
||||
expandedRowKeys.value = []
|
||||
}
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
updateTableHeight()
|
||||
})
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
:pagination="false"
|
||||
:row-key="rowKey"
|
||||
:row-selection="rowSelection"
|
||||
:default-expand-all="true"
|
||||
@refresh="refreshTable"
|
||||
@select="handleSelectChange"
|
||||
@selectAll="handleSelectAll"
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<a-input-number v-model:value="form.sort" :min="0" :step="1" style="width: 100%" placeholder="请输入排序" />
|
||||
</a-form-item>
|
||||
<a-form-item label="状态" name="status">
|
||||
<a-switch v-model:checked="statusChecked" checked-children="启用" un-checked-children="禁用" />
|
||||
<sc-select v-model:value="form.status" source-type="dictionary" dictionary-code="role_status" placeholder="请选择状态" allow-clear />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<template #footer>
|
||||
@@ -30,6 +30,7 @@
|
||||
<script setup>
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import scSelect from '@/components/scSelect/index.vue'
|
||||
import authApi from '@/api/auth'
|
||||
|
||||
const emit = defineEmits(['success', 'closed'])
|
||||
@@ -50,15 +51,7 @@ const form = reactive({
|
||||
code: '',
|
||||
description: '',
|
||||
sort: 1,
|
||||
status: 1
|
||||
})
|
||||
|
||||
// 状态开关计算属性
|
||||
const statusChecked = computed({
|
||||
get: () => form.status === 1,
|
||||
set: (val) => {
|
||||
form.status = val ? 1 : 0
|
||||
}
|
||||
status: null
|
||||
})
|
||||
|
||||
// 表单引用
|
||||
@@ -132,7 +125,7 @@ const setData = (data) => {
|
||||
form.code = data.code
|
||||
form.description = data.description || ''
|
||||
form.sort = data.sort
|
||||
form.status = data.status !== undefined ? data.status : 1
|
||||
form.status = data.status !== undefined ? data.status : null
|
||||
}
|
||||
|
||||
// 暴露方法给父组件
|
||||
|
||||
@@ -38,8 +38,11 @@
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="性别" name="gender">
|
||||
<sc-select v-model:value="form.gender" source-type="dictionary" dictionary-code="gender" placeholder="请选择性别" allow-clear />
|
||||
</a-form-item>
|
||||
<a-form-item label="状态" name="status">
|
||||
<a-switch v-model:checked="statusChecked" checked-children="启用" un-checked-children="禁用" />
|
||||
<sc-select v-model:value="form.status" source-type="dictionary" dictionary-code="user_status" placeholder="请选择状态" allow-clear />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<template #footer>
|
||||
@@ -53,6 +56,7 @@
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import scUpload from '@/components/scUpload/index.vue'
|
||||
import scSelect from '@/components/scSelect/index.vue'
|
||||
import authApi from '@/api/auth'
|
||||
|
||||
const emit = defineEmits(['success', 'closed'])
|
||||
@@ -76,15 +80,8 @@ const form = reactive({
|
||||
phone: '',
|
||||
department_id: null,
|
||||
role_ids: [],
|
||||
status: 1
|
||||
})
|
||||
|
||||
// 状态开关计算属性
|
||||
const statusChecked = computed({
|
||||
get: () => form.status === 1,
|
||||
set: (val) => {
|
||||
form.status = val ? 1 : 0
|
||||
}
|
||||
gender: null,
|
||||
status: null
|
||||
})
|
||||
|
||||
// 表单引用
|
||||
@@ -204,6 +201,7 @@ const submit = async () => {
|
||||
phone: form.phone,
|
||||
department_id: form.department_id,
|
||||
role_ids: form.role_ids,
|
||||
gender: form.gender,
|
||||
status: form.status
|
||||
}
|
||||
|
||||
@@ -242,7 +240,8 @@ const setData = (data) => {
|
||||
form.phone = data.phone
|
||||
form.department_id = data.department_id
|
||||
form.role_ids = data.roles ? data.roles.map(item => item.id) : []
|
||||
form.status = data.status !== undefined ? data.status : 1
|
||||
form.gender = data.gender !== undefined ? data.gender : null
|
||||
form.status = data.status !== undefined ? data.status : null
|
||||
}
|
||||
|
||||
// 组件挂载时加载数据
|
||||
|
||||
@@ -9,13 +9,14 @@
|
||||
</a-input>
|
||||
</div>
|
||||
<div class="body">
|
||||
<a-tree v-model:selectedKeys="selectedDeptKeys" :tree-data="filteredDepartmentTree"
|
||||
:field-names="{ title: 'name', key: 'id', children: 'children' }" show-line default-expand-all @select="onDeptSelect">
|
||||
<a-tree v-if="filteredDepartmentTree.length > 0" v-model:selectedKeys="selectedDeptKeys" v-model:expandedKeys="expandedDeptKeys" :tree-data="filteredDepartmentTree"
|
||||
:field-names="{ title: 'name', key: 'id', children: 'children' }" show-line @select="onDeptSelect">
|
||||
<template #icon="{ dataRef }">
|
||||
<ApartmentOutlined v-if="dataRef.children && dataRef.children.length > 0" />
|
||||
<UserOutlined v-else />
|
||||
</template>
|
||||
</a-tree>
|
||||
<a-empty v-else description="暂无部门数据" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="right-box">
|
||||
@@ -144,7 +145,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ref, reactive, onMounted, watch } from 'vue'
|
||||
import { message, Modal } from 'ant-design-vue'
|
||||
import {
|
||||
SearchOutlined,
|
||||
@@ -225,10 +226,40 @@ const departmentTree = ref([])
|
||||
const filteredDepartmentTree = ref([])
|
||||
const selectedDeptKeys = ref([])
|
||||
const departmentKeyword = ref('')
|
||||
const expandedDeptKeys = ref([])
|
||||
|
||||
// 行key
|
||||
const rowKey = 'id'
|
||||
|
||||
// 递归获取所有部门节点的key
|
||||
const getAllDepartmentKeys = (nodes) => {
|
||||
const keys = []
|
||||
const traverse = (list) => {
|
||||
list.forEach(node => {
|
||||
// 如果节点有children且不为空,则该节点需要展开
|
||||
if (node.children && node.children.length > 0) {
|
||||
keys.push(node.id)
|
||||
traverse(node.children)
|
||||
}
|
||||
})
|
||||
}
|
||||
traverse(nodes)
|
||||
return keys
|
||||
}
|
||||
|
||||
// 监听部门树数据变化,自动展开所有节点
|
||||
watch(
|
||||
() => filteredDepartmentTree.value,
|
||||
(newData) => {
|
||||
if (newData && newData.length > 0) {
|
||||
expandedDeptKeys.value = getAllDepartmentKeys(newData)
|
||||
} else {
|
||||
expandedDeptKeys.value = []
|
||||
}
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
)
|
||||
|
||||
// 表格列配置
|
||||
const columns = [
|
||||
{ title: '头像', dataIndex: 'avatar', key: 'avatar', width: 80, align: 'center', slot: 'avatar' },
|
||||
|
||||
@@ -57,6 +57,7 @@ import { useRouter, useRoute } from 'vue-router'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { UserOutlined, LockOutlined } from '@ant-design/icons-vue'
|
||||
import { useUserStore } from '@/stores/modules/user'
|
||||
import { useDictionaryStore } from '@/stores/modules/dictionary'
|
||||
import auth from '@/api/auth'
|
||||
import config from '@/config'
|
||||
import '@/assets/style/auth.scss'
|
||||
@@ -71,6 +72,7 @@ const loginFormRef = ref(null)
|
||||
const loading = ref(false)
|
||||
|
||||
const userStore = useUserStore()
|
||||
const dictionaryStore = useDictionaryStore()
|
||||
|
||||
// Login form data
|
||||
const loginForm = reactive({
|
||||
@@ -133,6 +135,11 @@ const handleLogin = async () => {
|
||||
userStore.setPermissions(loginData.permissions)
|
||||
}
|
||||
|
||||
// 4. Load dictionary data (缓存字典数据)
|
||||
dictionaryStore.loadAllDictionaries().catch(error => {
|
||||
console.error('加载字典数据失败:', error)
|
||||
})
|
||||
|
||||
// Success message
|
||||
message.success('登录成功!')
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import systemApi from '@/api/system'
|
||||
import dictionaryCache from '@/utils/dictionaryCache'
|
||||
import { useDictionaryStore } from '@/stores/modules/dictionary'
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
@@ -146,9 +146,12 @@ const valueChecked = computed({
|
||||
}
|
||||
})
|
||||
|
||||
// 初始化字典 store
|
||||
const dictionaryStore = useDictionaryStore()
|
||||
|
||||
// 加载配置分组
|
||||
const loadGroups = async () => {
|
||||
const groups = await dictionaryCache.getItemsByCode('config_group')
|
||||
const groups = await dictionaryStore.getDictionary('config_group')
|
||||
groupOptions.value = groups.map(item => ({
|
||||
label: item.label,
|
||||
value: item.value
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
<!-- 状态 -->
|
||||
<a-form-item label="状态" name="status">
|
||||
<a-switch v-model:checked="statusChecked" checked-children="启用" un-checked-children="禁用" />
|
||||
<sc-select v-model:value="form.status" source-type="dictionary" dictionary-code="dictionary_status" placeholder="请选择状态" allow-clear />
|
||||
</a-form-item>
|
||||
|
||||
<!-- 描述 -->
|
||||
@@ -43,6 +43,7 @@
|
||||
<script setup>
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import scSelect from '@/components/scSelect/index.vue'
|
||||
import systemApi from '@/api/system'
|
||||
|
||||
// ===== Props =====
|
||||
@@ -79,18 +80,10 @@ const form = ref({
|
||||
name: '',
|
||||
code: '',
|
||||
description: '',
|
||||
status: true,
|
||||
status: null,
|
||||
sort: 0
|
||||
})
|
||||
|
||||
// ===== 计算属性:状态开关 =====
|
||||
const statusChecked = computed({
|
||||
get: () => form.value.status === true,
|
||||
set: (val) => {
|
||||
form.value.status = val ? true : false
|
||||
}
|
||||
})
|
||||
|
||||
// ===== 验证规则 =====
|
||||
// 编码唯一性验证
|
||||
const validateCodeUnique = async (rule, value) => {
|
||||
@@ -131,7 +124,7 @@ const resetForm = () => {
|
||||
name: '',
|
||||
code: '',
|
||||
description: '',
|
||||
status: true,
|
||||
status: null,
|
||||
sort: 0
|
||||
}
|
||||
formRef.value?.clearValidate()
|
||||
@@ -145,7 +138,7 @@ const setData = (data) => {
|
||||
name: data.name || '',
|
||||
code: data.code || '',
|
||||
description: data.description || '',
|
||||
status: data.status !== undefined ? data.status : true,
|
||||
status: data.status !== undefined ? data.status : null,
|
||||
sort: data.sort !== undefined ? data.sort : 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,7 +178,7 @@ import systemApi from '@/api/system'
|
||||
import scTable from '@/components/scTable/index.vue'
|
||||
import DictionaryDialog from './components/DictionaryDialog.vue'
|
||||
import ItemDialog from './components/ItemDialog.vue'
|
||||
import dictionaryCache from '@/utils/dictionaryCache'
|
||||
import { useDictionaryStore } from '@/stores/modules/dictionary'
|
||||
|
||||
// ===== 字典列表相关 =====
|
||||
const dictionaryList = ref([])
|
||||
@@ -250,6 +250,8 @@ const loadDictionaryList = async () => {
|
||||
if (res.code === 200) {
|
||||
dictionaryList.value = res.data || []
|
||||
filteredDictionaries.value = res.data || []
|
||||
// 建立字典ID到Code的映射
|
||||
dictionaryStore.buildIdToCodeMap(res.data || [])
|
||||
} else {
|
||||
message.error(res.message || '加载字典列表失败')
|
||||
}
|
||||
@@ -388,6 +390,8 @@ const handleDeleteItem = async (record) => {
|
||||
const res = await systemApi.dictionaryItems.delete.delete(record.id)
|
||||
if (res.code === 200) {
|
||||
message.success('删除成功')
|
||||
// 清除字典缓存
|
||||
dictionaryStore.clearDictionary(selectedDictionaryId.value)
|
||||
refreshTable()
|
||||
// 刷新字典列表以更新项数量
|
||||
loadDictionaryList()
|
||||
@@ -420,6 +424,8 @@ const handleBatchDelete = () => {
|
||||
const res = await systemApi.dictionaryItems.batchDelete.post({ ids })
|
||||
if (res.code === 200) {
|
||||
message.success('删除成功')
|
||||
// 清除字典缓存
|
||||
dictionaryStore.clearDictionary(selectedDictionaryId.value)
|
||||
selectedRows.value = []
|
||||
refreshTable()
|
||||
loadDictionaryList()
|
||||
@@ -458,6 +464,8 @@ const handleBatchStatus = () => {
|
||||
})
|
||||
if (res.code === 200) {
|
||||
message.success(`${statusText}成功`)
|
||||
// 清除字典缓存
|
||||
dictionaryStore.clearDictionary(selectedDictionaryId.value)
|
||||
selectedRows.value = []
|
||||
refreshTable()
|
||||
} else {
|
||||
@@ -471,11 +479,14 @@ const handleBatchStatus = () => {
|
||||
})
|
||||
}
|
||||
|
||||
// 初始化字典 store
|
||||
const dictionaryStore = useDictionaryStore()
|
||||
|
||||
// ===== 方法:字典操作成功回调 =====
|
||||
const handleDictionarySuccess = () => {
|
||||
dialog.dictionary = false
|
||||
// 清理字典缓存
|
||||
dictionaryCache.clearDictionary()
|
||||
dictionaryStore.clearCache()
|
||||
loadDictionaryList()
|
||||
}
|
||||
|
||||
@@ -483,7 +494,7 @@ const handleDictionarySuccess = () => {
|
||||
const handleItemSuccess = () => {
|
||||
dialog.item = false
|
||||
// 清理字典缓存
|
||||
dictionaryCache.clearDictionary(selectedDictionaryId.value)
|
||||
dictionaryStore.clearDictionary(selectedDictionaryId.value)
|
||||
refreshTable()
|
||||
loadDictionaryList()
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
|
||||
<!-- 启用状态 -->
|
||||
<a-form-item label="启用状态" name="is_active">
|
||||
<a-switch v-model:checked="isActiveChecked" checked-children="启用" un-checked-children="禁用" />
|
||||
<sc-select v-model:value="form.is_active" source-type="dictionary" dictionary-code="yes_no" placeholder="请选择状态" allow-clear />
|
||||
</a-form-item>
|
||||
|
||||
<!-- 排序 -->
|
||||
@@ -93,6 +93,7 @@
|
||||
<script setup>
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import scSelect from '@/components/scSelect/index.vue'
|
||||
import systemApi from '@/api/system'
|
||||
|
||||
const props = defineProps({
|
||||
@@ -125,42 +126,13 @@ const form = ref({
|
||||
expression: '* * * * *',
|
||||
timezone: 'Asia/Shanghai',
|
||||
description: '',
|
||||
is_active: true,
|
||||
is_active: null,
|
||||
run_in_background: false,
|
||||
without_overlapping: false,
|
||||
only_one: false,
|
||||
sort: 0
|
||||
})
|
||||
|
||||
// 计算属性:开关
|
||||
const isActiveChecked = computed({
|
||||
get: () => form.value.is_active === true,
|
||||
set: (val) => {
|
||||
form.value.is_active = val ? true : false
|
||||
}
|
||||
})
|
||||
|
||||
const runInBackgroundChecked = computed({
|
||||
get: () => form.value.run_in_background === true,
|
||||
set: (val) => {
|
||||
form.value.run_in_background = val ? true : false
|
||||
}
|
||||
})
|
||||
|
||||
const withoutOverlappingChecked = computed({
|
||||
get: () => form.value.without_overlapping === true,
|
||||
set: (val) => {
|
||||
form.value.without_overlapping = val ? true : false
|
||||
}
|
||||
})
|
||||
|
||||
const onlyOneChecked = computed({
|
||||
get: () => form.value.only_one === true,
|
||||
set: (val) => {
|
||||
form.value.only_one = val ? true : false
|
||||
}
|
||||
})
|
||||
|
||||
// Cron 表达式验证函数
|
||||
const validateCronExpression = (rule, value) => {
|
||||
if (!value || !value.trim()) {
|
||||
@@ -304,7 +276,7 @@ const resetForm = () => {
|
||||
expression: '* * * * *',
|
||||
timezone: 'Asia/Shanghai',
|
||||
description: '',
|
||||
is_active: true,
|
||||
is_active: null,
|
||||
run_in_background: false,
|
||||
without_overlapping: false,
|
||||
only_one: false,
|
||||
@@ -324,7 +296,7 @@ const setData = (data) => {
|
||||
expression: data.expression || '* * * * *',
|
||||
timezone: data.timezone || 'Asia/Shanghai',
|
||||
description: data.description || '',
|
||||
is_active: data.is_active !== undefined ? data.is_active : true,
|
||||
is_active: data.is_active !== undefined ? data.is_active : null,
|
||||
run_in_background: data.run_in_background || false,
|
||||
without_overlapping: data.without_overlapping || false,
|
||||
only_one: data.only_one || false,
|
||||
|
||||
199
resources/admin/src/stores/modules/dictionary.js
Normal file
199
resources/admin/src/stores/modules/dictionary.js
Normal file
@@ -0,0 +1,199 @@
|
||||
import { ref } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
import { customStorage } from '../persist'
|
||||
import systemApi from '@/api/system'
|
||||
|
||||
export const useDictionaryStore = defineStore(
|
||||
'dictionary',
|
||||
() => {
|
||||
// 字典数据缓存(按 code 缓存字典项列表)
|
||||
const dictionaries = ref({})
|
||||
// 字典元数据缓存(按 code 缓存字典信息)
|
||||
const dictionaryMeta = ref({})
|
||||
// 字典ID到Code的映射(用于通过ID清除缓存)
|
||||
const dictionaryIdToCodeMap = ref({})
|
||||
// 字典数据加载状态
|
||||
const loading = ref(false)
|
||||
// 最后加载时间
|
||||
const lastLoadTime = ref(null)
|
||||
|
||||
/**
|
||||
* 加载所有字典数据
|
||||
*/
|
||||
async function loadAllDictionaries(forceRefresh = false) {
|
||||
// 如果已加载且不是强制刷新,直接返回
|
||||
if (!forceRefresh && Object.keys(dictionaries.value).length > 0) {
|
||||
return
|
||||
}
|
||||
|
||||
if (loading.value) return
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await systemApi.dictionaryItems.all.get()
|
||||
if (res.code === 200 && res.data) {
|
||||
// 将字典数据按 code 缓存
|
||||
const dictMap = {}
|
||||
const metaMap = {}
|
||||
|
||||
res.data.forEach(dict => {
|
||||
if (dict.code) {
|
||||
// 缓存字典项列表
|
||||
dictMap[dict.code] = dict.items || []
|
||||
// 缓存字典元数据(包含id以便通过id清除缓存)
|
||||
metaMap[dict.code] = {
|
||||
id: dict.code, // 使用code作为唯一标识
|
||||
name: dict.name,
|
||||
code: dict.code,
|
||||
description: dict.description
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
dictionaries.value = dictMap
|
||||
dictionaryMeta.value = metaMap
|
||||
lastLoadTime.value = new Date().getTime()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载字典数据失败:', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据字典 code 获取字典数据
|
||||
* @param {string} code 字典编码
|
||||
* @param {boolean} forceRefresh 是否强制刷新
|
||||
* @returns {Array} 字典数据数组
|
||||
*/
|
||||
async function getDictionary(code, forceRefresh = false) {
|
||||
// 如果缓存为空或强制刷新,则重新加载所有字典
|
||||
if (!dictionaries.value || Object.keys(dictionaries.value).length === 0 || forceRefresh) {
|
||||
await loadAllDictionaries()
|
||||
}
|
||||
|
||||
return dictionaries.value[code] || []
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量获取字典数据
|
||||
* @param {Array<string>} codes 字典编码数组
|
||||
* @param {boolean} forceRefresh 是否强制刷新
|
||||
* @returns {Object} 字典数据对象
|
||||
*/
|
||||
async function getDictionaries(codes, forceRefresh = false) {
|
||||
// 如果缓存为空或强制刷新,则重新加载所有字典
|
||||
if (!dictionaries.value || Object.keys(dictionaries.value).length === 0 || forceRefresh) {
|
||||
await loadAllDictionaries()
|
||||
}
|
||||
|
||||
const result = {}
|
||||
codes.forEach(code => {
|
||||
result[code] = dictionaries.value[code] || []
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据字典 code 和 value 获取 label
|
||||
* @param {string} code 字典编码
|
||||
* @param {any} value 字典值
|
||||
* @returns {string} 字典标签
|
||||
*/
|
||||
function getLabelByValue(code, value) {
|
||||
const dict = dictionaries.value[code]
|
||||
if (!dict) return value
|
||||
|
||||
const item = dict.find(item => item.value === value)
|
||||
return item ? item.label : value
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空字典缓存
|
||||
*/
|
||||
function clearCache() {
|
||||
dictionaries.value = {}
|
||||
dictionaryMeta.value = {}
|
||||
dictionaryIdToCodeMap.value = {}
|
||||
lastLoadTime.value = null
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除特定字典的缓存
|
||||
* @param {string|number} dictionaryIdOrCode 字典ID或编码
|
||||
*/
|
||||
function clearDictionary(dictionaryIdOrCode) {
|
||||
// 如果传入的是字典ID,需要先找到对应的code
|
||||
let code = dictionaryIdOrCode
|
||||
|
||||
if (typeof dictionaryIdOrCode === 'number') {
|
||||
// 直接从ID映射表中查找
|
||||
code = dictionaryIdToCodeMap.value[dictionaryIdOrCode]
|
||||
}
|
||||
|
||||
// 删除对应字典的缓存
|
||||
if (code && dictionaries.value[code]) {
|
||||
delete dictionaries.value[code]
|
||||
delete dictionaryMeta.value[code]
|
||||
if (dictionaryIdOrCode && typeof dictionaryIdOrCode === 'number') {
|
||||
delete dictionaryIdToCodeMap.value[dictionaryIdOrCode]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 建立字典ID到Code的映射
|
||||
* @param {Array} dictionaryList 字典列表(包含id和code)
|
||||
*/
|
||||
function buildIdToCodeMap(dictionaryList) {
|
||||
dictionaryList.forEach(dict => {
|
||||
if (dict.id && dict.code) {
|
||||
dictionaryIdToCodeMap.value[dict.id] = dict.code
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新字典缓存
|
||||
* @param {boolean} force 是否强制刷新
|
||||
*/
|
||||
async function refresh(force = true) {
|
||||
await loadAllDictionaries(force)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存信息
|
||||
*/
|
||||
function getCacheInfo() {
|
||||
return {
|
||||
count: Object.keys(dictionaries.value).length,
|
||||
lastLoadTime: lastLoadTime.value
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
dictionaries,
|
||||
dictionaryMeta,
|
||||
dictionaryIdToCodeMap,
|
||||
loading,
|
||||
lastLoadTime,
|
||||
loadAllDictionaries,
|
||||
getDictionary,
|
||||
getDictionaries,
|
||||
getLabelByValue,
|
||||
clearCache,
|
||||
clearDictionary,
|
||||
buildIdToCodeMap,
|
||||
refresh,
|
||||
getCacheInfo
|
||||
}
|
||||
},
|
||||
{
|
||||
persist: {
|
||||
key: 'dictionary-store',
|
||||
storage: customStorage,
|
||||
pick: ['dictionaries', 'lastLoadTime']
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -1,147 +0,0 @@
|
||||
/**
|
||||
* 数据字典缓存工具
|
||||
* 用于缓存和管理数据字典数据
|
||||
*/
|
||||
|
||||
import systemApi from '@/api/system'
|
||||
|
||||
// 缓存存储
|
||||
const cacheStorage = new Map()
|
||||
|
||||
// 缓存过期时间(毫秒)默认1小时
|
||||
const CACHE_EXPIRE_TIME = 3600 * 1000
|
||||
|
||||
/**
|
||||
* 字典缓存管理类
|
||||
*/
|
||||
class DictionaryCacheManager {
|
||||
/**
|
||||
* 获取所有字典(带缓存)
|
||||
*/
|
||||
async getAll() {
|
||||
const cacheKey = 'all'
|
||||
const cached = this.get(cacheKey)
|
||||
|
||||
if (cached) {
|
||||
return cached
|
||||
}
|
||||
|
||||
const res = await systemApi.dictionaries.all.get()
|
||||
if (res.code === 200) {
|
||||
const data = res.data || []
|
||||
this.set(cacheKey, data)
|
||||
return data
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据编码获取字典项(带缓存)
|
||||
*/
|
||||
async getItemsByCode(code) {
|
||||
const cacheKey = `items:${code}`
|
||||
const cached = this.get(cacheKey)
|
||||
|
||||
if (cached) {
|
||||
return cached
|
||||
}
|
||||
|
||||
const res = await systemApi.public.dictionaries.code.get({ code })
|
||||
if (res.code === 200) {
|
||||
const data = res.data || []
|
||||
this.set(cacheKey, data)
|
||||
return data
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据编码获取字典(带缓存)
|
||||
*/
|
||||
async getByCode(code) {
|
||||
const cacheKey = `code:${code}`
|
||||
const cached = this.get(cacheKey)
|
||||
|
||||
if (cached) {
|
||||
return cached
|
||||
}
|
||||
|
||||
const all = await this.getAll()
|
||||
const dictionary = all.find((item) => item.code === code)
|
||||
|
||||
if (dictionary) {
|
||||
this.set(cacheKey, dictionary)
|
||||
}
|
||||
|
||||
return dictionary
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据编码获取字典项的标签
|
||||
*/
|
||||
async getLabelByCode(code, value) {
|
||||
const items = await this.getItemsByCode(code)
|
||||
const item = items.find((item) => item.value === value)
|
||||
return item ? item.label : value
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除所有缓存
|
||||
*/
|
||||
clear() {
|
||||
cacheStorage.clear()
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除指定缓存
|
||||
*/
|
||||
delete(key) {
|
||||
cacheStorage.delete(key)
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除字典相关缓存
|
||||
*/
|
||||
clearDictionary(dictionaryId) {
|
||||
// 清除特定字典的缓存(暂时清除所有,因为前端不知道字典ID对应的code)
|
||||
this.clear()
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存
|
||||
*/
|
||||
get(key) {
|
||||
const item = cacheStorage.get(key)
|
||||
|
||||
if (!item) {
|
||||
return null
|
||||
}
|
||||
|
||||
// 检查是否过期
|
||||
if (Date.now() > item.expires) {
|
||||
cacheStorage.delete(key)
|
||||
return null
|
||||
}
|
||||
|
||||
return item.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置缓存
|
||||
*/
|
||||
set(key, data) {
|
||||
const item = {
|
||||
data,
|
||||
expires: Date.now() + CACHE_EXPIRE_TIME
|
||||
}
|
||||
|
||||
cacheStorage.set(key, item)
|
||||
}
|
||||
}
|
||||
|
||||
// 创建单例实例
|
||||
const dictionaryCache = new DictionaryCacheManager()
|
||||
|
||||
export default dictionaryCache
|
||||
Reference in New Issue
Block a user