1
0
mirror of https://github.com/zclzone/vue-naive-admin.git synced 2026-01-22 23:50:22 +08:00

25 Commits

Author SHA1 Message Date
leip247
10ae8ace90 Merge 43506681e9 into 70c2098d2e 2025-03-21 03:29:42 +00:00
leipu
43506681e9 fix: withPermission完善 2025-03-21 11:29:28 +08:00
leipu
2ff0befdd4 feat: 添加PH函数,根据按钮权限判断当前按钮是否渲染 2025-03-19 10:38:04 +08:00
leipu
e74856040a feat: 添加PH函数,根据按钮权限判断当前按钮是否渲染 2025-03-19 10:35:50 +08:00
leipu
84fb1198d5 Merge remote-tracking branch 'upstream/2.x' into 2.x 2025-03-19 10:28:26 +08:00
zclzone
70c2098d2e refactor: 操作指引改成非自动触发 2025-03-18 10:14:20 +08:00
zclzone
82748abcc3 fix: unocss废弃api替换 2025-03-18 10:05:50 +08:00
zclzone
e5fd1405da style: lint fix 2025-03-13 10:29:17 +08:00
zclzone
833321d31d chore: update deps 2025-02-28 17:25:28 +08:00
zclzone
53afc9b531 chore: update deps 2025-02-06 16:03:45 +08:00
zclzone
0733b81f82 fix(crud): 浅拷贝 -> 深拷贝 2025-01-21 14:37:16 +08:00
zclzone
26ce0bd551 chore: update deps 2025-01-09 21:15:09 +08:00
zclzone
1201e8e172 chore: update deps 2024-12-27 18:31:08 +08:00
leipu
be2dedf4e4 fix: 当用户访问一个当前用户没有的权限时导航去404 2024-12-22 11:21:48 +08:00
leipu
9a22ac1659 fix: 当用户访问一个当前用户没有的权限时导航区404 2024-12-20 11:17:58 +08:00
zclzone
78d9bb99f2 chore: update deps and lint fix 2024-12-14 19:56:56 +08:00
zclzone
9b510e8350 fix: pinia持久化paths -> pick 2024-11-29 15:26:07 +08:00
zclzone
7ce6cf1f7c fix: 移除多余的class 2024-11-22 16:00:49 +08:00
zclzone
30ca69cf16 fix: remove scss style 2024-11-22 15:59:20 +08:00
zclzone
c34c3646aa chore: update settings.json 2024-11-22 15:49:08 +08:00
leip247
6563797afc feat(guide): 添加操作指引 (#106)
* fix(slot): 优化部分slot写法,减少手动判断

* fix(slot): 优化部分slot写法,减少手动判断

* feat(guide): 添加操作指引
2024-11-22 15:47:00 +08:00
leipu
efb742beb9 feat(guide): 添加操作指引 2024-11-22 12:33:53 +08:00
leipu
3c6dce3eeb Merge remote-tracking branch 'upstream/2.x' into 2.x 2024-11-18 15:10:43 +08:00
leipu
fa0219ef99 fix(slot): 优化部分slot写法,减少手动判断 2024-11-13 10:48:58 +08:00
leipu
2b36f535c7 fix(slot): 优化部分slot写法,减少手动判断 2024-11-13 10:25:42 +08:00
33 changed files with 2465 additions and 3025 deletions

10
.vscode/settings.json vendored
View File

@@ -19,13 +19,5 @@
"markdown",
"json",
"jsonc"
],
"common-intellisense.ui": [
"naiveUi2"
],
"workbench.colorCustomizations": {
"activityBar.background": "#233212",
"titleBar.activeBackground": "#314619",
"titleBar.activeForeground": "#F9FCF6"
}
]
}

View File

