wip: crud table
This commit is contained in:
parent
9ea8ffd7fd
commit
f2e2fc6819
145
mock/api/post.js
145
mock/api/post.js
@ -1,74 +1,91 @@
|
|||||||
|
const posts = [
|
||||||
|
{
|
||||||
|
title: '使用纯css优雅配置移动端rem布局',
|
||||||
|
author: '大脸怪',
|
||||||
|
category: 'Css',
|
||||||
|
description: '通常配置rem布局会使用js进行处理,比如750的设计稿会这样...',
|
||||||
|
content: '通常配置rem布局会使用js进行处理,比如750的设计稿会这样',
|
||||||
|
isRecommend: true,
|
||||||
|
isPublish: true,
|
||||||
|
createDate: '2021-11-04T04:03:36.000Z',
|
||||||
|
updateDate: '2021-11-04T04:03:36.000Z',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Vue2&Vue3项目风格指南',
|
||||||
|
author: '大脸怪',
|
||||||
|
category: 'Vue',
|
||||||
|
description: '总结的Vue2和Vue3的项目风格',
|
||||||
|
content: '### 1. 命名风格\n\n> 文件夹如果是由多个单词组成,应该始终是横线连接 ',
|
||||||
|
isRecommend: true,
|
||||||
|
isPublish: true,
|
||||||
|
createDate: '2021-10-25T08:57:47.000Z',
|
||||||
|
updateDate: '2022-02-28T04:02:39.000Z',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '如何优雅的给图片添加水印',
|
||||||
|
author: '大脸怪',
|
||||||
|
category: 'JavaScript',
|
||||||
|
description: '优雅的给图片添加水印',
|
||||||
|
content: '我之前写过一篇文章记录了一次上传图片的优化史',
|
||||||
|
isRecommend: true,
|
||||||
|
isPublish: true,
|
||||||
|
createDate: '2021-06-24T18:46:19.000Z',
|
||||||
|
updateDate: '2021-09-23T07:51:22.000Z',
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
title: '前端缓存的理解',
|
||||||
|
author: '大脸怪',
|
||||||
|
category: 'Http',
|
||||||
|
description: '谈谈前端缓存的理解',
|
||||||
|
content: '> 背景\n\n公司有个vue-cli3移动端web项目发版更新后发现部分用户手机在钉钉内置浏览器打开出现了缓存',
|
||||||
|
isRecommend: true,
|
||||||
|
isPublish: true,
|
||||||
|
createDate: '2021-06-10T18:51:19.000Z',
|
||||||
|
updateDate: '2021-09-17T09:33:24.000Z',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Promise的五个静态方法',
|
||||||
|
author: '大脸怪',
|
||||||
|
category: 'JavaScript',
|
||||||
|
description: '简单介绍下在 Promise 类中,有5 种静态方法及它们的使用场景',
|
||||||
|
content: '## 1. Promise.all\n\n并行执行多个 promise,并等待所有 promise 都准备就绪。再对它们进行处理。',
|
||||||
|
isRecommend: true,
|
||||||
|
isPublish: true,
|
||||||
|
createDate: '2021-02-22T22:37:06.000Z',
|
||||||
|
updateDate: '2021-09-17T09:33:24.000Z',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
{
|
{
|
||||||
url: '/api/posts',
|
url: '/api/posts',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
response: () => {
|
response: (data = {}) => {
|
||||||
|
const { title, pageNo, pageSize } = data.query
|
||||||
|
let pageData = []
|
||||||
|
let total = 60
|
||||||
|
const filterData = posts.filter((item) => item.title.includes(title))
|
||||||
|
if (filterData.length) {
|
||||||
|
while (pageData.length < pageSize) {
|
||||||
|
pageData.push(filterData[Math.round(Math.random() * (filterData.length - 1))])
|
||||||
|
}
|
||||||
|
pageData = pageData.map((item, index) => ({
|
||||||
|
id: pageSize * (pageNo - 1) + index + 1,
|
||||||
|
...item,
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
total = 0
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
code: 0,
|
code: 0,
|
||||||
message: 'ok',
|
message: 'ok',
|
||||||
data: [
|
data: {
|
||||||
{
|
pageData,
|
||||||
id: 36,
|
total,
|
||||||
title: '使用纯css优雅配置移动端rem布局',
|
pageNo,
|
||||||
author: 'Ronnie',
|
pageSize,
|
||||||
category: '移动端,Css',
|
},
|
||||||
description: '通常配置rem布局会使用js进行处理,比如750的设计稿会这样...',
|
|
||||||
content: '通常配置rem布局会使用js进行处理,比如750的设计稿会这样',
|
|
||||||
isRecommend: true,
|
|
||||||
isPublish: true,
|
|
||||||
createDate: '2021-11-04T04:03:36.000Z',
|
|
||||||
updateDate: '2021-11-04T04:03:36.000Z',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 35,
|
|
||||||
title: 'Vue2&Vue3项目风格指南',
|
|
||||||
author: 'Ronnie',
|
|
||||||
category: 'Vue',
|
|
||||||
description: '总结的Vue2和Vue3的项目风格',
|
|
||||||
content: '### 1. 命名风格\n\n> 文件夹如果是由多个单词组成,应该始终是横线连接 ',
|
|
||||||
isRecommend: true,
|
|
||||||
isPublish: true,
|
|
||||||
createDate: '2021-10-25T08:57:47.000Z',
|
|
||||||
updateDate: '2022-02-28T04:02:39.000Z',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 28,
|
|
||||||
title: '如何优雅的给图片添加水印',
|
|
||||||
author: '大脸怪',
|
|
||||||
category: 'JavaScript',
|
|
||||||
description: '优雅的给图片添加水印',
|
|
||||||
content: '我之前写过一篇文章记录了一次上传图片的优化史',
|
|
||||||
isRecommend: true,
|
|
||||||
isPublish: true,
|
|
||||||
createDate: '2021-06-24T18:46:19.000Z',
|
|
||||||
updateDate: '2021-09-23T07:51:22.000Z',
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
id: 26,
|
|
||||||
title: '前端缓存的理解',
|
|
||||||
author: '大脸怪',
|
|
||||||
category: 'Http',
|
|
||||||
description: '谈谈前端缓存的理解',
|
|
||||||
content: '> 背景\n\n公司有个vue-cli3移动端web项目发版更新后发现部分用户手机在钉钉内置浏览器打开出现了缓存',
|
|
||||||
isRecommend: true,
|
|
||||||
isPublish: true,
|
|
||||||
createDate: '2021-06-10T18:51:19.000Z',
|
|
||||||
updateDate: '2021-09-17T09:33:24.000Z',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 18,
|
|
||||||
title: 'Promise的五个静态方法',
|
|
||||||
author: '大脸怪',
|
|
||||||
category: 'JavaScript',
|
|
||||||
description: '简单介绍下在 Promise 类中,有5 种静态方法及它们的使用场景',
|
|
||||||
content: '## 1. Promise.all\n\n并行执行多个 promise,并等待所有 promise 都准备就绪。再对它们进行处理。',
|
|
||||||
isRecommend: true,
|
|
||||||
isPublish: true,
|
|
||||||
createDate: '2021-02-22T22:37:06.000Z',
|
|
||||||
updateDate: '2021-09-17T09:33:24.000Z',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -5,8 +5,12 @@
|
|||||||
</n-space>
|
</n-space>
|
||||||
|
|
||||||
<div flex-shrink-0>
|
<div flex-shrink-0>
|
||||||
<n-button secondary type="primary">重置</n-button>
|
<n-button secondary type="primary" @click="emit('reset')">重置</n-button>
|
||||||
<n-button ml-20 type="primary">搜索</n-button>
|
<n-button ml-20 type="primary" @click="emit('search')">搜索</n-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
const emit = defineEmits(['search', 'reset'])
|
||||||
|
</script>
|
||||||
|
@ -1,16 +1,106 @@
|
|||||||
<template>
|
<template>
|
||||||
<QueryBar v-if="$slots.queryBar" mb-30>
|
<QueryBar v-if="$slots.queryBar" mb-30 @search="handleSearch" @reset="handleReset">
|
||||||
<slot name="queryBar" />
|
<slot name="queryBar" />
|
||||||
</QueryBar>
|
</QueryBar>
|
||||||
<slot />
|
|
||||||
|
<n-data-table
|
||||||
|
:remote="remote"
|
||||||
|
:loading="loading"
|
||||||
|
:scroll-x="scrollX"
|
||||||
|
:columns="columns"
|
||||||
|
:data="tableData"
|
||||||
|
:row-key="(row) => row[rowKey]"
|
||||||
|
:pagination="isPagination ? pagination : false"
|
||||||
|
@update:checked-row-keys="onChecked"
|
||||||
|
@update:page="onPageChange"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
/**
|
||||||
|
* @remote true: 后端分页 false: 前端分页
|
||||||
|
*/
|
||||||
|
remote: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* @remote 是否分页
|
||||||
|
*/
|
||||||
|
isPagination: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
scrollX: {
|
||||||
|
type: Number,
|
||||||
|
default: 1200,
|
||||||
|
},
|
||||||
|
rowKey: {
|
||||||
|
type: String,
|
||||||
|
default: 'id',
|
||||||
|
},
|
||||||
|
columns: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
queryForm: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
getData: {
|
||||||
|
type: Function,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:queryForm', 'onChecked'])
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
|
const initQuery = { ...props.queryForm }
|
||||||
const tableData = ref([])
|
const tableData = ref([])
|
||||||
const showModal = ref(true)
|
const pagination = reactive({ page: 1, pageSize: 10, itemCount: 100 })
|
||||||
const segmented = {
|
|
||||||
content: 'soft',
|
async function handleQuery(extraParams = {}) {
|
||||||
footer: 'soft',
|
try {
|
||||||
|
loading.value = true
|
||||||
|
const res = await props.getData(
|
||||||
|
{ ...props.queryForm, ...extraParams },
|
||||||
|
{ pageNo: pagination.page, pageSize: pagination.pageSize }
|
||||||
|
)
|
||||||
|
tableData.value = res?.pageData || res
|
||||||
|
pagination.itemCount = res.total ?? res.length
|
||||||
|
} catch (error) {
|
||||||
|
tableData.value = []
|
||||||
|
pagination.itemCount = 0
|
||||||
|
$message.error(error.message)
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
function handleSearch() {
|
||||||
|
pagination.page = 1
|
||||||
|
handleQuery()
|
||||||
|
}
|
||||||
|
async function handleReset() {
|
||||||
|
emit('update:queryForm', { ...initQuery })
|
||||||
|
await nextTick()
|
||||||
|
pagination.page = 1
|
||||||
|
handleQuery()
|
||||||
|
}
|
||||||
|
function onChecked(rowKeys) {
|
||||||
|
if (props.columns.some((item) => item.type === 'selection')) {
|
||||||
|
emit('onChecked', rowKeys)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function onPageChange(currentPage) {
|
||||||
|
pagination.page = currentPage
|
||||||
|
if (props.remote) {
|
||||||
|
handleQuery()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
handleSearch,
|
||||||
|
handleReset,
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
@ -6,51 +6,172 @@
|
|||||||
</n-button>
|
</n-button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<CrudTable>
|
<CrudTable
|
||||||
|
ref="$table"
|
||||||
|
v-model:query-form="queryForm"
|
||||||
|
:scroll-x="1600"
|
||||||
|
:columns="columns"
|
||||||
|
:get-data="getTableData"
|
||||||
|
@on-checked="onChecked"
|
||||||
|
>
|
||||||
<template #queryBar>
|
<template #queryBar>
|
||||||
<QueryBarItem label="标题" :label-width="50">
|
<QueryBarItem label="标题" :label-width="50">
|
||||||
<n-input v-model:value="queryForm.title" type="text" placeholder="请输入标题" />
|
<n-input v-model:value="queryForm.title" type="text" placeholder="请输入标题" />
|
||||||
</QueryBarItem>
|
</QueryBarItem>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<n-data-table
|
|
||||||
:loading="loading"
|
|
||||||
:scroll-x="1600"
|
|
||||||
:data="tableData.filter((item) => item.title.includes(queryForm.title))"
|
|
||||||
:columns="columns"
|
|
||||||
:pagination="pagination"
|
|
||||||
:row-key="(row) => row.id"
|
|
||||||
@update:checked-row-keys="handleCheck"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- 新增/编辑/查看 -->
|
|
||||||
<CrudModal v-model:visible="modalVisible" :title="modalTitle" @on-save="handleSave"> 内容 </CrudModal>
|
|
||||||
</CrudTable>
|
</CrudTable>
|
||||||
|
<!-- 新增/编辑/查看 -->
|
||||||
|
<CrudModal v-model:visible="modalVisible" :title="modalTitle" @on-save="handleSave"> 内容 </CrudModal>
|
||||||
</CommonPage>
|
</CommonPage>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { usePostTable } from './usePostTable'
|
import { NButton, NSwitch } from 'naive-ui'
|
||||||
|
import { formatDateTime } from '@/utils'
|
||||||
|
import { renderIcon } from '@/utils/icon'
|
||||||
|
import api from './api'
|
||||||
|
|
||||||
const queryForm = reactive({
|
const $table = ref(null)
|
||||||
|
const queryForm = ref({
|
||||||
title: '',
|
title: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
async function getTableData(query = {}, pagination = {}) {
|
||||||
|
const { pageSize = 10, pageNo = 1 } = pagination
|
||||||
|
try {
|
||||||
|
// * 参数可自定义,如不需要后端分页则可以不传 pagination 相关参数
|
||||||
|
const res = await api.getPosts({ ...query, pageSize, pageNo })
|
||||||
|
if (res.code === 0) {
|
||||||
|
return Promise.resolve(res.data)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 选中事件
|
||||||
|
function onChecked(rowKeys) {
|
||||||
|
if (rowKeys.length) $message.info(`选中${rowKeys.join(' ')}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{ type: 'selection' },
|
||||||
|
{ 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: '创建时间',
|
||||||
|
key: 'createDate',
|
||||||
|
width: 150,
|
||||||
|
render(row) {
|
||||||
|
return h('span', formatDateTime(row['createDate']))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '最后更新时间',
|
||||||
|
key: 'updateDate',
|
||||||
|
width: 150,
|
||||||
|
render(row) {
|
||||||
|
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,
|
||||||
|
align: 'center',
|
||||||
|
fixed: 'right',
|
||||||
|
render(row) {
|
||||||
|
return [
|
||||||
|
h(
|
||||||
|
NButton,
|
||||||
|
{
|
||||||
|
size: 'small',
|
||||||
|
type: 'primary',
|
||||||
|
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),
|
||||||
|
},
|
||||||
|
{ default: () => '删除', icon: renderIcon('material-symbols:delete-outline', { size: 14 }) }
|
||||||
|
),
|
||||||
|
]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
$table.value?.handleSearch()
|
||||||
|
})
|
||||||
|
|
||||||
const modalVisible = ref(false)
|
const modalVisible = ref(false)
|
||||||
const modalTitle = ref('新增文章')
|
const modalTitle = ref('新增文章')
|
||||||
|
|
||||||
const pagination = reactive({ pageSize: 10 })
|
function handleDelete(row) {
|
||||||
const { loading, columns, tableData, initColumns, initTableData } = usePostTable()
|
if (row && row.id) {
|
||||||
|
$dialog.confirm({
|
||||||
|
content: '确定删除?',
|
||||||
|
confirm() {
|
||||||
|
$message.success('删除成功')
|
||||||
|
initTableData()
|
||||||
|
},
|
||||||
|
cancel() {
|
||||||
|
$message.success('已取消')
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onBeforeMount(() => {
|
function handleEdit(row) {
|
||||||
initColumns()
|
modalTitle.value = '编辑文章'
|
||||||
initTableData()
|
modalVisible.value = true
|
||||||
})
|
}
|
||||||
|
|
||||||
function handleSave() {
|
function handleSave() {
|
||||||
modalVisible.value = false
|
modalVisible.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCheck(rowKeys) {
|
|
||||||
if (rowKeys.length) $message.info(`选中${rowKeys.join(' ')}`)
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
Loading…
Reference in New Issue
Block a user