feat: 新增 Vxe-Table 示例 (#22)
* feat: 新增 Vxe-Table 示例 * fix: table api type
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
66
src/plugins/vxe-table/index.ts
Normal file
66
src/plugins/vxe-table/index.ts
Normal 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)
|
||||
}
|
||||
@@ -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
34
src/styles/vxe-table.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
11
src/views/table/vxe-table/tsx/RoleColumnSolts.tsx
Normal file
11
src/views/table/vxe-table/tsx/RoleColumnSolts.tsx
Normal 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
|
||||
16
src/views/table/vxe-table/tsx/StatusColumnSolts.tsx
Normal file
16
src/views/table/vxe-table/tsx/StatusColumnSolts.tsx
Normal 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
|
||||
Reference in New Issue
Block a user