generated from wzp/v3-admin-vite
feat: adding router
This commit is contained in:
parent
7760d510e8
commit
71384106a7
@ -1,3 +1,4 @@
|
||||
|
||||
<div align="center">
|
||||
<img alt="V3 Admin Vite Logo" width="120" height="120" src="./src/assets/layout/logo.png">
|
||||
<h1>V3 Admin Vite</h1>
|
||||
|
1796
pnpm-lock.yaml
generated
1796
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
13
src/App.vue
13
src/App.vue
@ -1,7 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import { h } from "vue"
|
||||
import { useTheme } from "@/hooks/useTheme"
|
||||
import { ElNotification } from "element-plus"
|
||||
import zhCn from "element-plus/lib/locale/lang/zh-cn"
|
||||
|
||||
const { initTheme } = useTheme()
|
||||
@ -10,17 +8,6 @@ const { initTheme } = useTheme()
|
||||
initTheme()
|
||||
/** 将 Element Plus 的语言设置为中文 */
|
||||
const locale = zhCn
|
||||
|
||||
ElNotification({
|
||||
title: "Hello",
|
||||
message: h(
|
||||
"a",
|
||||
{ style: "color: teal", target: "_blank", href: "https://github.com/un-pany/v3-admin-vite" },
|
||||
"小项目获取 star 不易,如果你喜欢这个项目的话,欢迎点击这里支持一个 star !这是作者持续维护的唯一动力(小声:毕竟是免费的)"
|
||||
),
|
||||
duration: 0,
|
||||
position: "bottom-right"
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -21,11 +21,11 @@ interface ILayoutSettings {
|
||||
}
|
||||
|
||||
const layoutSettings: ILayoutSettings = {
|
||||
showSettings: true,
|
||||
showSettings: false,
|
||||
showTagsView: true,
|
||||
fixedHeader: true,
|
||||
showSidebarLogo: true,
|
||||
showNotify: true,
|
||||
showNotify: false,
|
||||
showThemeSwitch: true,
|
||||
showScreenfull: true,
|
||||
showGreyMode: false,
|
||||
|
@ -1,11 +0,0 @@
|
||||
<svg width="220" height="220" viewBox="0 0 220 220" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M117.722 167.444C117.722 139.83 140.108 117.444 167.722 117.444V117.444C195.336 117.444 217.722 139.83 217.722 167.444V167.444C217.722 195.058 195.336 217.444 167.722 217.444V217.444C140.108 217.444 117.722 195.058 117.722 167.444V167.444Z"
|
||||
fill="#fff" fill-opacity="0.6" />
|
||||
<path
|
||||
d="M117.722 52.5561C117.722 24.9419 140.108 2.55614 167.722 2.55614V2.55614C195.336 2.55614 217.722 24.9419 217.722 52.5561V97.5561C217.722 100.318 215.483 102.556 212.722 102.556H122.722C119.961 102.556 117.722 100.318 117.722 97.5561V52.5561Z"
|
||||
fill="#fff" fill-opacity="0.3" />
|
||||
<path
|
||||
d="M102.278 167.444C102.278 195.058 79.8922 217.444 52.278 217.444V217.444C24.6637 217.444 2.27796 195.058 2.27796 167.444L2.27796 122.444C2.27796 119.682 4.51654 117.444 7.27796 117.444L97.278 117.444C100.039 117.444 102.278 119.682 102.278 122.444L102.278 167.444Z"
|
||||
fill="#fff" />
|
||||
</svg>
|
Before Width: | Height: | Size: 996 B |
@ -57,165 +57,35 @@ export const constantRoutes: RouteRecordRaw[] = [
|
||||
]
|
||||
},
|
||||
{
|
||||
path: "/unocss",
|
||||
path: "/order",
|
||||
component: Layout,
|
||||
redirect: "/unocss/index",
|
||||
children: [
|
||||
{
|
||||
path: "index",
|
||||
component: () => import("@/views/unocss/index.vue"),
|
||||
name: "UnoCSS",
|
||||
meta: {
|
||||
title: "unocss",
|
||||
svgIcon: "unocss"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: "/link",
|
||||
component: Layout,
|
||||
children: [
|
||||
{
|
||||
path: "https://juejin.cn/post/7089377403717287972",
|
||||
component: () => {},
|
||||
name: "Link",
|
||||
meta: {
|
||||
title: "外链",
|
||||
svgIcon: "link"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: "/table",
|
||||
component: Layout,
|
||||
redirect: "/table/element-plus",
|
||||
name: "Table",
|
||||
redirect: "/order/",
|
||||
name: "Order",
|
||||
meta: {
|
||||
title: "表格",
|
||||
elIcon: "Grid"
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "element-plus",
|
||||
component: () => import("@/views/table/element-plus/index.vue"),
|
||||
name: "ElementPlus",
|
||||
meta: {
|
||||
title: "Element Plus",
|
||||
keepAlive: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "vxe-table",
|
||||
component: () => import("@/views/table/vxe-table/index.vue"),
|
||||
name: "VxeTable",
|
||||
meta: {
|
||||
title: "Vxe Table",
|
||||
keepAlive: true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: "/menu",
|
||||
component: Layout,
|
||||
redirect: "/menu/menu1",
|
||||
name: "Menu",
|
||||
meta: {
|
||||
title: "多级菜单",
|
||||
svgIcon: "menu"
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "menu1",
|
||||
component: () => import("@/views/menu/menu1/index.vue"),
|
||||
redirect: "/menu/menu1/menu1-1",
|
||||
name: "Menu1",
|
||||
meta: {
|
||||
title: "menu1"
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "menu1-1",
|
||||
component: () => import("@/views/menu/menu1/menu1-1/index.vue"),
|
||||
name: "Menu1-1",
|
||||
meta: {
|
||||
title: "menu1-1"
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "menu1-2",
|
||||
component: () => import("@/views/menu/menu1/menu1-2/index.vue"),
|
||||
redirect: "/menu/menu1/menu1-2/menu1-2-1",
|
||||
name: "Menu1-2",
|
||||
meta: {
|
||||
title: "menu1-2"
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "menu1-2-1",
|
||||
component: () => import("@/views/menu/menu1/menu1-2/menu1-2-1/index.vue"),
|
||||
name: "Menu1-2-1",
|
||||
meta: {
|
||||
title: "menu1-2-1"
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "menu1-2-2",
|
||||
component: () => import("@/views/menu/menu1/menu1-2/menu1-2-2/index.vue"),
|
||||
name: "Menu1-2-2",
|
||||
meta: {
|
||||
title: "menu1-2-2"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: "menu1-3",
|
||||
component: () => import("@/views/menu/menu1/menu1-3/index.vue"),
|
||||
name: "Menu1-3",
|
||||
meta: {
|
||||
title: "menu1-3"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: "menu2",
|
||||
component: () => import("@/views/menu/menu2/index.vue"),
|
||||
name: "Menu2",
|
||||
meta: {
|
||||
title: "menu2"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: "/hook-demo",
|
||||
component: Layout,
|
||||
redirect: "/hook-demo/use-fetch-select",
|
||||
name: "HookDemo",
|
||||
meta: {
|
||||
title: "hook 示例",
|
||||
elIcon: "Menu",
|
||||
title: "订单",
|
||||
elIcon: "Files",
|
||||
roles: ["admin", "editor"],
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "use-fetch-select",
|
||||
component: () => import("@/views/hook-demo/use-fetch-select.vue"),
|
||||
name: "UseFetchSelect",
|
||||
path: "create",
|
||||
component: () => import("@/views/order/create/index.vue"),
|
||||
name: "CreateOrder",
|
||||
meta: {
|
||||
title: "useFetchSelect"
|
||||
title: "创建订单",
|
||||
roles: ["editor"],
|
||||
elIcon: "FolderAdd"
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "use-fullscreen-loading",
|
||||
component: () => import("@/views/hook-demo/use-fullscreen-loading.vue"),
|
||||
name: "UseFullscreenLoading",
|
||||
path: "manager",
|
||||
component: () => import("@/views/order/manager/index.vue"),
|
||||
name: "ManagerOrder",
|
||||
meta: {
|
||||
title: "useFullscreenLoading"
|
||||
title: "管理订单",
|
||||
roles: ["admin"],
|
||||
elIcon: "FolderChecked"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -1,20 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { useFetchSelect } from "@/hooks/useFetchSelect"
|
||||
import { getSelectDataApi } from "@/api/hook-demo/use-fetch-select"
|
||||
|
||||
const { loading, options, value } = useFetchSelect({
|
||||
api: getSelectDataApi
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<h4>该示例是演示:通过 hook 自动调用 api 后拿到 Select 组件需要的数据并传递给 Select 组件</h4>
|
||||
<h5>Select 示例</h5>
|
||||
<el-select :loading="loading" v-model="value" filterable>
|
||||
<el-option v-for="(item, index) in options" v-bind="item" :key="index" placeholder="请选择" />
|
||||
</el-select>
|
||||
<h5>Select V2 示例(如果数据量过多,可以选择该组件)</h5>
|
||||
<el-select-v2 :loading="loading" v-model="value" :options="options" filterable placeholder="请选择" />
|
||||
</div>
|
||||
</template>
|
@ -1,44 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { useFullscreenLoading } from "@/hooks/useFullscreenLoading"
|
||||
import { getSuccessApi, getErrorApi } from "@/api/hook-demo/use-fullscreen-loading"
|
||||
import { ElMessage } from "element-plus"
|
||||
|
||||
const svg = `
|
||||
<path class="path" d="
|
||||
M 30 15
|
||||
L 28 17
|
||||
M 25.61 25.61
|
||||
A 15 15, 0, 0, 1, 15 30
|
||||
A 15 15, 0, 1, 1, 27.99 7.5
|
||||
L 15 15
|
||||
" style="stroke-width: 4px; fill: rgba(0, 0, 0, 0)"/>
|
||||
`
|
||||
|
||||
const options = {
|
||||
text: "即将发生错误...",
|
||||
background: "#F56C6C20",
|
||||
svg,
|
||||
svgViewBox: "-10, -10, 50, 50"
|
||||
}
|
||||
|
||||
const querySuccess = async () => {
|
||||
const res = await useFullscreenLoading(getSuccessApi)()
|
||||
ElMessage.success(res.message)
|
||||
}
|
||||
|
||||
const queryError = async () => {
|
||||
try {
|
||||
await useFullscreenLoading(getErrorApi, options)()
|
||||
} catch (err: any) {
|
||||
ElMessage.error(err.message)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<h4>该示例是演示:通过将要执行的函数传递给 hook,让 hook 自动开启全屏 loading,函数执行结束后自动关闭 loading</h4>
|
||||
<el-button @click="querySuccess">查询成功</el-button>
|
||||
<el-button @click="queryError">查询失败</el-button>
|
||||
</div>
|
||||
</template>
|
@ -1,7 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-card header="menu 1">
|
||||
<router-view />
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
@ -1,5 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-card> menu 1-1 </el-card>
|
||||
</div>
|
||||
</template>
|
@ -1,7 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-card header="menu 1-2">
|
||||
<router-view />
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
@ -1,5 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-card> menu 1-2-1 </el-card>
|
||||
</div>
|
||||
</template>
|
@ -1,5 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-card> menu 1-2-2 </el-card>
|
||||
</div>
|
||||
</template>
|
@ -1,5 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-card> menu 1-3 </el-card>
|
||||
</div>
|
||||
</template>
|
@ -1,5 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-card> menu 2 </el-card>
|
||||
</div>
|
||||
</template>
|
7
src/views/order/create/index.vue
Normal file
7
src/views/order/create/index.vue
Normal file
@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<div>OrderCreate</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup></script>
|
||||
|
||||
<style scoped></style>
|
5
src/views/order/manager/index.vue
Normal file
5
src/views/order/manager/index.vue
Normal file
@ -0,0 +1,5 @@
|
||||
<template><div>OrderManger</div></template>
|
||||
|
||||
<script lang="ts" setup></script>
|
||||
|
||||
<style scoped></style>
|
@ -1,12 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { useRoute, useRouter } from "vue-router"
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
router.replace({ path: "/" + route.params.path, query: route.query })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div />
|
||||
</template>
|
@ -1,249 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, watch } from "vue"
|
||||
import { createTableDataApi, deleteTableDataApi, updateTableDataApi, getTableDataApi } from "@/api/table"
|
||||
import { type IGetTableData } from "@/api/table/types/table"
|
||||
import { type FormInstance, type FormRules, ElMessage, ElMessageBox } from "element-plus"
|
||||
import { Search, Refresh, CirclePlus, Delete, Download, RefreshRight } from "@element-plus/icons-vue"
|
||||
import { usePagination } from "@/hooks/usePagination"
|
||||
|
||||
defineOptions({
|
||||
name: "ElementPlus"
|
||||
})
|
||||
|
||||
const loading = ref<boolean>(false)
|
||||
const { paginationData, handleCurrentChange, handleSizeChange } = usePagination()
|
||||
|
||||
//#region 增
|
||||
const dialogVisible = ref<boolean>(false)
|
||||
const formRef = ref<FormInstance | null>(null)
|
||||
const formData = reactive({
|
||||
username: "",
|
||||
password: ""
|
||||
})
|
||||
const formRules: FormRules = reactive({
|
||||
username: [{ required: true, trigger: "blur", message: "请输入用户名" }],
|
||||
password: [{ required: true, trigger: "blur", message: "请输入密码" }]
|
||||
})
|
||||
const handleCreate = () => {
|
||||
formRef.value?.validate((valid: boolean) => {
|
||||
if (valid) {
|
||||
if (currentUpdateId.value === undefined) {
|
||||
createTableDataApi({
|
||||
username: formData.username,
|
||||
password: formData.password
|
||||
}).then(() => {
|
||||
ElMessage.success("新增成功")
|
||||
dialogVisible.value = false
|
||||
getTableData()
|
||||
})
|
||||
} else {
|
||||
updateTableDataApi({
|
||||
id: currentUpdateId.value,
|
||||
username: formData.username
|
||||
}).then(() => {
|
||||
ElMessage.success("修改成功")
|
||||
dialogVisible.value = false
|
||||
getTableData()
|
||||
})
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
}
|
||||
const resetForm = () => {
|
||||
currentUpdateId.value = undefined
|
||||
formData.username = ""
|
||||
formData.password = ""
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region 删
|
||||
const handleDelete = (row: IGetTableData) => {
|
||||
ElMessageBox.confirm(`正在删除用户:${row.username},确认删除?`, "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
deleteTableDataApi(row.id).then(() => {
|
||||
ElMessage.success("删除成功")
|
||||
getTableData()
|
||||
})
|
||||
})
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region 改
|
||||
const currentUpdateId = ref<undefined | string>(undefined)
|
||||
const handleUpdate = (row: IGetTableData) => {
|
||||
currentUpdateId.value = row.id
|
||||
formData.username = row.username
|
||||
dialogVisible.value = true
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region 查
|
||||
const tableData = ref<IGetTableData[]>([])
|
||||
const searchFormRef = ref<FormInstance | null>(null)
|
||||
const searchData = reactive({
|
||||
username: "",
|
||||
phone: ""
|
||||
})
|
||||
const getTableData = () => {
|
||||
loading.value = true
|
||||
getTableDataApi({
|
||||
currentPage: paginationData.currentPage,
|
||||
size: paginationData.pageSize,
|
||||
username: searchData.username || undefined,
|
||||
phone: searchData.phone || undefined
|
||||
})
|
||||
.then((res) => {
|
||||
paginationData.total = res.data.total
|
||||
tableData.value = res.data.list
|
||||
})
|
||||
.catch(() => {
|
||||
tableData.value = []
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
const handleSearch = () => {
|
||||
if (paginationData.currentPage === 1) {
|
||||
getTableData()
|
||||
}
|
||||
paginationData.currentPage = 1
|
||||
}
|
||||
const resetSearch = () => {
|
||||
searchFormRef.value?.resetFields()
|
||||
if (paginationData.currentPage === 1) {
|
||||
getTableData()
|
||||
}
|
||||
paginationData.currentPage = 1
|
||||
}
|
||||
const handleRefresh = () => {
|
||||
getTableData()
|
||||
}
|
||||
//#endregion
|
||||
|
||||
/** 监听分页参数的变化 */
|
||||
watch([() => paginationData.currentPage, () => paginationData.pageSize], getTableData, { immediate: true })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-card v-loading="loading" shadow="never" class="search-wrapper">
|
||||
<el-form ref="searchFormRef" :inline="true" :model="searchData">
|
||||
<el-form-item prop="username" label="用户名">
|
||||
<el-input v-model="searchData.username" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item prop="phone" label="手机号">
|
||||
<el-input v-model="searchData.phone" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :icon="Search" @click="handleSearch">查询</el-button>
|
||||
<el-button :icon="Refresh" @click="resetSearch">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<el-card v-loading="loading" shadow="never">
|
||||
<div class="toolbar-wrapper">
|
||||
<div>
|
||||
<el-button type="primary" :icon="CirclePlus" @click="dialogVisible = true">新增用户</el-button>
|
||||
<el-button type="danger" :icon="Delete">批量删除</el-button>
|
||||
</div>
|
||||
<div>
|
||||
<el-tooltip content="下载">
|
||||
<el-button type="primary" :icon="Download" circle />
|
||||
</el-tooltip>
|
||||
<el-tooltip content="刷新表格">
|
||||
<el-button type="primary" :icon="RefreshRight" circle @click="handleRefresh" />
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-wrapper">
|
||||
<el-table :data="tableData">
|
||||
<el-table-column type="selection" width="50" align="center" />
|
||||
<el-table-column prop="username" label="用户名" align="center" />
|
||||
<el-table-column prop="roles" label="角色" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.roles === 'admin'" effect="plain">admin</el-tag>
|
||||
<el-tag v-else type="warning" effect="plain">{{ scope.row.roles }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="phone" label="手机号" align="center" />
|
||||
<el-table-column prop="email" label="邮箱" align="center" />
|
||||
<el-table-column prop="status" label="状态" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.status" type="success" effect="plain">启用</el-tag>
|
||||
<el-tag v-else type="danger" effect="plain">禁用</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" align="center" />
|
||||
<el-table-column fixed="right" label="操作" width="150" align="center">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" text bg size="small" @click="handleUpdate(scope.row)">修改</el-button>
|
||||
<el-button type="danger" text bg size="small" @click="handleDelete(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<div class="pager-wrapper">
|
||||
<el-pagination
|
||||
background
|
||||
:layout="paginationData.layout"
|
||||
:page-sizes="paginationData.pageSizes"
|
||||
:total="paginationData.total"
|
||||
:page-size="paginationData.pageSize"
|
||||
:currentPage="paginationData.currentPage"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
<!-- 新增/修改 -->
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="currentUpdateId === undefined ? '新增用户' : '修改用户'"
|
||||
@close="resetForm"
|
||||
width="30%"
|
||||
>
|
||||
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px" label-position="left">
|
||||
<el-form-item prop="username" label="用户名">
|
||||
<el-input v-model="formData.username" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item prop="password" label="密码" v-if="currentUpdateId === undefined">
|
||||
<el-input v-model="formData.password" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleCreate">确认</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.search-wrapper {
|
||||
margin-bottom: 20px;
|
||||
:deep(.el-card__body) {
|
||||
padding-bottom: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.toolbar-wrapper {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.table-wrapper {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.pager-wrapper {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
@ -1,388 +0,0 @@
|
||||
<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 { type GetTableResponseData } from "@/api/table/types/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"
|
||||
|
||||
defineOptions({
|
||||
name: "VxeTable"
|
||||
})
|
||||
|
||||
//#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: GetTableResponseData) => {
|
||||
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">
|
||||
<!-- 表格 -->
|
||||
<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>
|
@ -1,11 +0,0 @@
|
||||
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} el-tag--plain`}>{cellValue}</span>]
|
||||
}
|
||||
}
|
||||
|
||||
export default solts
|
@ -1,16 +0,0 @@
|
||||
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
|
@ -1,16 +0,0 @@
|
||||
<template>
|
||||
<div h-full uno-padding-20>
|
||||
<div h-full text-center flex select-none all:transition-400>
|
||||
<div ma>
|
||||
<div text-5xl fw100 animate-bounce-alt animate-count-infinite animate-1s>UnoCSS</div>
|
||||
<div op30 dark:op60 text-lg fw300 m1>具有高性能且极具灵活性的即时原子化 CSS 引擎</div>
|
||||
<div m2 flex justify-center text-lg op30 dark:op60 hover="op80" dark:hover="op80">
|
||||
<a href="https://antfu.me/posts/reimagine-atomic-css-zh" target="_blank">推荐阅读:重新构想原子化 CSS</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div absolute bottom-5 right-0 left-0 text-center op30 dark:op60 fw300>
|
||||
该页面是一个 UnoCSS 的使用案例,其他页面依旧采用 Scss
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
Loading…
x
Reference in New Issue
Block a user