@@ -13,46 +13,43 @@
},
"dependencies": {
"@arco-design/color": "^0.4.0",
"@vueuse/core": "^11.1.0",
"axios": "^1.7.7",
"@vueuse/core": "^12.7.0",
"axios": "^1.8.1",
"dayjs": "^1.11.13",
"echarts": "^5.5.1",
"exceljs": "^4.4.0",
"echarts": "^5.6.0",
"lodash-es": "^4.17.21",
"naive-ui": "^2.40.1",
"pinia": "^2.2.4",
"pinia-plugin-persistedstate": "^4.1.1",
"vue": "^3.5.12",
"naive-ui": "^2.41.0",
"pinia": "^3.0.1",
"pinia-plugin-persistedstate": "^4.2.0",
"vue": "^3.5.13",
"vue-echarts": "^7.0.3",
"vue-router": "^4.4.5",
"vxe-pc-ui": "4.2.5",
"vxe-table": "4.8.13",
"vxe-table-plugin-export-xlsx": "^4.0.4",
"vue-router": "^4.5.0",
"xlsx": "^0.18.5"
},
"devDependencies": {
"@antfu/eslint-config": "^3.8.0",
"@iconify/json": "^2.2.262",
"@unocss/eslint-config": "^0.63.6",
"@unocss/eslint-plugin": "^0.63.6",
"@unocss/preset-rem-to-px": "^0.63.6",
"@vitejs/plugin-vue": "^5.1.4",
"@vitejs/plugin-vue-jsx": "^4.0.1",
"eslint": "^9.13.0",
"eslint-plugin-format": "^0.1.2",
"@antfu/eslint-config": "^4.3.0",
"@iconify/json": "^2.2.311",
"@unocss/eslint-config": "^66.0.0",
"@unocss/eslint-plugin": "^66.0.0",
"@unocss/preset-rem-to-px": "^66.0.0",
"@vitejs/plugin-vue": "^5.2.1",
"@vitejs/plugin-vue-jsx": "^4.1.1",
"eslint": "^9.21.0",
"eslint-plugin-format": "^1.0.1",
"esno": "^4.8.0",
"fs-extra": "^11.2.0",
"glob": "^11.0.0",
"lint-staged": "^15.2.10",
"rollup-plugin-visualizer": "^5.12.0",
"fs-extra": "^11.3.0",
"glob": "^11.0.1",
"lint-staged": "^15.4.3",
"rollup-plugin-visualizer": "^5.14.0",
"simple-git-hooks": "^2.11.1",
"taze": "^0.17.2",
"unocss": "^0.63.6",
"unplugin-auto-import": "^0.18.3",
"unplugin-vue-components": "^0.27.4",
"vite": "^5.4.9",
"taze": "^18.6.0",
"unocss": "^66.0.0",
"unplugin-auto-import": "^19.1.1",
"unplugin-vue-components": "^28.4.1",
"vite": "^6.2.0",
"vite-plugin-router-warn": "^1.0.0",
"vite-plugin-vue-devtools": "^7.5.2"
"vite-plugin-vue-devtools": "^7.7.2",
"vue3-intro-step": "^1.0.5"
},
"simple-git-hooks": {
"pre-commit": "pnpm lint-staged"

4797
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -10,7 +10,7 @@
<div>
<n-tooltip trigger="hover" placement="left">
<template #trigger>
<div class="f-c-c rounded-4 bg-primary p-8" @click="modalRef.open()">
<div id="layout-setting" class="f-c-c rounded-4 bg-primary p-8" @click="modalRef.open()">
<i class="i-fe:settings cursor-pointer bg-white text-20" />
</div>
</template>

View File

@@ -1,240 +0,0 @@
<template>
<div
:style="isComputedHeight ? `height: calc(100% - ${autoHeight}px);` : `height: ${autoHeight}px`"
>
<vxe-grid
ref="gridRef"
:columns="vxeColumns"
:loading="loading"
:data="data"
height="auto"
:stripe="true"
:border="true"
size="mini"
:auto-resize="true"
:pager-config="pagerConfig"
:export-config="exportConfig"
:toolbar-config="vxeToolbarConfig"
:proxy-config="proxyConfig"
v-on="gridEvent"
>
<template #loading>
<n-spin :show="loading" />
</template>
<template #total>
<span class="font-size-14 line-height-28px"> {{ pagerConfig.total }} 条数据</span>
</template>
<template v-for="(c, k) in slots" :key="k" #[c.key]="{ row }">
<template v-if="Array.isArray(c.render(row))">
<component :is="r" v-for="(r, k1) in c.render(row)" :key="k1" :row="row" :col="c" />
</template>
<template v-else>
<component :is="c.render(row)" :row="row" :col="c" />
</template>
</template>
</vxe-grid>
</div>
</template>
<script setup>
import { computed } from 'vue'
defineOptions({ name: 'MeVxeGrid' })
const props = defineProps({
/**
* @remote 当isComputedHeight为true时为除table外的高度 false时为table高度
*/
autoHeight: {
type: Number,
default: 300,
},
/**
* @isPagination 是否为自动计算高度
*/
isComputedHeight: {
type: Boolean,
default: false,
},
loading: {
type: Boolean,
default: false,
},
/**
* @remote table的列
*/
columns: {
type: Array,
default: () => [],
},
data: {
type: Array,
default: () => [],
},
pagination: {
type: [Boolean, Object],
default: true,
},
exportConfig: {
type: Object,
default: () => ({}),
},
toolbarConfig: {
type: Object,
default: () => ({ }),
},
/**
* 是否显示复选框
*/
showCheck: {
type: Boolean,
default: false,
},
rowKey: {
type: String, // rowKey
default: 'id',
},
exportMethod: {
type: Function,
default: () => null,
},
remoteMethod: {
type: Function,
default: () => null,
},
})
const emits = defineEmits(['update:checked-row-keys', 'pageChanged'])
const isFirst = shallowRef(true)
const gridRef = ref(null)
watch(
() => props.data,
(n) => {
isFirst.value = false
gridRef.value.reloadData(n)
},
)
const vxeToolbarConfig = computed(() => {
return {
size: 'mini',
className: 'c-toolbar',
perfect: true,
refresh: true,
export: true,
zoom: true,
custom: true,
...props.toolbarConfig,
}
})
const pagerConfig = computed(() => {
if (props.pagination === false) {
return {
enabled: false,
}
}
return {
currentPage: props.pagination.pageNo,
pageSize: props.pagination.pageSize,
total: props.pagination.itemCount || 0,
background: true,
size: 'small',
layouts: [
'Home',
'PrevPage',
'JumpNumber',
'NextPage',
'End',
'Sizes',
'FullJump',
// 'Total',
],
slots: {
right: 'total',
},
}
})
/**
* 将naive 转为vxe-table可用的列
*/
const vxeColumns = computed(() => {
if (props.showCheck) {
return [{
type: 'checkbox',
resizable: false,
width: 45,
align: 'center',
fixed: 'left',
}, ...props.columns.map((item) => {
item.field = item.key
if (item.render) {
item.slots = { default: item.key }
}
return item
})]
}
return props.columns.map((item) => {
item.field = item.key
if (item.render) {
item.slots = { default: item.key }
}
return item
})
})
/**
* 将naive render函数转换为vxeTable的slots
*/
const slots = computed(() => {
return props.columns.filter(item => item.render)
})
const gridEvent = {
checkboxAll() {
const $grid = gridRef.value
if ($grid) {
emits(
'update:checked-row-keys',
$grid.getCheckboxRecords().map(item => item[props.rowKey]),
)
}
},
checkboxChange() {
const $grid = gridRef.value
if ($grid) {
emits(
'update:checked-row-keys',
$grid.getCheckboxRecords().map(item => item[props.rowKey]),
)
}
},
pageChange(e) {
emits('pageChanged', e)
},
}
const proxyConfig = {
props: {
result: 'data.pageData',
total: 'data.total',
},
ajax: {
query: async ({ page }) => {
if (isFirst.value === true) {
return Promise.reject('')
}
props.remoteMethod(page.currentPage, page.pageSize)
},
queryAll: async () => {
const res = await props.exportMethod()
return res
},
},
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -2,6 +2,7 @@
<n-tooltip trigger="hover">
<template #trigger>
<n-color-picker
id="theme-setting"
class="mr-16 h-32 w-32"
:value="appStore.primaryColor"
:swatches="primaryColors"

View File

@@ -1,5 +1,6 @@
<template>
<i
id="toggleTheme"
class="mr-16 cursor-pointer"
:class="isDark ? 'i-fe:moon' : 'i-fe:sun'"
@click="toggleDark"

View File

@@ -2,6 +2,5 @@ export { default as AppCard } from './AppCard.vue'
export { default as AppPage } from './AppPage.vue'
export { default as CommonPage } from './CommonPage.vue'
export { default as LayoutSetting } from './LayoutSetting.vue'
export { default as MeVxeGrid } from './MeVxeGrid.vue'
export { default as TheFooter } from './TheFooter.vue'
export { default as ToggleTheme } from './ToggleTheme.vue'

View File

@@ -7,59 +7,38 @@
--------------------------------->
<template>
<div :class="computedHeight === true ? 'h-100%' : ''">
<div id="search_main">
<AppCard v-if="$slots.default" bordered bg="#fafafc dark:black" class="mb-30 min-h-60 rounded-4">
<form class="flex justify-between p-16" @submit.prevent="handleSearch()">
<n-scrollbar x-scrollable>
<n-space :wrap="!expand || isExpanded" :size="[32, 16]" class="p-10">
<slot />
</n-space>
</n-scrollbar>
<div class="flex-shrink-0 p-10">
<n-button ghost type="primary" @click="handleReset">
<i class="i-fe:rotate-ccw mr-4" />
重置
</n-button>
<n-button attr-type="submit" class="ml-20" type="primary">
<i class="i-fe:search mr-4" />
搜索
</n-button>
<AppCard v-if="$slots.default" bordered bg="#fafafc dark:black" class="mb-30 min-h-60 rounded-4">
<form class="flex justify-between p-16" @submit.prevent="handleSearch()">
<n-scrollbar x-scrollable>
<n-space :wrap="!expand || isExpanded" :size="[32, 16]" class="p-10">
<slot />
</n-space>
</n-scrollbar>
<div class="flex-shrink-0 p-10">
<n-button ghost type="primary" @click="handleReset">
<i class="i-fe:rotate-ccw mr-4" />
重置
</n-button>
<n-button attr-type="submit" class="ml-20" type="primary">
<i class="i-fe:search mr-4" />
搜索
</n-button>
<template v-if="expand">
<n-button v-if="!isExpanded" type="primary" text @click="toggleExpand">
<i class="i-fe:chevrons-down ml-4" />
展开
</n-button>
<n-button v-else text type="primary" @click="toggleExpand">
<i class="i-fe:chevrons-up ml-4" />
收起
</n-button>
</template>
</div>
</form>
</AppCard>
</div>
<MeVxeGrid
:remote="remote"
:loading="loading"
:scroll-x="scrollX"
:columns="columns"
:export-config="{ filename: exportName, modes: ['all', 'current'] }"
:toolbar-config="toolbarConfig"
:data="tableData"
:auto-height="computedHeight === true ? autoHeight : computedHeight"
:row-key="rowKey"
:is-computed-height="computedHeight === true"
:show-check="showCheck"
:pagination="isPagination ? pagination : false"
:remote-method="remoteMethod"
:export-method="exportMethod"
@update:checked-row-keys="onChecked"
@update:page="onPageChange"
/>
<template v-if="expand">
<n-button v-if="!isExpanded" type="primary" text @click="toggleExpand">
<i class="i-fe:chevrons-down ml-4" />
展开
</n-button>
<n-button v-else text type="primary" @click="toggleExpand">
<i class="i-fe:chevrons-up ml-4" />
收起
</n-button>
</template>
</div>
</form>
</AppCard>
<!-- <NDataTable
<NDataTable
:remote="remote"
:loading="loading"
:scroll-x="scrollX"
@@ -69,39 +48,14 @@
:pagination="isPagination ? pagination : false"
@update:checked-row-keys="onChecked"
@update:page="onPageChange"
/> -->
</div>
/>
</template>
<script setup>
import { NDataTable } from 'naive-ui'
import { utils, writeFile } from 'xlsx'
const props = defineProps({
/**
* 是否自动计算高度适用只有search和table
*/
computedHeight: {
type: [Boolean, Number],
default: () => true,
},
showCheck: {
type: Boolean,
default: () => false,
},
toolbarConfig: {
type: Object,
default: () => ({
export: true,
zoom: true,
}),
},
/**
* @remote 导出数据表格名称
*/
exportName: {
type: String,
default: '导出数据',
},
/**
* @remote true: 后端分页 false 前端分页
*/
@@ -151,18 +105,8 @@ const props = defineProps({
/** 是否支持展开 */
expand: Boolean,
})
const emit = defineEmits(['update:queryItems', 'onChecked', 'onDataChange'])
const autoHeight = ref(0)
window.onresize = () => {
autoHeight.value = document.getElementById('search_main').clientHeight + 24
}
onMounted(() => {
autoHeight.value = document.getElementById('search_main').clientHeight + 24
})
const loading = ref(false)
const initQuery = { ...props.queryItems }
const tableData = ref([])
@@ -181,22 +125,6 @@ function toggleExpand() {
isExpanded.value = !isExpanded.value
}
async function exportMethod() {
const { data } = await props.getData({
pageNo: 1,
pageSize: 2000,
...props.queryItems,
})
return data
}
function remoteMethod(page, pageSize) {
pagination.page = page
pagination.pageSize = pageSize
handleQuery()
}
async function handleQuery() {
try {
loading.value = true
@@ -215,7 +143,6 @@ async function handleQuery() {
// 如果当前页数据为空且总条数不为0则返回上一页数据
onPageChange(pagination.page - 1)
}
return data
}
catch (error) {
console.error(error)
@@ -253,7 +180,9 @@ function onPageChange(currentPage) {
}
}
function onChecked(rowKeys) {
emit('onChecked', rowKeys)
if (props.columns.some(item => item.type === 'selection')) {
emit('onChecked', rowKeys)
}
}
function handleExport(columns = props.columns, data = tableData.value) {
if (!data?.length)

View File

@@ -6,6 +6,7 @@
* Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
**********************************/
import { cloneDeep } from 'lodash-es'
import { useForm, useModal } from '.'
const ACTIONS = {
@@ -21,7 +22,7 @@ export function useCrud({ name, initForm = {}, doCreate, doDelete, doUpdate, ref
/** 新增 */
function handleAdd(row = {}, title) {
handleOpen({ action: 'add', title, row: { ...initForm, ...row } })
handleOpen({ action: 'add', title, row: Object.assign({}, cloneDeep(initForm), cloneDeep(row)) })
}
/** 修改 */

View File

@@ -6,9 +6,11 @@
* Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
**********************************/
import { cloneDeep } from 'lodash-es'
export function useForm(initFormData = {}) {
const formRef = ref(null)
const formModel = ref({ ...initFormData })
const formModel = ref(cloneDeep(initFormData))
const rules = {
required: {
required: true,

View File

@@ -8,7 +8,7 @@
import { router } from '@/router'
const permission = {
export const permission = {
mounted(el, binding) {
const currentRoute = unref(router.currentRoute)
const btns = currentRoute.meta?.btns?.map(item => item.code) || []

View File

@@ -0,0 +1,120 @@
<template>
<n-tooltip trigger="hover">
<template #trigger>
<i
class="i-fe:beginner mr-16 cursor-pointer text-20"
@click="show = true"
/>
</template>
操作指引
</n-tooltip>
<Vue3IntroStep
ref="myIntroStep"
v-model:show="show"
:config="config"
>
<template #prev="{ tipItem, index }">
<NButton class="mr-12" type="primary" color="#fff" text-color="#fff" ghost round size="small" @click="prev(tipItem, index)">
上一步
</NButton>
</template>
<template #next="{ tipItem }">
<NButton class="mr-12" type="primary" color="#fff" text-color="#fff" ghost round size="small" @click="next(tipItem)">
下一步
</NButton>
</template>
<template #skip>
<NButton type="primary" color="#fff" text-color="#fff" ghost round size="small" @click="skip">
跳过
</NButton>
</template>
<template #done>
<NButton type="primary" color="#fff" text-color="#fff" ghost round size="small" @click="done">
完成
</NButton>
</template>
</Vue3IntroStep>
</template>
<script setup>
import Vue3IntroStep from 'vue3-intro-step'
const myIntroStep = shallowRef(null)
const show = shallowRef(false)
const config = {
backgroundOpacity: 0.8,
titleStyle: {
textAlign: 'left',
fontSize: '18px',
},
contentStyle: {
textAlign: 'left',
fontSize: '14px',
},
tips: [
{
el: '#toggleTheme',
tipPosition: 'bottom',
title: '切换系统主题',
content: '一键开启护眼模式',
},
{
el: '#fullscreen',
tipPosition: 'bottom',
title: '全屏/退出全屏',
content: '一键开启全屏',
},
{
el: '#theme-setting',
tipPosition: 'bottom',
title: '设置主题色',
content: '调整为你喜欢的主题色',
},
{
el: '#user-dropdown',
tipPosition: 'bottom',
title: '个人中心',
content: '查看个人资料和退出系统',
},
{
el: '#menu-collapse',
tipPosition: 'bottom',
title: '展开/收起菜单',
content: '一键展开/收起菜单',
},
{
el: '#top-tab',
tipPosition: 'bottom',
title: '标签栏',
content: '鼠标滚轮滑动可调整至最佳视野',
},
{
el: '#layout-setting',
tipPosition: 'left',
title: '调整系统布局',
content: '将系统布局调整为你喜欢的样子',
},
],
}
function skip() {
show.value = false
}
function done() {
show.value = false
}
function next() {
// tipItem当前的提示项信息
// 调用vue3-intro-step的next方法 手动触发下一步
myIntroStep.value.next()
}
function prev() {
// 调用vue3-intro-step的prev方法 手动触发上一步
myIntroStep.value.prev()
}
</script>

View File

@@ -1,5 +1,6 @@
<template>
<i
id="fullscreen"
class="mr-16 cursor-pointer"
:class="isFullscreen ? 'i-fe:minimize' : 'i-fe:maximize'"
@click="toggle"

View File

@@ -8,6 +8,7 @@
<template>
<div
id="menu-collapse"
class="f-c-c cursor-pointer rounded-4 p-6 text-22 transition-all-300 auto-bg-hover"
@click="appStore.switchCollapsed"
>

View File

@@ -8,7 +8,7 @@
<template>
<n-dropdown :options="options" @select="handleSelect">
<div class="flex cursor-pointer items-center">
<div id="user-dropdown" class="flex cursor-pointer items-center">
<n-avatar round :size="36" :src="userStore.avatar" />
<div v-if="userStore.userInfo" class="ml-12 flex-col flex-shrink-0 items-center">
<span class="text-14">{{ userStore.nickName ?? userStore.username }}</span>

View File

@@ -1,3 +1,4 @@
export { default as BeginnerGuide } from './BeginnerGuide.vue'
export { default as BreadCrumb } from './BreadCrumb.vue'
export { default as Fullscreen } from './Fullscreen.vue'
export { default as MenuCollapse } from './MenuCollapse.vue'

View File

@@ -7,7 +7,7 @@
--------------------------------->
<template>
<div>
<div id="top-tab">
<n-tabs
:value="tabStore.activeTab"
:closable="tabStore.tabs.length > 1"

View File

@@ -13,6 +13,8 @@
<BreadCrumb />
<div class="ml-auto flex flex-shrink-0 items-center px-12 text-18">
<BeginnerGuide />
<ToggleTheme />
<Fullscreen />
@@ -35,7 +37,7 @@
<script setup>
import { ToggleTheme } from '@/components'
import { BreadCrumb, Fullscreen, MenuCollapse, UserAvatar } from '@/layouts/components'
import { BeginnerGuide, BreadCrumb, Fullscreen, MenuCollapse, UserAvatar } from '@/layouts/components'
function handleLinkClick(link) {
window.open(link)

View File

@@ -15,6 +15,8 @@
<span class="mx-6 opacity-20">|</span>
<div class="flex flex-shrink-0 items-center px-12 text-18">
<BeginnerGuide />
<ToggleTheme />
<Fullscreen />
@@ -37,7 +39,7 @@
<script setup>
import { ToggleTheme } from '@/components'
import { AppTab, Fullscreen, MenuCollapse, UserAvatar } from '@/layouts/components'
import { AppTab, BeginnerGuide, Fullscreen, MenuCollapse, UserAvatar } from '@/layouts/components'
function handleLinkClick(link) {
window.open(link)

View File

@@ -8,35 +8,19 @@
* Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
**********************************/
import ExcelJS from 'exceljs'
import { createApp } from 'vue'
import App from './App.vue'
import { setupDirectives } from './directives'
/**
* 引入vxe-table 和 vxe-pc-ui excel导出
*/
import VxeUI from 'vxe-pc-ui'
import VXETable from 'vxe-table'
import VXETablePluginExportXLSX from 'vxe-table-plugin-export-xlsx'
import { setupRouter } from './router'
import { setupStore } from './store'
import { setupNaiveDiscreteApi } from './utils'
import 'vxe-pc-ui/lib/style.css'
import 'vxe-table/lib/style.css'
import '@/styles/reset.css'
import '@/styles/global.css'
import 'uno.css'
async function bootstrap() {
const app = createApp(App)
VXETable.use(VXETablePluginExportXLSX, {
ExcelJS,
})
app.use(VxeUI).use(VXETable)
setupStore(app)
setupDirectives(app)
await setupRouter(app)

View File

@@ -43,6 +43,12 @@ export function createPermissionGuard(router) {
return { ...to, replace: true }
}
router.addRoute({
name: 'catchAll',
path: '/:catchAll(.*)',
redirect: '/404',
})
const routes = router.getRoutes()
if (routes.find(route => route.name === to.name))
return true

View File

@@ -10,7 +10,6 @@ import { defaultLayout, defaultPrimaryColor, naiveThemeOverrides } from '@/setti
import { generate, getRgbStr } from '@arco-design/color'
import { useDark } from '@vueuse/core'
import { defineStore } from 'pinia'
import { VxeUI } from 'vxe-pc-ui'
export const useAppStore = defineStore('app', {
state: () => ({
@@ -41,18 +40,6 @@ export const useAppStore = defineStore('app', {
list: true,
dark: isDark,
})
VxeUI.setTheme(isDark ? 'dark' : 'light')
if (isDark) {
document.body.style.setProperty('--vxe-ui-font-color', '#fff')
}
else {
document.body.style.setProperty('--vxe-ui-font-color', '#000')
}
document.body.style.setProperty('--vxe-ui-font-primary-color', color)
document.body.style.setProperty('--vxe-ui-font-primary-lighten-color', color)
document.body.style.setProperty('--vxe-ui-font-primary-darken-color', color)
document.body.style.setProperty('--primary-color', getRgbStr(colors[5]))
this.naiveThemeOverrides.common = Object.assign(this.naiveThemeOverrides.common || {}, {
primaryColor: colors[5],
@@ -63,7 +50,7 @@ export const useAppStore = defineStore('app', {
},
},
persist: {
paths: ['collapsed', 'layout', 'primaryColor', 'naiveThemeOverrides'],
pick: ['collapsed', 'layout', 'primaryColor', 'naiveThemeOverrides'],
storage: sessionStorage,
},
})

View File

@@ -16,6 +16,8 @@ export const useRouterStore = defineStore('router', () => {
accessRoutes.forEach((item) => {
router.hasRoute(item.name) && router.removeRoute(item.name)
})
router.removeRoute('catchAll')
}
return {

View File

@@ -88,7 +88,7 @@ export const useTabStore = defineStore('tab', {
},
},
persist: {
paths: ['tabs'],
pick: ['tabs'],
storage: sessionStorage,
},
})

View File

@@ -11,4 +11,5 @@ export * from './common'
export * from './http'
export * from './is'
export * from './naiveTools'
export * from './permissionH'
export * from './storage'

9
src/utils/permissionH.js Normal file
View File

@@ -0,0 +1,9 @@
import { permission } from '@/directives'
/**
* @description: 按钮权限渲染
*/
import { withDirectives } from 'vue'
export function withPermission(vnode, code) {
return withDirectives(vnode, [[permission, code]])
}

View File

@@ -11,14 +11,14 @@ import { createStorage } from './storage'
const prefixKey = 'vue-naive-admin_'
export const createLocalStorage = function (option = {}) {
export function createLocalStorage(option = {}) {
return createStorage({
prefixKey: option.prefixKey || '',
storage: localStorage,
})
}
export const createSessionStorage = function (option = {}) {
export function createSessionStorage(option = {}) {
return createStorage({
prefixKey: option.prefixKey || '',
storage: sessionStorage,

View File

@@ -98,7 +98,7 @@
<script setup>
import { sleep } from '@/utils'
const handleDelete = function () {
function handleDelete() {
$dialog.confirm({
content: '确认删除?',
confirm() {

View File

@@ -81,7 +81,6 @@
:columns="btnsColumns"
:scroll-x="-1"
:get-data="api.getButtons"
:computed-height="300"
:query-items="{ parentId: currentMenu.id }"
/>
</template>

View File

@@ -37,7 +37,6 @@
:scroll-x="1200"
:columns="columns"
:get-data="api.getAllUsers"
:show-check="true"
@on-checked="onChecked"
>
<MeQueryItem label="用户名" :label-width="50">
@@ -91,7 +90,7 @@ const genders = [
]
const columns = [
// { type: 'selection', fixed: 'left' },
{ type: 'selection', fixed: 'left' },
{
title: '头像',
key: 'avatar',
@@ -102,10 +101,11 @@ const columns = [
src: avatar,
}),
},
{ title: '用户名', key: 'username', ellipsis: { tooltip: true } },
{ title: '用户名', key: 'username', width: 150, ellipsis: { tooltip: true } },
{
title: '角色',
key: 'roles',
width: 200,
ellipsis: { tooltip: true },
render: ({ roles }) => {
if (roles?.length) {
@@ -156,7 +156,7 @@ const columns = [
{
title: '操作',
key: 'actions',
width: 120,
width: 100,
align: 'right',
fixed: 'right',
hideInExcel: true,

View File

@@ -112,7 +112,7 @@
<script setup>
import { MeCrud, MeModal, MeQueryItem } from '@/components'
import { useCrud } from '@/composables'
import { formatDateTime } from '@/utils'
import { formatDateTime, withPermission } from '@/utils'
import { NAvatar, NButton, NSwitch, NTag } from 'naive-ui'
import api from './api'
@@ -162,10 +162,11 @@ const columns = [
src: avatar,
}),
},
{ title: '用户名', key: 'username', ellipsis: { tooltip: true } },
{ title: '用户名', key: 'username', width: 150, ellipsis: { tooltip: true } },
{
title: '角色',
key: 'roles',
width: 200,
ellipsis: { tooltip: true },
render: ({ roles }) => {
if (roles?.length) {
@@ -218,17 +219,30 @@ const columns = [
{
title: '操作',
key: 'actions',
width: 320,
width: 380,
align: 'right',
fixed: 'right',
hideInExcel: true,
render(row) {
return [
withPermission(
h(NButton, {
size: 'small',
type: 'primary',
secondary: true,
onClick: () => handleOpenRolesSet(row),
}, {
default: () => '超管专属',
icon: () => h('i', { class: 'i-carbon:user-role text-14' }),
}),
'superAdmin',
),
h(
NButton,
{
size: 'small',
type: 'primary',
class: 'ml-12px',
secondary: true,
onClick: () => handleOpenRolesSet(row),
},

View File

@@ -8,13 +8,13 @@
import { FileSystemIconLoader } from '@iconify/utils/lib/loader/node-loaders'
import presetRemToPx from '@unocss/preset-rem-to-px'
import { defineConfig, presetAttributify, presetIcons, presetUno } from 'unocss'
import { defineConfig, presetAttributify, presetIcons, presetWind3 } from 'unocss'
import { getIcons } from './build/index.js'
const icons = getIcons()
export default defineConfig({
presets: [
presetUno(),
presetWind3(),
presetAttributify(),
presetIcons({
warn: true,