feat: 新增 Vxe-Table 示例 (#22)

* feat: 新增 Vxe-Table 示例

* fix: table api type
This commit is contained in:
nevlf
2022-10-25 17:29:28 +08:00
committed by GitHub
parent 28acd67472
commit 91c210a284
14 changed files with 985 additions and 10 deletions

View File

@@ -6,7 +6,7 @@ interface ICreateTableDataApi {
}
interface IUpdateTableDataApi {
id: number
id: string
username: string
password?: string
}
@@ -31,7 +31,7 @@ export function createTableDataApi(data: ICreateTableDataApi) {
}
/** 删 */
export function deleteTableDataApi(id: number) {
export function deleteTableDataApi(id: string) {
return request({
url: `table/${id}`,
method: "delete"

View File

@@ -13,6 +13,8 @@ import "uno.css"
import "normalize.css"
import "element-plus/dist/index.css"
import "element-plus/theme-chalk/dark/css-vars.css"
import "vxe-table/lib/style.css"
import "vxe-table-plugin-element/dist/style.css"
import "@/styles/index.scss"
const app = createApp(App)

View File

@@ -1,8 +1,10 @@
import { type App } from "vue"
import { loadElementPlus } from "./element-pus"
import { loadElementPlusIcon } from "./element-pus-icon"
import { loadVxeTable } from "./vxe-table"
export function loadPlugins(app: App) {
loadElementPlus(app)
loadElementPlusIcon(app)
loadVxeTable(app)
}

View File

@@ -0,0 +1,66 @@
import { type App } from "vue"
// https://vxetable.cn/#/table/start/install
import VXETable from "vxe-table"
// https://github.com/x-extends/vxe-table-plugin-element
import VXETablePluginElement from "vxe-table-plugin-element"
VXETable.use(VXETablePluginElement)
/** 全局默认参数 */
VXETable.setup({
/** 全局尺寸 */
size: "medium",
/** 全局 zIndex 起始值,如果项目的的 z-index 样式值过大时就需要跟随设置更大,避免被遮挡 */
zIndex: 9999,
/** 版本号,对于某些带数据缓存的功能有用到,上升版本号可以用于重置数据 */
version: 0,
/** 全局 loading 提示内容,如果为 null 则不显示文本 */
loadingText: null,
table: {
showHeader: true,
showOverflow: "tooltip",
showHeaderOverflow: "tooltip",
autoResize: true,
// stripe: false,
border: false,
// round: false,
emptyText: "暂无数据",
rowConfig: {
isHover: true,
isCurrent: true
},
columnConfig: {
resizable: true
},
align: "center",
headerAlign: "center",
/** 行数据的唯一主键字段名 */
rowId: "_VXE_ID"
},
pager: {
// size: "medium",
/** 配套的样式 */
perfect: false,
pageSize: 10,
pagerCount: 7,
pageSizes: [10, 20, 50],
layouts: ["Total", "PrevJump", "PrevPage", "Number", "NextPage", "NextJump", "Sizes", "FullJump"]
},
modal: {
minWidth: 500,
minHeight: 400,
lockView: true,
mask: true,
// duration: 3000,
// marginSize: 20,
dblclickZoom: false,
showTitleOverflow: true,
transfer: true,
draggable: false
}
})
export function loadVxeTable(app: App) {
/** Vxe-Table 组件完整引入 */
app.use(VXETable)
}

View File

@@ -2,6 +2,8 @@
@import "./variables.css";
// Transition
@import "./transition.scss";
// Vxe-Table
@import "./vxe-table.scss";
// 注册多主题
@import "./theme/register.scss";

34
src/styles/vxe-table.scss Normal file
View File

@@ -0,0 +1,34 @@
/** 自定义 vxe-table 样式 */
.vxe-grid {
// 表单
&--form-wrapper {
.vxe-form {
padding: 10px 20px;
margin-bottom: 20px;
}
}
// 工具栏
&--toolbar-wrapper {
.vxe-toolbar {
padding: 20px;
}
}
// 分页
&--pager-wrapper {
.vxe-pager {
height: 70px;
&--wrapper {
@media screen and (max-width: 750px) {
.vxe-pager--total,
.vxe-pager--sizes,
.vxe-pager--jump {
display: none;
}
}
}
}
}
}

View File

@@ -1,8 +1,8 @@
import dayjs from "dayjs"
/** 格式化时间 */
export const formatDateTime = (time: null | string) => {
if (time == null || time === "") {
export const formatDateTime = (time: string | number | Date) => {
if (!time) {
return "N/A"
}
const date = new Date(time)

View File

@@ -69,7 +69,7 @@ const handleDelete = (row: any) => {
//#endregion
//#region 改
const currentUpdateId = ref<undefined | number>(undefined)
const currentUpdateId = ref<undefined | string>(undefined)
const handleUpdate = (row: any) => {
currentUpdateId.value = row.id
formData.username = row.username
@@ -90,8 +90,8 @@ const getTableData = () => {
getTableDataApi({
currentPage: paginationData.currentPage,
size: paginationData.pageSize,
username: searchData.username === "" ? undefined : searchData.username,
phone: searchData.phone === "" ? undefined : searchData.phone
username: searchData.username || undefined,
phone: searchData.phone || undefined
})
.then((res: any) => {
paginationData.total = res.data.total

View File

@@ -1,7 +1,383 @@
<script lang="ts" setup></script>
<script lang="ts" setup>
import { nextTick, reactive, ref } from "vue"
import { type ElMessageBoxOptions, ElMessageBox, ElMessage } from "element-plus"
import { deleteTableDataApi, getTableDataApi } from "@/api/table"
import RoleColumnSolts from "./tsx/RoleColumnSolts"
import StatusColumnSolts from "./tsx/StatusColumnSolts"
import {
type VxeGridInstance,
type VxeGridProps,
type VxeModalInstance,
type VxeModalProps,
type VxeFormInstance,
type VxeFormProps,
type VxeGridPropTypes,
type VxeFormDefines
} from "vxe-table"
//#region vxe-grid
interface IRowMeta {
id: string
username: string
roles: string
phone: string
email: string
status: boolean
createTime: string
/** vxe-table 自动添加上去的属性 */
_VXE_ID?: string
}
const xGridDom = ref<VxeGridInstance>()
const xGridOpt: VxeGridProps = reactive({
loading: true,
autoResize: true,
/** 分页配置项 */
pagerConfig: {
align: "right"
},
/** 表单配置项 */
formConfig: {
items: [
{
field: "username",
itemRender: {
name: "$input",
props: { placeholder: "用户名", clearable: true }
}
},
{
field: "phone",
itemRender: {
name: "$input",
props: { placeholder: "手机号", clearable: true }
}
},
{
itemRender: {
name: "$buttons",
children: [
{
props: { type: "submit", content: "查询", status: "primary" }
},
{
props: { type: "reset", content: "重置" }
}
]
}
}
]
},
/** 工具栏配置 */
toolbarConfig: {
refresh: true,
custom: true,
slots: { buttons: "toolbar-btns" }
},
/** 自定义列配置项 */
customConfig: {
/** 是否允许列选中 */
checkMethod: ({ column }) => !["username"].includes(column.field)
},
/** 列配置 */
columns: [
{
type: "checkbox",
width: "50px"
},
{
field: "username",
title: "用户名"
},
{
field: "roles",
title: "角色",
/** 自定义列与 type: "html" 的列一起使用,会产生错误,所以采用 TSX 实现 */
slots: RoleColumnSolts
},
{
field: "phone",
title: "手机号"
},
{
field: "email",
title: "邮箱"
},
{
field: "status",
title: "状态",
slots: StatusColumnSolts
},
{
field: "createTime",
title: "创建时间"
},
{
title: "操作",
width: "150px",
fixed: "right",
showOverflow: false,
slots: { default: "row-operate" }
}
],
/** 数据代理配置项(基于 Promise API */
proxyConfig: {
/** 启用动态序号代理 */
seq: true,
/** 是否代理表单 */
form: true,
/** 是否自动加载,默认为 true */
// autoLoad: false,
props: {
total: "total"
},
ajax: {
query: ({ page, form }: VxeGridPropTypes.ProxyAjaxQueryParams) => {
xGridOpt.loading = true
crudStore.clearTable()
return new Promise<any>((resolve: Function) => {
let total = 0
let result: IRowMeta[] = []
/** 加载数据 */
const callback = (res: any) => {
if (res && res.data) {
const resData = res.data
// 总数
if (Number.isInteger(resData.total)) {
total = resData.total
}
// 分页数据
if (Array.isArray(resData.list)) {
result = resData.list
}
}
xGridOpt.loading = false
resolve({ total, result })
}
/** 接口需要的参数 */
const params = {
username: form.username || undefined,
phone: form.phone || undefined,
size: page.pageSize,
currentPage: page.currentPage
}
/** 调用接口 */
getTableDataApi(params).then(callback).catch(callback)
})
}
}
}
})
//#endregion
//#region vxe-modal
const xModalDom = ref<VxeModalInstance>()
const xModalOpt: VxeModalProps = reactive({
title: "",
showClose: true,
escClosable: true,
maskClosable: true,
beforeHideMethod: () => {
xFormDom.value?.clearValidate()
return Promise.resolve()
}
})
//#endregion
//#region vxe-form
const xFormDom = ref<VxeFormInstance>()
const xFormOpt = reactive<VxeFormProps>({
span: 24,
titleWidth: "100px",
loading: false,
/** 是否显示标题冒号 */
titleColon: false,
/** 表单数据 */
data: {
username: "",
password: ""
},
/** 项列表 */
items: [
{
field: "username",
title: "用户名",
itemRender: { name: "$input", props: { placeholder: "请输入" } }
},
{
field: "password",
title: "密码",
itemRender: { name: "$input", props: { placeholder: "请输入" } }
},
{
align: "right",
itemRender: {
name: "$buttons",
children: [
{ props: { content: "取消" }, events: { click: () => xModalDom.value?.close() } },
{
props: { type: "submit", content: "确定", status: "primary" },
events: { click: () => crudStore.onSubmitForm() }
}
]
}
}
],
/** 校验规则 */
rules: {
username: [
{
required: true,
validator: ({ itemValue }) => {
if (!itemValue) {
return new Error("请输入")
}
if (!itemValue.trim()) {
return new Error("空格无效")
}
}
}
],
password: [
{
required: true,
validator: ({ itemValue }) => {
if (!itemValue) {
return new Error("请输入")
}
if (!itemValue.trim()) {
return new Error("空格无效")
}
}
}
]
}
})
//#endregion
//#region CRUD
const crudStore = reactive({
/** 表单类型修改true 新增false */
isUpdate: true,
/** 加载表格数据 */
commitQuery: () => xGridDom.value?.commitProxy("query"),
/** 清空表格数据 */
clearTable: () => xGridDom.value?.reloadData([]),
/** 点击显示弹窗 */
onShowModal: (row?: IRowMeta) => {
if (row) {
crudStore.isUpdate = true
xModalOpt.title = "修改用户"
// 赋值
xFormOpt.data.username = row.username
} else {
crudStore.isUpdate = false
xModalOpt.title = "新增用户"
}
// 禁用表单项
if (xFormOpt.items) {
if (xFormOpt.items[0]?.itemRender?.props) {
xFormOpt.items[0].itemRender.props.disabled = crudStore.isUpdate
}
}
xModalDom.value?.open()
nextTick(() => {
!crudStore.isUpdate && xFormDom.value?.reset()
xFormDom.value?.clearValidate()
})
},
/** 确定并保存 */
onSubmitForm: () => {
if (xFormOpt.loading) return
xFormDom.value?.validate((errMap?: VxeFormDefines.ValidateErrorMapParams) => {
if (errMap) return
xFormOpt.loading = true
const callback = (err?: any) => {
xFormOpt.loading = false
if (err) return
xModalDom.value?.close()
ElMessage.success("操作成功")
!crudStore.isUpdate && crudStore.afterInsert()
crudStore.commitQuery()
}
if (crudStore.isUpdate) {
// 调用修改接口
setTimeout(() => callback(), 1000)
} else {
// 调用新增接口
setTimeout(() => callback(), 1000)
}
})
},
/** 新增后是否跳入最后一页 */
afterInsert: () => {
const pager: VxeGridPropTypes.ProxyAjaxQueryPageParams = xGridDom.value?.getProxyInfo()?.pager
if (pager) {
const currTotal: number = pager.currentPage * pager.pageSize
if (currTotal === pager.total) {
++pager.currentPage
}
}
},
/** 删除 */
onDelete: (row: IRowMeta) => {
const tip = `确定 <strong style='color:red;'>删除</strong> 用户 <strong style='color:#409eff;'>${row.username}</strong> `
const config: ElMessageBoxOptions = {
type: "warning",
showClose: true,
closeOnClickModal: true,
closeOnPressEscape: true,
cancelButtonText: "取消",
confirmButtonText: "确定",
dangerouslyUseHTMLString: true
}
ElMessageBox.confirm(tip, "提示", config)
.then(() => {
deleteTableDataApi(row.id)
.then(() => {
ElMessage.success("删除成功")
crudStore.afterDelete()
crudStore.commitQuery()
})
.catch(() => 1)
})
.catch(() => 1)
},
/** 删除后是否返回上一页 */
afterDelete: () => {
const tableData: IRowMeta[] = xGridDom.value!.getData()
const pager: VxeGridPropTypes.ProxyAjaxQueryPageParams = xGridDom.value?.getProxyInfo()?.pager
if (pager && pager.currentPage > 1 && tableData.length === 1) {
--pager.currentPage
}
},
/** 更多自定义方法 */
moreFunc: () => {}
})
//#endregion
</script>
<template>
<div class="app-container">欢迎 PR !!!</div>
<div class="app-container">
<!-- 表格 -->
<vxe-grid ref="xGridDom" v-bind="xGridOpt">
<!-- 左侧按钮列表 -->
<template #toolbar-btns>
<vxe-button status="primary" icon="vxe-icon-add" @click="crudStore.onShowModal()">新增用户</vxe-button>
<vxe-button status="danger" icon="vxe-icon-delete">批量删除</vxe-button>
</template>
<!-- 操作 -->
<template #row-operate="{ row }">
<el-button link type="primary" @click="crudStore.onShowModal(row)">编辑</el-button>
<el-button link type="danger" @click="crudStore.onDelete(row)">删除</el-button>
</template>
</vxe-grid>
<!-- 弹窗 -->
<vxe-modal ref="xModalDom" v-bind="xModalOpt">
<!-- 表单 -->
<vxe-form ref="xFormDom" v-bind="xFormOpt" />
</vxe-modal>
</div>
</template>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,11 @@
import { type VxeColumnPropTypes } from "vxe-table/types/column"
const solts: VxeColumnPropTypes.Slots = {
default: ({ row, column }) => {
const cellValue = row[column.field]
const type = cellValue === "admin" ? "" : "warning"
return [<span class={`el-tag el-tag--${type}`}>{cellValue}</span>]
}
}
export default solts

View File

@@ -0,0 +1,16 @@
import { type VxeColumnPropTypes } from "vxe-table/types/column"
const solts: VxeColumnPropTypes.Slots = {
default: ({ row, column }) => {
const cellValue = row[column.field]
let type = "danger"
let value = "禁用"
if (cellValue) {
type = "success"
value = "启用"
}
return [<span class={`el-tag el-tag--${type} el-tag--plain`}>{value}</span>]
}
}
export default solts