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

62 Commits

Author SHA1 Message Date
zclzone
22693b02ee chore: update deps 2026-01-20 18:28:41 +08:00
zclzone
cba38db3c9 docs: update README 2025-12-30 10:06:03 +08:00
zclzone
2e03a4b1a6 chore: update deps 2025-12-23 11:43:59 +08:00
zclzone
a5a20a30c9 perf: 添加optimizeDeps优化项 2025-12-01 17:40:41 +08:00
zclzone
9a7170f10c chore: update deps and lint fix 2025-12-01 17:38:59 +08:00
zclzone
fb4d18cc0a Merge branch '2.x' of https://gitcode.com/zclzone/vue-naive-admin into 2.x 2025-11-05 14:43:20 +08:00
zclzone
d078bba63e docs: update README 2025-11-05 14:39:45 +08:00
zclzone
5663f58b6e !1 merge 2.x into 2.x
refactor: 重构认证过期处理逻辑,消除代码重复

Created-by: u013737132
Commit-by: Ray.Hao
Merged-by: zclzone
Description: refactor: 重构认证过期处理逻辑,消除代码重复

See merge request: zclzone/vue-naive-admin!1
2025-11-05 14:28:22 +08:00
Ray.Hao
32700aee17 refactor: 重构认证过期处理逻辑,消除代码重复 2025-11-03 10:28:55 +08:00
zclzone
e593ba3ca5 docs: update README 2025-10-20 16:37:43 +08:00
zclzone
d9de814074 docs: add gitcode badge 2025-10-20 16:21:17 +08:00
zclzone
5457c4e70b fix: 修复切换主题闪屏问题 2025-10-14 14:06:02 +08:00
zclzone
6757e47fb1 fix: 首页饼图样式 2025-10-10 17:45:50 +08:00
zclzone
3fbcfdf0e1 fix: 删除失效图片地址 2025-10-10 17:12:28 +08:00
zclzone
d6764158f3 fix: 更新apifox云端mock地址 2025-10-10 17:09:39 +08:00
zclzone
4785842629 chore: update deps and lint fix 2025-08-26 11:30:11 +08:00
zclzone
5b4b0bd9ad fix: lint fix 2025-08-04 09:39:13 +08:00
goofy
3c04bbce8e fix: 修复上级菜单隐藏,下级菜单不生成路由问题 (#130)
fix: 修复上级菜单隐藏,下级菜单不生成路由问题
2025-08-04 09:36:56 +08:00
zclzone
0b69d55765 fix: 修复重新登录后basePermissions被清空问题,close #128 2025-06-27 16:00:34 +08:00
zclzone
91739a0cd5 chore: update deps and lint fix 2025-06-19 16:11:31 +08:00
zclzone
c925ca3e00 chore: update recommendations 2025-05-26 16:37:34 +08:00
zclzone
11b0f45381 feat: 增加操作列宽度至420 2025-05-23 17:56:28 +08:00
zclzone
0fe0b9d41b feat: 优化crud表格弹性高度 2025-05-19 11:21:45 +08:00
zclzone
7243f4f38e style: lint fix 2025-05-19 11:20:51 +08:00
zclzone
5178b70aca chore: update deps 2025-05-19 10:37:18 +08:00
zclzone
1934776063 chore: update deps 2025-04-24 15:36:37 +08:00
Khalil Yao
c675a4d420 feat: optimize password input. (#122) 2025-04-12 10:42:32 +08:00
zclzone
e29343586f docs: update README 2025-04-08 21:15:02 +08:00
zclzone
6fea2a1b69 Merge branch '2.x' of https://github.com/zclzone/vue-naive-admin into 2.x 2025-03-30 21:02:00 +08:00
zclzone
2513e475be fix: tooltip箭头未居中问题 2025-03-30 21:01:20 +08:00
dxmqj200682
dde25dd7ab fix: 菜单path跳转判空,close #120 2025-03-30 15:43:11 +08:00
leip247
1eb59b7cad fix: 正确指向官网 (#119) 2025-03-27 09:08:03 +08:00
zclzone
27a50a99b4 fix: 移除演示按钮的点击事件 2025-03-24 11:09:11 +08:00
zclzone
cf0ba0a2bf fix: withPermission完善 2025-03-21 14:39: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
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
leip247
6af6eb3c99 fix(slot): 优化部分slot写法,减少手动判断 (#101)
* fix(slot): 优化部分slot写法,减少手动判断

* fix(slot): 优化部分slot写法,减少手动判断
2024-11-13 11:05:03 +08:00
zclzone
69e6e9a14a fix: 替换失效图片链接 2024-10-29 15:14:04 +08:00
zclzone
d066c999cc chore: update deps 2024-10-22 15:27:46 +08:00
zclzone
97fb638ac2 chore: update deps 2024-10-08 15:44:41 +08:00
zclzone
db800f60a9 docs: update README 2024-09-26 17:25:52 +08:00
zclzone
304b74bb49 chore: 弃用sass,使用原生css替换 2024-09-25 18:19:48 +08:00
zclzone
359f683955 chore: update deps, close #97 2024-09-25 11:24:23 +08:00
zclzone
d7a4034c69 chore: 移除无用文件 2024-09-14 10:12:08 +08:00
zclzone
520db7ec6e style: lint 2024-09-06 14:46:07 +08:00
zclzone
2264512a8b chore: update deps 2024-09-06 14:27:47 +08:00
zclzone
3359b0f2b5 fix: 修复keepAlive页面transition不生效问题, close #93 2024-09-03 09:02:17 +08:00
zclzone
615c3b63f7 chore: 集成plugin-vue-jsx插件,支持jsx写法 2024-08-27 10:50:14 +08:00
zclzone
51f2d5d22c fix: CRUD组件前端分页问题 2024-08-21 11:35:48 +08:00
zclzone
02e614e903 chore: 兼容较低版本node 2024-08-21 09:51:41 +08:00
67 changed files with 4028 additions and 3890 deletions

View File

@@ -6,7 +6,7 @@ VITE_PUBLIC_PATH = '/'
# Axios 基础路径
# VITE_AXIOS_BASE_URL = '/api' # 用于代理
VITE_AXIOS_BASE_URL = 'https://mock.apipark.cn/m1/3776410-0-default' # apifox云端mock
VITE_AXIOS_BASE_URL = 'https://m1.apifoxmock.com/m1/3776410-3408296-default' # apifox云端mock
# 代理配置-target
VITE_PROXY_TARGET = 'http://localhost:8085'

View File

@@ -4,7 +4,5 @@ VITE_USE_HASH = 'false'
# 资源公共路径,需要以 /开头和结尾
VITE_PUBLIC_PATH = '/'
VITE_AXIOS_BASE_URL = '/api' # 用于代理
# 代理配置-target
VITE_PROXY_TARGET = 'http://localhost:8085'
# VITE_AXIOS_BASE_URL = '/api' # 用于代理
VITE_AXIOS_BASE_URL = 'https://m1.apifoxmock.com/m1/3776410-3408296-default' # apifox云端mock

View File

@@ -1,63 +0,0 @@
{
"globals": {
"$loadingBar": true,
"$message": true,
"$dialog": true,
"$notification": true,
"$modal": true,
"defineOptions": true,
"EffectScope": true,
"computed": true,
"createApp": true,
"customRef": true,
"defineAsyncComponent": true,
"defineComponent": true,
"effectScope": true,
"getCurrentInstance": true,
"getCurrentScope": true,
"h": true,
"inject": true,
"isProxy": true,
"isReactive": true,
"isReadonly": true,
"isRef": true,
"markRaw": true,
"nextTick": true,
"onActivated": true,
"onBeforeMount": true,
"onBeforeUnmount": true,
"onBeforeUpdate": true,
"onDeactivated": true,
"onErrorCaptured": true,
"onMounted": true,
"onRenderTracked": true,
"onRenderTriggered": true,
"onScopeDispose": true,
"onServerPrefetch": true,
"onUnmounted": true,
"onUpdated": true,
"provide": true,
"reactive": true,
"readonly": true,
"ref": true,
"resolveComponent": true,
"shallowReactive": true,
"shallowReadonly": true,
"shallowRef": true,
"toRaw": true,
"toRef": true,
"toRefs": true,
"triggerRef": true,
"unref": true,
"useAttrs": true,
"useCssModule": true,
"useCssVars": true,
"useRoute": true,
"useRouter": true,
"useSlots": true,
"watch": true,
"watchEffect": true,
"watchPostEffect": true,
"watchSyncEffect": true
}
}

View File

@@ -7,6 +7,6 @@
"esbenp.prettier-vscode",
"sdras.vue-vscode-snippets",
"mikestead.dotenv",
"simonhe.common-intellisense"
"tu6ge.naive-ui-intelligence"
]
}

View File

@@ -19,6 +19,5 @@
"markdown",
"json",
"jsonc"
],
"common-intellisense.ui": ["naiveUi2"]
]
}

