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 @@
-
+
新建文章
@@ -10,19 +10,73 @@
ref="$table"
v-model:query-items="queryItems"
:extra-params="extraParams"
- :scroll-x="1600"
+ :scroll-x="1200"
:columns="columns"
:get-data="api.getPosts"
@on-checked="onChecked"
>
-
+
- 内容
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -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
-}