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">
|
<div align="center">
|
||||||
<img alt="V3 Admin Vite Logo" width="120" height="120" src="./src/assets/layout/logo.png">
|
<img alt="V3 Admin Vite Logo" width="120" height="120" src="./src/assets/layout/logo.png">
|
||||||
<h1>V3 Admin Vite</h1>
|
<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>
|
<script lang="ts" setup>
|
||||||
import { h } from "vue"
|
|
||||||
import { useTheme } from "@/hooks/useTheme"
|
import { useTheme } from "@/hooks/useTheme"
|
||||||
import { ElNotification } from "element-plus"
|
|
||||||
import zhCn from "element-plus/lib/locale/lang/zh-cn"
|
import zhCn from "element-plus/lib/locale/lang/zh-cn"
|
||||||
|
|
||||||
const { initTheme } = useTheme()
|
const { initTheme } = useTheme()
|
||||||
@ -10,17 +8,6 @@ const { initTheme } = useTheme()
|
|||||||
initTheme()
|
initTheme()
|
||||||
/** 将 Element Plus 的语言设置为中文 */
|
/** 将 Element Plus 的语言设置为中文 */
|
||||||
const locale = zhCn
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -21,11 +21,11 @@ interface ILayoutSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const layoutSettings: ILayoutSettings = {
|
const layoutSettings: ILayoutSettings = {
|
||||||
showSettings: true,
|
showSettings: false,
|
||||||
showTagsView: true,
|
showTagsView: true,
|
||||||
fixedHeader: true,
|
fixedHeader: true,
|
||||||
showSidebarLogo: true,
|
showSidebarLogo: true,
|
||||||
showNotify: true,
|
showNotify: false,
|
||||||
showThemeSwitch: true,
|
showThemeSwitch: true,
|
||||||
showScreenfull: true,
|
showScreenfull: true,
|
||||||
showGreyMode: false,
|
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,
|
component: Layout,
|
||||||
redirect: "/unocss/index",
|
redirect: "/order/",
|
||||||
children: [
|
name: "Order",
|
||||||
{
|
|
||||||
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",
|
|
||||||
meta: {
|
meta: {
|
||||||
title: "表格",
|
title: "订单",
|
||||||
elIcon: "Grid"
|
elIcon: "Files",
|
||||||
},
|
roles: ["admin", "editor"],
|
||||||
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",
|
|
||||||
alwaysShow: true
|
alwaysShow: true
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: "use-fetch-select",
|
path: "create",
|
||||||
component: () => import("@/views/hook-demo/use-fetch-select.vue"),
|
component: () => import("@/views/order/create/index.vue"),
|
||||||
name: "UseFetchSelect",
|
name: "CreateOrder",
|
||||||
meta: {
|
meta: {
|
||||||
title: "useFetchSelect"
|
title: "创建订单",
|
||||||
|
roles: ["editor"],
|
||||||
|
elIcon: "FolderAdd"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "use-fullscreen-loading",
|
path: "manager",
|
||||||
component: () => import("@/views/hook-demo/use-fullscreen-loading.vue"),
|
component: () => import("@/views/order/manager/index.vue"),
|
||||||
name: "UseFullscreenLoading",
|
name: "ManagerOrder",
|
||||||
meta: {
|
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