View File

@@ -5,8 +5,17 @@
</p>
<p align="center">
<a href="./LICENSE"><img alt="MIT License" src="https://badgen.net/github/license/zclzone/vue-naive-admin"/></a>
<a href="https://gitcode.com/zclzone/vue-naive-admin"><img alt="GitCode" src="https://gitcode.com/zclzone/vue-naive-admin/star/badge.svg
"/></a>
</p>
---
<a href="https://hellogithub.com/repository/54f19ba1f9ae4238b3cbd111f3c428b0" target="_blank"><img src="https://abroad.hellogithub.com/v1/widgets/recommend.svg?rid=54f19ba1f9ae4238b3cbd111f3c428b0&claim_uid=jXGayRdJZScqMNr" alt="FeaturedHelloGitHub" style="width: 250px; height: 54px;" width="250" height="54" /></a>
> [!Tip]
> 我们提供WebPC+H5+小程序)的技术支持服务及定制开发,不限前后端,详细需求可联系作者,微信: isme-admin邮箱: zclzone@outlook.com
## 简介
Vue Naive Admin 是一款极简风格的后台管理模板,包含前后端解决方案,前端使用 Vite + Vue3 + Pinia + Unocss后端使用 Nestjs + TypeOrm + MySql简单易用赏心悦目历经十几次重构和细节打磨诚意满满
@@ -51,6 +60,7 @@ Vue Naive Admin 提供一套后端代码,技术栈使用 Nestjs + TypeOrm + My
- 源码-github: [isme-nest-serve | github](https://github.com/zclzone/isme-nest-serve)
- 源码-gitee: [isme-nest-serve | gitee](https://gitee.com/isme-admin/isme-nest-serve)
- 源码-gitcode: [isme-nest-serve | gitcode](https://gitcode.com/zclzone/isme-nest-serve)
## 文档
@@ -83,8 +93,6 @@ npx degit zclzone/vue-naive-admin
- [naive-admin-go](https://github.com/ituserxxx/naive-admin-go): 一个 Go 后端服务,基于 gin、gorm、mysql、jwt和session已对接 Vue Naive Admin 2.0。
- [isme-java](https://github.com/AllenDengMs/isme-java): 一个轻量且完成度高的Java后端服务基于Springboot 3 + JDK21层次结构严谨注释齐全避免过度封装代码可读性度高依赖最简化上手成本低已集成 账号管理、权限管理、API鉴权、消息国际化等功能。
## 入群交流
## 联系作者 or 进交流
添加微信,拉你进群
![](https://static.isme.top/image/coder_wx.jpg)
[https://www.isme.top/contact.html](https://www.isme.top/contact.html)

View File

@@ -8,7 +8,7 @@
import path from 'node:path'
import { globSync } from 'glob'
import dynamicIcons from '../src/assets/icons/dynamic-icons'
import dynamicIcons from '../src/assets/icons/dynamic-icons.js'
/**
* @usage 生成icons, 用于 unocss safelist以支持页面动态渲染自定义图标

View File

@@ -6,5 +6,5 @@
* Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
**********************************/
export { pluginPagePathes } from './page-pathes'
export { pluginIcons } from './icons'
export { pluginPagePathes } from './page-pathes'

View File

@@ -1,13 +1,13 @@
{
"compilerOptions": {
"target": "ESNext",
"jsx": "preserve",
"baseUrl": "./",
"moduleResolution": "node",
"paths": {
"@/*": ["src/*"],
"~/*": ["./*"]
},
"jsx": "preserve",
"allowJs": true
},
"exclude": ["node_modules", "dist"]

View File

@@ -13,44 +13,46 @@
},
"dependencies": {
"@arco-design/color": "^0.4.0",
"@vueuse/core": "^10.11.1",
"axios": "^1.7.3",
"dayjs": "^1.11.12",
"echarts": "^5.5.1",
"lodash-es": "^4.17.21",
"naive-ui": "^2.39.0",
"pinia": "^2.2.1",
"pinia-plugin-persistedstate": "^3.2.1",
"vue": "^3.4.37",
"vue-echarts": "^6.7.3",
"vue-router": "^4.4.3",
"@vueuse/core": "^14.1.0",
"axios": "^1.13.2",
"dayjs": "^1.11.19",
"echarts": "^6.0.0",
"lodash-es": "^4.17.22",
"naive-ui": "^2.43.2",
"pinia": "^3.0.4",
"pinia-plugin-persistedstate": "^4.7.1",
"vue": "^3.5.27",
"vue-echarts": "^8.0.1",
"vue-router": "^4.6.4",
"xlsx": "^0.18.5"
},
"devDependencies": {
"@antfu/eslint-config": "^2.25.1",
"@iconify/json": "^2.2.235",
"@unocss/eslint-config": "^0.61.9",
"@unocss/eslint-plugin": "^0.61.9",
"@unocss/preset-rem-to-px": "^0.61.9",
"@vitejs/plugin-vue": "^5.1.2",
"eslint": "^9.9.0",
"eslint-plugin-format": "^0.1.2",
"esno": "^4.7.0",
"fs-extra": "^11.2.0",
"lint-staged": "^15.2.8",
"rollup-plugin-visualizer": "^5.12.0",
"sass": "^1.77.8",
"simple-git-hooks": "^2.11.1",
"taze": "^0.16.3",
"unocss": "^0.61.9",
"unplugin-auto-import": "^0.18.2",
"unplugin-vue-components": "^0.27.3",
"vite": "^5.4.0",
"vite-plugin-router-warn": "^1.0.0",
"vite-plugin-vue-devtools": "^7.3.7"
"@antfu/eslint-config": "^7.0.1",
"@iconify/json": "^2.2.430",
"@unocss/eslint-config": "^66.6.0",
"@unocss/eslint-plugin": "^66.6.0",
"@unocss/preset-rem-to-px": "^66.6.0",
"@vitejs/plugin-vue": "^6.0.3",
"@vitejs/plugin-vue-jsx": "^5.1.3",
"eslint": "^9.39.2",
"eslint-plugin-format": "^1.3.1",
"esno": "^4.8.0",
"fs-extra": "^11.3.3",
"glob": "^13.0.0",
"lint-staged": "^16.2.7",
"rollup-plugin-visualizer": "^6.0.5",
"simple-git-hooks": "^2.13.1",
"taze": "^19.9.2",
"unocss": "^66.6.0",
"unplugin-auto-import": "^21.0.0",
"unplugin-vue-components": "^31.0.0",
"vite": "^7.3.1",
"vite-plugin-router-warn": "^2.0.0",
"vite-plugin-vue-devtools": "^8.0.5",
"vue3-intro-step": "^1.0.5"
},
"simple-git-hooks": {
"pre-commit": "pnpm lint-staged"
"pre-commit": "npx lint-staged"
},
"lint-staged": {
"*": "eslint --fix"

6992
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -16,9 +16,11 @@
>
<router-view v-if="Layout" v-slot="{ Component, route: curRoute }">
<component :is="Layout">
<KeepAlive :include="keepAliveNames">
<component :is="Component" v-if="!tabStore.reloading" :key="curRoute.fullPath" />
</KeepAlive>
<transition name="fade-slide" mode="out-in" appear>
<KeepAlive :include="keepAliveNames">
<component :is="Component" v-if="!tabStore.reloading" :key="curRoute.fullPath" />
</KeepAlive>
</transition>
</component>
<LayoutSetting v-if="layoutSettingVisible" class="fixed right-12 top-1/2 z-999" />
@@ -28,9 +30,9 @@
<script setup>
import { darkTheme, dateZhCN, zhCN } from 'naive-ui'
import { layoutSettingVisible } from './settings'
import { LayoutSetting } from '@/components'
import { useAppStore, useTabStore } from '@/store'
import { layoutSettingVisible } from './settings'
const layouts = new Map()
function getLayout(name) {

View File

@@ -8,13 +8,12 @@
<template>
<main class="cus-scroll h-full flex-col flex-1 bg-#f5f6fb dark:bg-#121212">
<transition name="fade-slide" mode="out-in" appear>
<main :class="{ 'flex-1': full }" class="m-12">
<slot />
</main>
</transition>
<slot v-if="$slots.footer" name="footer" />
<TheFooter v-else-if="showFooter" class="mb-12 mt-auto" />
<main :class="{ 'flex-1': full }" class="m-12">
<slot />
</main>
<slot name="footer">
<TheFooter v-if="showFooter" class="mb-12 mt-auto" />
</slot>
<n-back-top :bottom="20" />
</main>
</template>

View File

@@ -7,18 +7,17 @@
--------------------------------->
<template>
<main class="h-full flex-1 overflow-hidden bg-#f5f6fb dark:bg-#121212">
<div class="h-full flex-col">
<AppCard
v-if="showHeader"
class="sticky top-0 z-1 min-h-60 flex items-center justify-between px-24"
border-b="1px solid light_border dark:dark_border"
>
<slot v-if="$slots.header" name="header" />
<template v-else>
<div class="flex items-center">
<slot v-if="$slots['title-prefix']" name="title-prefix" />
<template v-else-if="back">
<main class="h-full flex-col flex-1 overflow-hidden bg-#f5f6fb dark:bg-#121212">
<AppCard
v-if="showHeader"
class="sticky top-0 z-1 min-h-60 flex items-center justify-between px-24"
border-b="1px solid light_border dark:dark_border"
>
<slot v-if="$slots.header" name="header" />
<template v-else>
<div class="flex items-center">
<slot name="title-prefix">
<template v-if="back">
<div
class="mr-16 flex cursor-pointer items-center text-16 opacity-60 transition-all-300 hover:opacity-40"
@click="router.back()"
@@ -27,29 +26,27 @@
<span class="ml-4">返回</span>
</div>
</template>
<div class="mr-12 h-16 w-4 rounded-l-2 bg-primary" />
<h2 class="font-normal">
{{ title ?? route.meta?.title }}
</h2>
<slot name="title-suffix" />
</div>
<slot name="action" />
</template>
</AppCard>
<transition name="fade-slide" mode="out-in" appear>
<AppCard class="cus-scroll m-12 h-0 flex-1 rounded-8 p-24" bordered>
<slot />
</AppCard>
</transition>
</div>
</main>
</slot>
<footer class="bg-#f5f6fb dark:bg-#121212">
<slot v-if="$slots.footer" name="footer" />
<AppCard v-else-if="showFooter" class="py-12">
<TheFooter />
<div class="mr-12 h-16 w-4 rounded-l-2 bg-primary" />
<h2 class="font-normal">
{{ title ?? route.meta?.title }}
</h2>
<slot name="title-suffix" />
</div>
<slot name="action" />
</template>
</AppCard>
</footer>
<AppCard class="cus-scroll m-12 h-0 flex-1 rounded-8 p-24" bordered>
<slot />
</AppCard>
<slot name="footer">
<AppCard v-if="showFooter" class="flex-shrink-0 py-12">
<TheFooter />
</AppCard>
</slot>
</main>
</template>
<script setup>

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>
@@ -94,8 +94,8 @@
<script setup>
import { MeModal } from '@/components'
import { useAppStore } from '@/store'
import { useModal } from '@/composables'
import { useAppStore } from '@/store'
const appStore = useAppStore()
const [modalRef] = useModal()

View File

@@ -1,16 +1,19 @@
<template>
<n-tooltip trigger="hover">
<template #trigger>
<n-color-picker
class="mr-16 h-32 w-32"
:value="appStore.primaryColor"
:swatches="primaryColors"
:on-update:value="(v) => appStore.setPrimaryColor(v)"
:render-label="() => ''"
/>
</template>
设置主题色
</n-tooltip>
<div class="f-c-c">
<n-tooltip trigger="hover" placement="bottom">
<template #trigger>
<n-color-picker
id="theme-setting"
class="h-32 w-32"
:value="appStore.primaryColor"
:swatches="primaryColors"
:on-update:value="(v) => appStore.setPrimaryColor(v)"
:render-label="() => ''"
/>
</template>
设置主题色
</n-tooltip>
</div>
</template>
<script setup>

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"
@@ -38,6 +39,7 @@ async function toggleDark({ clientX, clientY }) {
duration: 500,
easing: 'ease-in',
pseudoElement: `::view-transition-${isDark.value ? 'old' : 'new'}(root)`,
fill: 'both',
},
)
}

View File

@@ -1,6 +1,6 @@
export { default as AppCard } from './AppCard.vue'
export { default as TheFooter } from './TheFooter.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 TheFooter } from './TheFooter.vue'
export { default as ToggleTheme } from './ToggleTheme.vue'

View File

@@ -7,48 +7,52 @@
--------------------------------->
<template>
<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" />
展开
<div class="h-full flex flex-col overflow-hidden">
<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 v-else text type="primary" @click="toggleExpand">
<i class="i-fe:chevrons-up ml-4" />
收起
<n-button attr-type="submit" class="ml-20" type="primary">
<i class="i-fe:search mr-4" />
搜索
</n-button>
</template>
</div>
</form>
</AppCard>
<NDataTable
:remote="remote"
:loading="loading"
:scroll-x="scrollX"
:columns="columns"
:data="tableData"
:row-key="(row) => row[rowKey]"
:pagination="isPagination ? pagination : false"
@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
:remote="remote"
:loading="loading"
:scroll-x="scrollX"
:columns="columns"
:data="tableData"
:row-key="(row) => row[rowKey]"
:pagination="isPagination ? pagination : false"
flex-height
class="flex-1"
@update:checked-row-keys="onChecked"
@update:page="onPageChange"
/>
</div>
</template>
<script setup>
@@ -156,7 +160,7 @@ async function handleQuery() {
}
function handleSearch(keepCurrentPage = false) {
if (keepCurrentPage) {
if (keepCurrentPage || !props.remote) {
handleQuery()
}
else {

View File

@@ -1,3 +1,3 @@
export { default as MeModal } from './modal/index.vue'
export { default as MeCrud } from './crud/index.vue'
export { default as MeQueryItem } from './crud/QueryItem.vue'
export { default as MeModal } from './modal/index.vue'

View File

@@ -26,21 +26,22 @@
<!-- 底部按钮 -->
<template #footer>
<slot v-if="$slots.footer" name="footer" />
<footer v-else-if="modalOptions.showFooter" class="flex justify-end">
<n-button v-if="modalOptions.showCancel" @click="handleCancel()">
{{ modalOptions.cancelText }}
</n-button>
<n-button
v-if="modalOptions.showOk"
type="primary"
:loading="modalOptions.okLoading"
class="ml-20"
@click="handleOk()"
>
{{ modalOptions.okText }}
</n-button>
</footer>
<slot name="footer">
<footer v-if="modalOptions.showFooter" class="flex justify-end">
<n-button v-if="modalOptions.showCancel" @click="handleCancel()">
{{ modalOptions.cancelText }}
</n-button>
<n-button
v-if="modalOptions.showOk"
type="primary"
:loading="modalOptions.okLoading"
class="ml-20"
@click="handleOk()"
>
{{ modalOptions.okText }}
</n-button>
</footer>
</slot>
</template>
</n-card>
</n-modal>

View File

@@ -1,4 +1,4 @@
export * from './useAliveData'
export * from './useCrud'
export * from './useForm'
export * from './useModal'
export * from './useAliveData'

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

@@ -6,6 +6,7 @@
* Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
**********************************/
import { withDirectives } from 'vue'
import { router } from '@/router'
const permission = {
@@ -21,3 +22,17 @@ const permission = {
export function setupDirectives(app) {
app.directive('permission', permission)
}
/**
* 用于h函数使用自定义权限指令
*
* @param {*} vnode 虚拟节点
* @param {*} code 权限码
* @returns 返回一个包含权限指令的vnode
*
* 使用示例withPermission(h('button', {class: 'text-red-500'}, '删除'), 'user:delete')
*
*/
export function withPermission(vnode, code) {
return withDirectives(vnode, [[permission, 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,7 +8,8 @@
<template>
<div
class="f-c-c cursor-pointer rounded-4 p-6 text-22 transition-all-300 auto-bg-hover"
id="menu-collapse"
class="f-c-c cursor-pointer rounded-4 auto-bg-hover p-6 text-22 transition-all-300"
@click="appStore.switchCollapsed"
>
<i :class="appStore.collapsed ? 'i-line-md-menu-unfold-left' : 'i-line-md-menu-fold-left'" />

View File

@@ -54,12 +54,14 @@ function handleMenuSelect(key, item) {
})
}
else {
if (!item.path)
return
router.push(item.path)
}
}
</script>
<style lang="scss">
<style>
.side-menu:not(.n-menu--collapsed) {
.n-menu-item-content {
&::before {

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>
@@ -21,9 +21,9 @@
</template>
<script setup>
import { useAuthStore, usePermissionStore, useUserStore } from '@/store'
import { RoleSelect } from '@/layouts/components'
import api from '@/api'
import { RoleSelect } from '@/layouts/components'
import { useAuthStore, usePermissionStore, useUserStore } from '@/store'
const router = useRouter()
const userStore = useUserStore()

View File

@@ -1,8 +1,9 @@
export { default as RoleSelect } from './RoleSelect.vue'
export { default as UserAvatar } from './UserAvatar.vue'
export { default as MenuCollapse } from './MenuCollapse.vue'
export { default as BeginnerGuide } from './BeginnerGuide.vue'
export { default as BreadCrumb } from './BreadCrumb.vue'
export { default as AppTab } from './tab/index.vue'
export { default as Fullscreen } from './Fullscreen.vue'
export { default as MenuCollapse } from './MenuCollapse.vue'
export { default as RoleSelect } from './RoleSelect.vue'
export { default as SideLogo } from './SideLogo.vue'
export { default as SideMenu } from './SideMenu.vue'
export { default as Fullscreen } from './Fullscreen.vue'
export { default as AppTab } from './tab/index.vue'
export { default as UserAvatar } from './UserAvatar.vue'

View File

@@ -7,7 +7,7 @@
--------------------------------->
<template>
<div>
<div id="top-tab">
<n-tabs
:value="tabStore.activeTab"
:closable="tabStore.tabs.length > 1"
@@ -36,8 +36,8 @@
</template>
<script setup>
import ContextMenu from './ContextMenu.vue'
import { useTabStore } from '@/store'
import ContextMenu from './ContextMenu.vue'
const router = useRouter()
const tabStore = useTabStore()
@@ -74,7 +74,7 @@ async function handleContextMenu(e, tagItem) {
}
</script>
<style scoped lang="scss">
<style scoped>
:deep(.n-tabs) {
.n-tabs-tab {
padding-left: 16px;

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

@@ -27,10 +27,10 @@
</template>
<script setup>
import SideBar from './sidebar/index.vue'
import AppHeader from './header/index.vue'
import { useAppStore } from '@/store'
import { AppTab } from '@/layouts/components'
import { useAppStore } from '@/store'
import AppHeader from './header/index.vue'
import SideBar from './sidebar/index.vue'
const appStore = useAppStore()
</script>

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

@@ -24,9 +24,9 @@
</template>
<script setup>
import SideBar from './sidebar/index.vue'
import AppHeader from './header/index.vue'
import { useAppStore } from '@/store'
import AppHeader from './header/index.vue'
import SideBar from './sidebar/index.vue'
const appStore = useAppStore()
</script>

View File

@@ -23,8 +23,8 @@
</template>
<script setup>
import SideBar from './sidebar/index.vue'
import { useAppStore } from '@/store'
import SideBar from './sidebar/index.vue'
const appStore = useAppStore()
</script>

View File

@@ -8,16 +8,16 @@
* Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
**********************************/
import 'uno.css'
import '@/styles/reset.css'
import '@/styles/global.scss'
import { createApp } from 'vue'
import App from './App.vue'
import { setupDirectives } from './directives'
import { setupRouter } from './router'
import { setupStore } from './store'
import { setupNaiveDiscreteApi } from './utils'
import { setupDirectives } from './directives'
import '@/styles/reset.css'
import '@/styles/global.css'
import 'uno.css'
async function bootstrap() {
const app = createApp(App)

View File

@@ -6,8 +6,8 @@
* Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
**********************************/
import { useAuthStore, usePermissionStore, useUserStore } from '@/store'
import api from '@/api'
import { useAuthStore, usePermissionStore, useUserStore } from '@/store'
import { getPermissions, getUserInfo } from '@/store/helper'
const WHITE_LIST = ['/login', '/404']

View File

@@ -7,8 +7,8 @@
**********************************/
import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router'
import { setupRouterGuards } from './guards'
import { basicRoutes } from './basic-routes'
import { setupRouterGuards } from './guards'
export const router = createRouter({
history:

View File

@@ -1,5 +1,6 @@
import { basePermissions } from '@/settings'
import { cloneDeep } from 'lodash-es'
import api from '@/api'
import { basePermissions } from '@/settings'
export async function getUserInfo() {
const res = await api.getUser()
@@ -26,5 +27,5 @@ export async function getPermissions() {
catch (error) {
console.error(error)
}
return basePermissions.concat(asyncPermissions)
return cloneDeep(basePermissions).concat(asyncPermissions)
}

View File

@@ -6,9 +6,9 @@
* Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
**********************************/
import { defineStore } from 'pinia'
import { useDark } from '@vueuse/core'
import { generate, getRgbStr } from '@arco-design/color'
import { useDark } from '@vueuse/core'
import { defineStore } from 'pinia'
import { defaultLayout, defaultPrimaryColor, naiveThemeOverrides } from '@/settings'
export const useAppStore = defineStore('app', {
@@ -50,7 +50,7 @@ export const useAppStore = defineStore('app', {
},
},
persist: {
paths: ['collapsed', 'layout', 'primaryColor', 'naiveThemeOverrides'],
pick: ['collapsed', 'layout', 'primaryColor', 'naiveThemeOverrides'],
storage: sessionStorage,
},
})

View File

@@ -1,6 +1,6 @@
export * from './app'
export * from './auth'
export * from './permission'
export * from './router'
export * from './tab'
export * from './user'
export * from './router'

View File

@@ -29,8 +29,6 @@ export const usePermissionStore = defineStore('permission', {
const route = this.generateRoute(item, item.show ? null : parent?.key)
if (item.enable && route.path && !route.path.startsWith('http'))
this.accessRoutes.push(route)
if (!item.show)
return null
const menuItem = {
label: route.meta.title,
key: route.name,
@@ -48,6 +46,8 @@ export const usePermissionStore = defineStore('permission', {
if (!menuItem.children.length)
delete menuItem.children
}
if (!item.show)
return null
return menuItem
},
generateRoute(item, parentKey) {

View File

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

View File

@@ -26,12 +26,12 @@ body {
.fade-slide-enter-from {
opacity: 0;
transform: translateX(-30px);
transform: translateX(-2%);
}
.fade-slide-leave-to {
opacity: 0;
transform: translateX(30px);
transform: translateX(2%);
}
/* 自定义滚动条样式 */

View File

@@ -34,8 +34,8 @@ html,
-webkit-text-size-adjust: 100%; /* 2 */
-moz-tab-size: 4; /* 3 */
tab-size: 4; /* 3 */
font-family: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
'Noto Color Emoji'; /* 4 */
font-family:
ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; /* 4 */
font-feature-settings: normal; /* 5 */
font-variation-settings: normal; /* 6 */
-webkit-tap-highlight-color: transparent; /* 7 */
@@ -114,7 +114,8 @@ code,
kbd,
samp,
pre {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; /* 1 */
font-family:
ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; /* 1 */
font-feature-settings: normal; /* 2 */
font-variation-settings: normal; /* 3 */
font-size: 1em; /* 4 */

View File

@@ -10,45 +10,34 @@
import { useAuthStore } from '@/store'
let isConfirming = false
function handleAuthExpired(content, needTip) {
if (isConfirming || !needTip)
return
isConfirming = true
$dialog.confirm({
title: '提示',
type: 'info',
content,
confirm() {
useAuthStore().logout()
window.$message?.success('已退出登录')
isConfirming = false
},
cancel() {
isConfirming = false
},
})
return false
}
export function resolveResError(code, message, needTip = true) {
switch (code) {
case 401:
if (isConfirming || !needTip)
return
isConfirming = true
$dialog.confirm({
title: '提示',
type: 'info',
content: '登录已过期,是否重新登录?',
confirm() {
useAuthStore().logout()
window.$message?.success('已退出登录')
isConfirming = false
},
cancel() {
isConfirming = false
},
})
return false
return handleAuthExpired('登录已过期,是否重新登录?', needTip)
case 11007:
case 11008:
if (isConfirming || !needTip)
return
isConfirming = true
$dialog.confirm({
title: '提示',
type: 'info',
content: `${message},是否重新登录?`,
confirm() {
useAuthStore().logout()
window.$message?.success('已退出登录')
isConfirming = false
},
cancel() {
isConfirming = false
},
})
return false
return handleAuthExpired(`${message},是否重新登录?`, needTip)
case 403:
message = '请求被拒绝'
break

View File

@@ -7,29 +7,10 @@
* Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
**********************************/
import { resolveResError } from './helpers'
import { useAuthStore } from '@/store'
import { resolveResError } from './helpers'
export function setupInterceptors(axiosInstance) {
function reqResolve(config) {
// 处理不需要token的请求
if (config.needToken === false) {
return config
}
const { accessToken } = useAuthStore()
if (accessToken) {
// token: Bearer + xxx
config.headers.Authorization = `Bearer ${accessToken}`
}
return config
}
function reqReject(error) {
return Promise.reject(error)
}
const SUCCESS_CODES = [0, 200]
function resResolve(response) {
const { data, status, config, statusText, headers } = response
@@ -49,22 +30,41 @@ export function setupInterceptors(axiosInstance) {
return Promise.resolve(data ?? response)
}
async function resReject(error) {
if (!error || !error.response) {
const code = error?.code
/** 根据code处理对应的操作并返回处理后的message */
const message = resolveResError(code, error.message)
return Promise.reject({ code, message, error })
}
const { data, status, config } = error.response
const code = data?.code ?? status
const needTip = config?.needTip !== false
const message = resolveResError(code, data?.message ?? error.message, needTip)
return Promise.reject({ code, message, error: error.response?.data || error.response })
}
axiosInstance.interceptors.request.use(reqResolve, reqReject)
axiosInstance.interceptors.response.use(resResolve, resReject)
}
function reqResolve(config) {
// 处理不需要token的请求
if (config.needToken === false) {
return config
}
const { accessToken } = useAuthStore()
if (accessToken) {
// token: Bearer + xxx
config.headers.Authorization = `Bearer ${accessToken}`
}
return config
}
function reqReject(error) {
return Promise.reject(error)
}
async function resReject(error) {
if (!error || !error.response) {
const code = error?.code
/** 根据code处理对应的操作并返回处理后的message */
const message = resolveResError(code, error.message)
return Promise.reject({ code, message, error })
}
const { data, status, config } = error.response
const code = data?.code ?? status
const needTip = config?.needTip !== false
const message = resolveResError(code, data?.message ?? error.message, needTip)
return Promise.reject({ code, message, error: error.response?.data || error.response })
}

View File

@@ -8,8 +8,8 @@
**********************************/
import * as NaiveUI from 'naive-ui'
import { isNullOrUndef } from '@/utils'
import { useAppStore } from '@/store'
import { isNullOrUndef } from '@/utils'
export function setupMessage(NMessage) {
class Message {

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

@@ -22,8 +22,8 @@
<script setup>
import { MeModal } from '@/components'
import { sleep } from '@/utils'
import { useModal } from '@/composables'
import { sleep } from '@/utils'
const text = ref('')
const [$modal1, okLoading1] = useModal()

View File

@@ -26,7 +26,7 @@
<li
v-for="item in icons"
:key="item"
class="w-160 f-c-c flex-col cursor-pointer rounded-12 px-12 py-24 card-border auto-bg"
class="w-160 f-c-c flex-col cursor-pointer card-border rounded-12 auto-bg px-12 py-24"
@click="copy(`&lt;i class=&quot;${item}&quot; /&gt;`)"
>
<i :class="`${item}?mask`" class="text-28 text-gray-600 hover:bg-primary" />

View File

@@ -22,7 +22,7 @@
</p>
<div mt-20 w-350 f-c-c flex-col>
<div flex flex-wrap justify-around rounded-10 p-10 card-border>
<div flex flex-wrap justify-around card-border rounded-10 p-10>
<div m-20 h-50 w-50 f-c-c rounded-5 p-10 border="1 solid">
<span h-6 w-6 rounded-3 bg-black dark:bg-white />
</div>

View File

@@ -62,12 +62,7 @@ defineOptions({ name: 'ImgUpload' })
const { copy, copied } = useClipboard()
const imgList = reactive([
{ url: 'https://cdn.isme.top/images/5c23d52f880511ebb6edd017c2d2eca2.jpg' },
{ url: 'https://cdn.isme.top/images/5c23d52f880511ebb6edd017c2d2eca2.jpg' },
{ url: 'https://cdn.isme.top/images/5c23d52f880511ebb6edd017c2d2eca2.jpg' },
{ url: 'https://cdn.isme.top/images/5c23d52f880511ebb6edd017c2d2eca2.jpg' },
])
const imgList = reactive([])
watch(copied, (val) => {
if (val)

View File

@@ -30,10 +30,9 @@
<n-card class="ml-12 w-70%" title="✨ 欢迎使用 Vue Naive Admin 2.0">
<template #header-extra>
<a
class="text-14 text-primary text-highlight hover:underline hover:opacity-80"
class="text-highlight text-14 text-primary hover:underline hover:opacity-80"
href="https://isme.top"
target="_blank"
@click.prevent="message?.info('官网正在火速开发中...')"
>
isme.top
</a>
@@ -69,7 +68,7 @@
<div class="mt-12 flex">
<n-card class="w-50%" title="💯 特性" segmented>
<template #header-extra>
<span class="opacity-90 text-highlight">👏 历经十几次重构和细节打磨</span>
<span class="text-highlight opacity-90">👏 历经十几次重构和细节打磨</span>
</template>
<ul class="opacity-90">
@@ -160,9 +159,9 @@
</template>
<script setup>
import * as echarts from 'echarts/core'
import { GridComponent, LegendComponent, TooltipComponent } from 'echarts/components'
import { BarChart, LineChart, PieChart } from 'echarts/charts'
import { GridComponent, LegendComponent, TooltipComponent } from 'echarts/components'
import * as echarts from 'echarts/core'
import { UniversalTransition } from 'echarts/features'
import { CanvasRenderer } from 'echarts/renderers'
import VChart from 'vue-echarts'
@@ -251,7 +250,7 @@ const skillOption = {
},
series: [
{
top: '12%',
bottom: '12%',
type: 'pie',
radius: ['35%', '90%'],
avoidLabelOverlap: true,
@@ -284,6 +283,4 @@ const skillOption = {
},
],
}
const message = $message
</script>

View File

@@ -9,7 +9,7 @@
<template>
<div class="wh-full flex-col bg-[url(@/assets/images/login_bg.webp)] bg-cover">
<div
class="m-auto max-w-700 min-w-345 f-c-c rounded-8 bg-opacity-20 bg-cover p-12 card-shadow auto-bg"
class="m-auto max-w-700 min-w-345 f-c-c rounded-8 auto-bg bg-opacity-20 bg-cover p-12 card-shadow"
>
<div class="hidden w-380 px-20 py-35 md:block">
<img src="@/assets/images/login_banner.webp" class="w-full" alt="login_banner">
@@ -102,9 +102,9 @@
<script setup>
import { useStorage } from '@vueuse/core'
import api from './api'
import { lStorage, throttle } from '@/utils'
import { useAuthStore } from '@/store'
import { lStorage, throttle } from '@/utils'
import api from './api'
const authStore = useAuthStore()
const router = useRouter()

View File

@@ -38,8 +38,8 @@
</template>
<script setup>
import { withModifiers } from 'vue'
import { NButton } from 'naive-ui'
import { withModifiers } from 'vue'
import api from '../api'
import ResAddOrEdit from './ResAddOrEdit.vue'

View File

@@ -159,12 +159,12 @@
</template>
<script setup>
import pagePathes from 'isme:page-pathes'
import icons from 'isme:icons'
import api from '../api'
import QuestionLabel from './QuestionLabel.vue'
import pagePathes from 'isme:page-pathes'
import { MeModal } from '@/components'
import { useForm, useModal } from '@/composables'
import api from '../api'
import QuestionLabel from './QuestionLabel.vue'
const props = defineProps({
menus: {

View File

@@ -11,7 +11,7 @@
<div class="flex">
<n-spin size="small" :show="treeLoading">
<MenuTree
v-model:currentMenu="currentMenu"
v-model:current-menu="currentMenu"
class="w-320 shrink-0"
:tree-data="treeData"
@refresh="initData"
@@ -93,10 +93,10 @@
<script setup>
import { NButton, NSwitch } from 'naive-ui'
import { MeCrud } from '@/components'
import api from './api'
import MenuTree from './components/MenuTree.vue'
import ResAddOrEdit from './components/ResAddOrEdit.vue'
import api from './api'
import { MeCrud } from '@/components'
const treeData = ref([])
const treeLoading = ref(false)

View File

@@ -75,7 +75,7 @@
:checked-keys="modalForm.permissionIds"
:on-update:checked-keys="(keys) => (modalForm.permissionIds = keys)"
default-expand-all checkable check-on-click
checkable check-on-click default-expand-all
class="cus-scroll max-h-200 w-full"
/>
</n-form-item>
@@ -96,9 +96,9 @@
<script setup>
import { NButton, NSwitch } from 'naive-ui'
import api from './api'
import { MeCrud, MeModal, MeQueryItem } from '@/components'
import { useCrud } from '@/composables'
import api from './api'
defineOptions({ name: 'RoleMgt' })

View File

@@ -69,9 +69,9 @@
<script setup>
import { NAvatar, NButton, NSwitch, NTag } from 'naive-ui'
import { h } from 'vue'
import api from './api'
import { MeCrud, MeQueryItem } from '@/components'
import { formatDateTime } from '@/utils'
import api from './api'
defineOptions({ name: 'RoleUser' })
const route = useRoute()
@@ -163,31 +163,31 @@ const columns = [
render(row) {
return row.roles?.some(item => item.id === +route.params.roleId)
? h(
NButton,
{
size: 'small',
type: 'error',
secondary: true,
onClick: () => handleBatchRemove([row.id]),
},
{
default: () => '取消授权',
icon: () => h('i', { class: 'i-material-symbols:delete-outline text-14' }),
},
)
NButton,
{
size: 'small',
type: 'error',
secondary: true,
onClick: () => handleBatchRemove([row.id]),
},
{
default: () => '取消授权',
icon: () => h('i', { class: 'i-material-symbols:delete-outline text-14' }),
},
)
: h(
NButton,
{
size: 'small',
type: 'primary',
secondary: true,
onClick: () => handleBatchAdd([row.id]),
},
{
default: () => '授权',
icon: () => h('i', { class: 'i-line-md:confirm-circle text-14' }),
},
)
NButton,
{
size: 'small',
type: 'primary',
secondary: true,
onClick: () => handleBatchAdd([row.id]),
},
{
default: () => '授权',
icon: () => h('i', { class: 'i-line-md:confirm-circle text-14' }),
},
)
},
},
]

View File

@@ -77,7 +77,7 @@
trigger: ['input', 'blur'],
}"
>
<n-input v-model:value="modalForm.password" />
<n-input v-model:value="modalForm.password" type="password" show-password-on="mousedown" />
</n-form-item>
<n-form-item v-if="['add', 'setRole'].includes(modalAction)" label="角色" path="roleIds">
@@ -111,10 +111,11 @@
<script setup>
import { NAvatar, NButton, NSwitch, NTag } from 'naive-ui'
import api from './api'
import { formatDateTime } from '@/utils'
import { MeCrud, MeModal, MeQueryItem } from '@/components'
import { useCrud } from '@/composables'
import { withPermission } from '@/directives'
import { formatDateTime } from '@/utils'
import api from './api'
defineOptions({ name: 'UserMgt' })
@@ -219,17 +220,29 @@ const columns = [
{
title: '操作',
key: 'actions',
width: 320,
width: 420,
align: 'right',
fixed: 'right',
hideInExcel: true,
render(row) {
return [
withPermission(
h(NButton, {
size: 'small',
type: 'primary',
secondary: true,
}, {
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

@@ -73,10 +73,10 @@
require-mark-placement="left"
>
<n-form-item label="原密码" path="oldPassword" :rule="required">
<n-input v-model:value="pwdForm.oldPassword" type="password" placeholder="请输入原密码" />
<n-input v-model:value="pwdForm.oldPassword" type="password" placeholder="请输入原密码" show-password-on="mousedown" />
</n-form-item>
<n-form-item label="新密码" path="newPassword" :rule="required">
<n-input v-model:value="pwdForm.newPassword" type="password" placeholder="请输入新密码" />
<n-input v-model:value="pwdForm.newPassword" type="password" placeholder="请输入新密码" show-password-on="mousedown" />
</n-form-item>
</n-form>
</MeModal>
@@ -105,11 +105,11 @@
</template>
<script setup>
import api from './api'
import { MeModal } from '@/components'
import { useForm, useModal } from '@/composables'
import { useUserStore } from '@/store'
import { getUserInfo } from '@/store/helper'
import api from './api'
const userStore = useUserStore()
const required = {

View File

@@ -6,15 +6,15 @@
* Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
**********************************/
import { defineConfig, presetAttributify, presetIcons, presetUno } from 'unocss'
import presetRemToPx from '@unocss/preset-rem-to-px'
import { FileSystemIconLoader } from '@iconify/utils/lib/loader/node-loaders'
import { getIcons } from './build'
import presetRemToPx from '@unocss/preset-rem-to-px'
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,

View File

@@ -7,14 +7,15 @@
**********************************/
import path from 'node:path'
import { defineConfig, loadEnv } from 'vite'
import Vue from '@vitejs/plugin-vue'
import VueDevTools from 'vite-plugin-vue-devtools'
import VueJsx from '@vitejs/plugin-vue-jsx'
import Unocss from 'unocss/vite'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers'
import Components from 'unplugin-vue-components/vite'
import { defineConfig, loadEnv } from 'vite'
import removeNoMatch from 'vite-plugin-router-warn'
import VueDevTools from 'vite-plugin-vue-devtools'
import { pluginIcons, pluginPagePathes } from './build/plugin-isme'
export default defineConfig(({ mode }) => {
@@ -25,6 +26,7 @@ export default defineConfig(({ mode }) => {
base: VITE_PUBLIC_PATH || '/',
plugins: [
Vue(),
VueJsx(),
VueDevTools(),
Unocss(),
AutoImport({
@@ -67,6 +69,9 @@ export default defineConfig(({ mode }) => {
},
},
},
optimizeDeps: {
include: ['vue3-intro-step'],
},
build: {
chunkSizeWarningLimit: 1024, // chunk 大小警告的限制单位kb
},