From b59e47b5dd496cfc197e3425aadd7bfa007d0054 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E4=BC=A0=E9=BE=99?= Date: Sat, 3 Sep 2022 22:28:37 +0800 Subject: [PATCH] feat: finish curd table --- mock/api/post.js | 44 ++++- src/components/table/CrudModal.vue | 6 +- src/composables/index.js | 1 + src/composables/useCRUD.js | 99 +++++++++++ src/utils/http/interceptors.js | 9 +- src/views/examples/table/post/api.js | 8 +- src/views/examples/table/post/index.vue | 216 +++++++++++++++--------- 7 files changed, 292 insertions(+), 91 deletions(-) create mode 100644 src/composables/index.js diff --git a/mock/api/post.js b/mock/api/post.js index 9195bfa..579c533 100644 --- a/mock/api/post.js +++ b/mock/api/post.js @@ -12,7 +12,7 @@ const posts = [ }, { title: 'Vue2&Vue3项目风格指南', - author: '大脸怪', + author: 'Ronnie', category: 'Vue', description: '总结的Vue2和Vue3的项目风格', content: '### 1. 命名风格\n\n> 文件夹如果是由多个单词组成,应该始终是横线连接 ', @@ -93,4 +93,46 @@ export default [ } }, }, + { + url: '/api/post', + method: 'post', + response: ({ body }) => { + return { + code: 0, + message: 'ok', + data: body, + } + }, + }, + { + url: '/api/post/:id', + method: 'put', + response: ({ query, body }) => { + return { + code: 0, + message: 'ok', + data: { + id: query.id, + body, + }, + } + }, + }, + { + url: '/api/post/:id', + method: 'delete', + response: ({ query }) => { + if (!query.id) { + return { code: -1, message: '删除失败,id不能为空' } + } else { + return { + code: 0, + message: 'ok', + data: { + id: query.id, + }, + } + } + }, + }, ] diff --git a/src/components/table/CrudModal.vue b/src/components/table/CrudModal.vue index 40404f4..431eb15 100644 --- a/src/components/table/CrudModal.vue +++ b/src/components/table/CrudModal.vue @@ -5,7 +5,7 @@ @@ -30,6 +30,10 @@ const props = defineProps({ type: Boolean, required: true, }, + loading: { + type: Boolean, + default: false, + }, }) const emit = defineEmits(['update:visible', 'onSave']) diff --git a/src/composables/index.js b/src/composables/index.js new file mode 100644 index 0000000..e410b83 --- /dev/null +++ b/src/composables/index.js @@ -0,0 +1 @@ +export { default as useCRUD } from './useCRUD' diff --git a/src/composables/useCRUD.js b/src/composables/useCRUD.js index e683ac5..e2ea6f7 100644 --- a/src/composables/useCRUD.js +++ b/src/composables/useCRUD.js @@ -1,5 +1,104 @@ +import { isNullOrWhitespace } from '../utils/is' + const ACTIONS = { view: '查看', edit: '编辑', add: '新增', } + +export default function ({ name, initForm = {}, doCreate, doDelete, doUpdate, refresh }) { + const modalVisible = ref(false) + const modalAction = ref('') + const modalTitle = computed(() => ACTIONS[modalAction.value] + name) + const modalLoading = ref(false) + const modalFormRef = ref(null) + const modalForm = ref({ ...initForm }) + + /** 新增 */ + function handleAdd() { + modalAction.value = 'add' + modalVisible.value = true + modalForm.value = { ...initForm } + } + + /** 修改 */ + function handleEdit(row) { + modalAction.value = 'edit' + modalVisible.value = true + modalForm.value = { ...row } + } + + /** 查看 */ + function handleView(row) { + modalAction.value = 'view' + modalVisible.value = true + modalForm.value = { ...row } + } + + /** 保存 */ + function handleSave() { + if (!['edit', 'add'].includes(modalAction.value)) { + modalVisible.value = false + return + } + modalFormRef.value?.validate(async (err) => { + if (err) return + const actions = { + add: { + api: () => doCreate(modalForm.value), + cb: () => $message.success('新增成功'), + }, + edit: { + api: () => doUpdate(modalForm.value), + cb: () => $message.success('编辑成功'), + }, + } + const action = actions[modalAction.value] + + try { + modalLoading.value = true + const data = await action.api() + action.cb() + modalLoading.value = modalVisible.value = false + data && refresh(data) + } catch (error) { + $message.error('操作失败') + modalLoading.value = false + } + }) + } + + /** 删除 */ + function handleDelete(id, confirmOptions) { + if (isNullOrWhitespace(id)) return + $dialog.confirm({ + content: '确定删除?', + async confirm() { + try { + modalLoading.value = true + const data = await doDelete(id) + $message.success('删除成功') + modalLoading.value = false + refresh(data) + } catch (error) { + modalLoading.value = false + } + }, + ...confirmOptions, + }) + } + + return { + modalVisible, + modalAction, + modalTitle, + modalLoading, + handleAdd, + handleDelete, + handleEdit, + handleView, + handleSave, + modalForm, + modalFormRef, + } +} diff --git a/src/utils/http/interceptors.js b/src/utils/http/interceptors.js index 160941a..51ce7d8 100644 --- a/src/utils/http/interceptors.js +++ b/src/utils/http/interceptors.js @@ -35,7 +35,11 @@ export function reqReject(error) { } export function repResolve(response) { - return response?.data + if (response?.data?.code !== 0) { + $message.error(response?.data?.message || '操作异常') + return Promise.reject(response?.data) + } + return Promise.resolve(response?.data) } export function repReject(error) { @@ -67,5 +71,6 @@ export function repReject(error) { } } console.error(`【${code}】 ${error}`) - return Promise.resolve({ code, message, error }) + $message.error(message || '操作异常') + return Promise.reject({ code, message, error }) } diff --git a/src/views/examples/table/post/api.js b/src/views/examples/table/post/api.js index 9416f24..d17fbdf 100644 --- a/src/views/examples/table/post/api.js +++ b/src/views/examples/table/post/api.js @@ -3,11 +3,7 @@ import request from '@/utils/http' export default { getPosts: (params = {}) => request.get('posts', { params }), getPostById: (id) => request.get(`/post/${id}`), - savePost: (id, data = {}) => { - if (id) { - return request.put(`/post/${id}`, data) - } - return request.post('/post', data) - }, + addPost: (data) => request.post('/post', data), + updatePost: (data) => request.put(`/post/${data.id}`, data), deletePost: (id) => request.delete(`/post/${id}`), } diff --git a/src/views/examples/table/post/index.vue b/src/views/examples/table/post/index.vue index 7615c40..7af4293 100644 --- a/src/views/examples/table/post/index.vue +++ b/src/views/examples/table/post/index.vue @@ -1,7 +1,7 @@ @@ -30,29 +84,42 @@ import { NButton, NSwitch } from 'naive-ui' import { formatDateTime } from '@/utils' import { renderIcon } from '@/utils/icon' +import { useCRUD } from '@/composables' import api from './api' +import { isNullOrUndef } from '@/utils/is' const $table = ref(null) -/** queryBar参数 */ -const queryItems = ref({}) -/** 可选,用于补充参数 */ +/** QueryBar筛选参数(可选) */ +const queryItems = ref({ + title: '', +}) +/** 补充参数(可选) */ const extraParams = ref({}) -// 选中事件 -function onChecked(rowKeys) { - if (rowKeys.length) $message.info(`选中${rowKeys.join(' ')}`) -} +onMounted(() => { + $table.value?.handleSearch() +}) const columns = [ - { type: 'selection' }, + { type: 'selection', fixed: 'left' }, + { + title: '发布', + key: 'isPublish', + width: 60, + align: 'center', + fixed: 'left', + render(row) { + return h(NSwitch, { + size: 'small', + rubberBand: false, + value: row['isPublish'], + loading: !!row.publishing, + onUpdateValue: () => handlePublish(row), + }) + }, + }, { title: '标题', key: 'title', width: 150, ellipsis: { tooltip: true } }, { title: '分类', key: 'category', width: 80, ellipsis: { tooltip: true } }, - { - title: '描述', - key: 'description', - width: 200, - ellipsis: { tooltip: true }, - }, { title: '创建人', key: 'author', width: 80 }, { title: '创建时间', @@ -70,42 +137,10 @@ const columns = [ return h('span', formatDateTime(row['updateDate'])) }, }, - { - title: '推荐', - key: 'isRecommend', - width: 120, - align: 'center', - fixed: 'right', - render(row) { - return h(NSwitch, { - size: 'small', - value: row['isRecommend'], - rubberBand: false, - loading: !!row.recommending, - onUpdateValue: () => handleRecommend(row), - }) - }, - }, - { - title: '发布', - key: 'isPublish', - width: 120, - align: 'center', - fixed: 'right', - render(row) { - return h(NSwitch, { - size: 'small', - rubberBand: false, - value: row['isPublish'], - loading: !!row.publishing, - onUpdateValue: () => handlePublish(row), - }) - }, - }, { title: '操作', key: 'actions', - width: 200, + width: 240, align: 'center', fixed: 'right', render(row) { @@ -115,17 +150,29 @@ const columns = [ { size: 'small', type: 'primary', + secondary: true, + onClick: () => handleView(row), + }, + { default: () => '查看', icon: renderIcon('majesticons:eye-line', { size: 14 }) } + ), + h( + NButton, + { + size: 'small', + type: 'primary', + style: 'margin-left: 15px;', onClick: () => handleEdit(row), }, { default: () => '编辑', icon: renderIcon('material-symbols:edit-outline', { size: 14 }) } ), + h( NButton, { size: 'small', type: 'error', style: 'margin-left: 15px;', - onClick: () => handleDelete(row), + onClick: () => handleDelete(row.id), }, { default: () => '删除', icon: renderIcon('material-symbols:delete-outline', { size: 14 }) } ), @@ -134,34 +181,41 @@ const columns = [ }, ] -onMounted(() => { - $table.value?.handleSearch() +// 选中事件 +function onChecked(rowKeys) { + if (rowKeys.length) $message.info(`选中${rowKeys.join(' ')}`) +} + +// 发布 +function handlePublish(row) { + if (isNullOrUndef(row.id)) return + + row.publishing = true + setTimeout(() => { + row.isPublish = !row.isPublish + row.publishing = false + $message?.success(row.isPublish ? '已发布' : '已取消发布') + }, 1000) +} + +const { + modalVisible, + modalAction, + modalTitle, + modalLoading, + handleAdd, + handleDelete, + handleEdit, + handleView, + handleSave, + modalForm, + modalFormRef, +} = useCRUD({ + name: '文章', + initForm: { author: '大脸怪' }, + doCreate: api.addPost, + doDelete: api.deletePost, + doUpdate: api.updatePost, + refresh: () => $table.value?.handleSearch(), }) - -const modalVisible = ref(false) -const modalTitle = ref('新增文章') - -function handleDelete(row) { - if (row && row.id) { - $dialog.confirm({ - content: '确定删除?', - confirm() { - $message.success('删除成功') - initTableData() - }, - cancel() { - $message.success('已取消') - }, - }) - } -} - -function handleEdit(row) { - modalTitle.value = '编辑文章' - modalVisible.value = true -} - -function handleSave() { - modalVisible.value = false -}