mirror of
https://github.com/zclzone/vue-naive-admin.git
synced 2025-12-28 04:00:22 +08:00
Compare commits
92 Commits
54c134c879
...
2.x
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2e03a4b1a6 | ||
|
|
a5a20a30c9 | ||
|
|
9a7170f10c | ||
|
|
fb4d18cc0a | ||
|
|
d078bba63e | ||
|
|
5663f58b6e | ||
|
|
32700aee17 | ||
|
|
e593ba3ca5 | ||
|
|
d9de814074 | ||
|
|
5457c4e70b | ||
|
|
6757e47fb1 | ||
|
|
3fbcfdf0e1 | ||
|
|
d6764158f3 | ||
|
|
4785842629 | ||
|
|
5b4b0bd9ad | ||
|
|
3c04bbce8e | ||
|
|
0b69d55765 | ||
|
|
91739a0cd5 | ||
|
|
c925ca3e00 | ||
|
|
11b0f45381 | ||
|
|
0fe0b9d41b | ||
|
|
7243f4f38e | ||
|
|
5178b70aca | ||
|
|
1934776063 | ||
|
|
c675a4d420 | ||
|
|
e29343586f | ||
|
|
6fea2a1b69 | ||
|
|
2513e475be | ||
|
|
dde25dd7ab | ||
|
|
1eb59b7cad | ||
|
|
27a50a99b4 | ||
|
|
cf0ba0a2bf | ||
|
|
70c2098d2e | ||
|
|
82748abcc3 | ||
|
|
e5fd1405da | ||
|
|
833321d31d | ||
|
|
53afc9b531 | ||
|
|
0733b81f82 | ||
|
|
26ce0bd551 | ||
|
|
1201e8e172 | ||
|
|
78d9bb99f2 | ||
|
|
9b510e8350 | ||
|
|
7ce6cf1f7c | ||
|
|
30ca69cf16 | ||
|
|
c34c3646aa | ||
|
|
6563797afc | ||
|
|
6af6eb3c99 | ||
|
|
69e6e9a14a | ||
|
|
d066c999cc | ||
|
|
97fb638ac2 | ||
|
|
db800f60a9 | ||
|
|
304b74bb49 | ||
|
|
359f683955 | ||
|
|
d7a4034c69 | ||
|
|
520db7ec6e | ||
|
|
2264512a8b | ||
|
|
3359b0f2b5 | ||
|
|
615c3b63f7 | ||
|
|
51f2d5d22c | ||
|
|
02e614e903 | ||
|
|
fdb6ad5f1d | ||
|
|
723d141705 | ||
|
|
7b1b990d77 | ||
|
|
36ce5c34d0 | ||
|
|
1e0e1f2c48 | ||
|
|
491237919a | ||
|
|
0c0e7a465b | ||
|
|
0a0c970f5c | ||
|
|
c9e0feea47 | ||
|
|
f28e3a946e | ||
|
|
7dde0bbfc6 | ||
|
|
cf76f2177b | ||
|
|
d0a99dcf12 | ||
|
|
6ab97f511f | ||
|
|
2384113120 | ||
|
|
8cb3d2cf19 | ||
|
|
c5d93628f0 | ||
|
|
4d494801e3 | ||
|
|
e14bd2084a | ||
|
|
3c2e120e34 | ||
|
|
c230e86767 | ||
|
|
b464c70ae3 | ||
|
|
c87f56814e | ||
|
|
fd4582fc0c | ||
|
|
c2ff434b9d | ||
|
|
c98e489063 | ||
|
|
0ab029a124 | ||
|
|
e30e1fa27f | ||
|
|
3c64aca739 | ||
|
|
995175a8a8 | ||
|
|
eca0859f66 | ||
|
|
38fbbfd376 |
@@ -4,6 +4,6 @@ root = true
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
end_of_line = unset
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
3
.npmrc
3
.npmrc
@@ -1,2 +1,3 @@
|
||||
registry=https://registry.npmmirror.com
|
||||
shamefully-hoist=true
|
||||
shamefully-hoist=true
|
||||
strict-peer-dependencies=false
|
||||
|
||||
3
.vscode/extensions.json
vendored
3
.vscode/extensions.json
vendored
@@ -6,6 +6,7 @@
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode",
|
||||
"sdras.vue-vscode-snippets",
|
||||
"mikestead.dotenv"
|
||||
"mikestead.dotenv",
|
||||
"tu6ge.naive-ui-intelligence"
|
||||
]
|
||||
}
|
||||
|
||||
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@@ -1,8 +1,4 @@
|
||||
{
|
||||
// Enable the ESlint flat config support
|
||||
// (remove this if your ESLint extension above v3.0.5)
|
||||
"eslint.experimental.useFlatConfig": true,
|
||||
|
||||
"prettier.enable": false,
|
||||
"editor.formatOnSave": false,
|
||||
|
||||
|
||||
28
README.md
28
README.md
@@ -5,8 +5,14 @@
|
||||
</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="Featured|HelloGitHub" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||
|
||||
## 简介
|
||||
|
||||
Vue Naive Admin 是一款极简风格的后台管理模板,包含前后端解决方案,前端使用 Vite + Vue3 + Pinia + Unocss,后端使用 Nestjs + TypeOrm + MySql,简单易用,赏心悦目,历经十几次重构和细节打磨,诚意满满!!
|
||||
@@ -51,17 +57,24 @@ 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)
|
||||
|
||||
## 文档
|
||||
|
||||
- 项目文档: [docs | vue-naive-admin](https://docs.isme.top/web/#/624306705/188522224)
|
||||
- 项目文档: [docs | vue-naive-admin](https://isme.top)
|
||||
- 接口文档: [apidoc | isme-nest-serve](https://apifox.com/apidoc/shared-ff4a4d32-c0d1-4caf-b0ee-6abc130f734a)
|
||||
|
||||
> 注:有个比较常见的问题,就是如何添加菜单和修改菜单,由于项目是由后端控制菜单资源的,所以需要对接后端后在资源管理功能对菜单进行增删改,然后在角色管理功能给对应角色进行授权。具体如何对接后端,请参考 [项目文档](https://docs.isme.top/web/#/624306705/188522224)。当然,可能有些菜单你不想通过权限控制,那么你可以在 `/src/settings.js` 文件添加 basePermissions,只需对齐菜单资源的结构即可,结构可以参照 [接口文档](https://apifox.com/apidoc/shared-ff4a4d32-c0d1-4caf-b0ee-6abc130f734a/api-134536978)。
|
||||
> 注:有个比较常见的问题,就是如何添加菜单和修改菜单,由于项目是由后端控制菜单资源的,所以需要对接后端后在资源管理功能对菜单进行增删改,然后在角色管理功能给对应角色进行授权。具体如何对接后端,请参考 [项目文档](https://isme.top)。当然,可能有些菜单你不想通过权限控制,那么你可以在 `/src/settings.js` 文件添加 basePermissions,只需对齐菜单资源的结构即可,结构可以参照 [接口文档](https://apifox.com/apidoc/shared-ff4a4d32-c0d1-4caf-b0ee-6abc130f734a/api-134536978)。
|
||||
|
||||
## 使用这个模板创建 Github 仓库
|
||||
## 使用这个模板开始你的项目
|
||||
|
||||
[使用这个模板创建仓库](https://github.com/zclzone/vue-naive-admin/generate).
|
||||
[使用这个模板创建Github仓库](https://github.com/zclzone/vue-naive-admin/generate).
|
||||
|
||||
或者使用 `degit` 克隆此仓库,这样将没有任何历史提交记录:
|
||||
|
||||
```cmd
|
||||
npx degit zclzone/vue-naive-admin
|
||||
```
|
||||
|
||||
## 版权说明
|
||||
|
||||
@@ -75,9 +88,8 @@ Vue Naive Admin 提供一套后端代码,技术栈使用 Nestjs + TypeOrm + My
|
||||
|
||||
- [isme-java-serve](https://github.com/DHBin/isme-java-serve): 一个轻量级的Java后端服务,基于SpringBoot、MybatisPlus、SaToken、MapStruct等实现,已对接 Vue Naive Admin 2.0。
|
||||
- [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://www.isme.top/contact.html](https://www.isme.top/contact.html)
|
||||
|
||||
@@ -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,以支持页面动态渲染自定义图标
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="icon" href="/favicon.png" />
|
||||
<link rel="stylesheet" href="/resource/loading.css" />
|
||||
<title>%VITE_TITLE%</title>
|
||||
<style>
|
||||
.loading-container {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"jsx": "preserve",
|
||||
"baseUrl": "./",
|
||||
"moduleResolution": "node",
|
||||
"paths": {
|
||||
"@/*": ["src/*"],
|
||||
"~/*": ["./*"]
|
||||
},
|
||||
"jsx": "preserve",
|
||||
"allowJs": true
|
||||
},
|
||||
"exclude": ["node_modules", "dist"]
|
||||
|
||||
70
package.json
70
package.json
@@ -13,46 +13,46 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@arco-design/color": "^0.4.0",
|
||||
"@vueuse/core": "^10.10.0",
|
||||
"axios": "^1.7.2",
|
||||
"dayjs": "^1.11.11",
|
||||
"echarts": "^5.5.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"naive-ui": "^2.38.2",
|
||||
"pinia": "^2.1.7",
|
||||
"pinia-plugin-persistedstate": "^3.2.1",
|
||||
"vue": "^3.4.27",
|
||||
"vue-echarts": "^6.7.3",
|
||||
"vue-router": "^4.3.2",
|
||||
"@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.26",
|
||||
"vue-echarts": "^8.0.1",
|
||||
"vue-router": "^4.6.4",
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "^2.20.0",
|
||||
"@iconify/json": "^2.2.216",
|
||||
"@iconify/utils": "^2.1.24",
|
||||
"@unocss/eslint-config": "^0.60.4",
|
||||
"@unocss/eslint-plugin": "^0.60.4",
|
||||
"@unocss/preset-rem-to-px": "^0.60.4",
|
||||
"@vitejs/plugin-vue": "^5.0.5",
|
||||
"eslint": "^9.4.0",
|
||||
"eslint-plugin-format": "^0.1.1",
|
||||
"esno": "^4.7.0",
|
||||
"fs-extra": "^11.2.0",
|
||||
"glob": "^10.4.1",
|
||||
"lint-staged": "^15.2.5",
|
||||
"rollup-plugin-visualizer": "^5.12.0",
|
||||
"sass": "^1.77.4",
|
||||
"simple-git-hooks": "^2.11.1",
|
||||
"taze": "^0.13.8",
|
||||
"unocss": "^0.60.4",
|
||||
"unplugin-auto-import": "^0.17.6",
|
||||
"unplugin-vue-components": "^0.27.0",
|
||||
"vite": "^5.2.12",
|
||||
"vite-plugin-router-warn": "^1.0.0",
|
||||
"vite-plugin-vue-devtools": "^7.2.1"
|
||||
"@antfu/eslint-config": "^6.7.3",
|
||||
"@iconify/json": "^2.2.417",
|
||||
"@unocss/eslint-config": "^66.5.10",
|
||||
"@unocss/eslint-plugin": "^66.5.10",
|
||||
"@unocss/preset-rem-to-px": "^66.5.10",
|
||||
"@vitejs/plugin-vue": "^6.0.3",
|
||||
"@vitejs/plugin-vue-jsx": "^5.1.2",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint-plugin-format": "^1.1.0",
|
||||
"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.5.10",
|
||||
"unplugin-auto-import": "^20.3.0",
|
||||
"unplugin-vue-components": "^30.0.0",
|
||||
"vite": "^7.3.0",
|
||||
"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"
|
||||
|
||||
7254
pnpm-lock.yaml
generated
7254
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
11
src/App.vue
11
src/App.vue
@@ -16,12 +16,14 @@
|
||||
>
|
||||
<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 class="fixed right-12 top-1/2 z-999" />
|
||||
<LayoutSetting v-if="layoutSettingVisible" class="fixed right-12 top-1/2 z-999" />
|
||||
</router-view>
|
||||
</n-config-provider>
|
||||
</template>
|
||||
@@ -30,6 +32,7 @@
|
||||
import { darkTheme, dateZhCN, zhCN } from 'naive-ui'
|
||||
import { LayoutSetting } from '@/components'
|
||||
import { useAppStore, useTabStore } from '@/store'
|
||||
import { layoutSettingVisible } from './settings'
|
||||
|
||||
const layouts = new Map()
|
||||
function getLayout(name) {
|
||||
|
||||
@@ -14,7 +14,7 @@ export default {
|
||||
// 刷新token
|
||||
refreshToken: () => request.get('/auth/refresh/token'),
|
||||
// 登出
|
||||
logout: () => request.post('/auth/logout'),
|
||||
logout: () => request.post('/auth/logout', {}, { needTip: false }),
|
||||
// 切换当前角色
|
||||
switchCurrentRole: role => request.post(`/auth/current-role/switch/${role}`),
|
||||
// 获取角色权限
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
- Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
|
||||
--------------------------------->
|
||||
<template>
|
||||
<div class="h-40 w-40 rounded-4 bg-primary p-1/100">
|
||||
<div class="h-32 w-32 rounded-4 bg-primary">
|
||||
<img src="@/assets/images/isme.png" alt="Logo">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -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>
|
||||
|
||||
46
src/components/common/ToggleTheme.vue
Normal file
46
src/components/common/ToggleTheme.vue
Normal file
@@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<i
|
||||
id="toggleTheme"
|
||||
class="mr-16 cursor-pointer"
|
||||
:class="isDark ? 'i-fe:moon' : 'i-fe:sun'"
|
||||
@click="toggleDark"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useDark, useToggle } from '@vueuse/core'
|
||||
import { useAppStore } from '@/store'
|
||||
|
||||
const appStore = useAppStore()
|
||||
const isDark = useDark()
|
||||
async function toggleDark({ clientX, clientY }) {
|
||||
function handler() {
|
||||
appStore.toggleDark()
|
||||
useToggle(isDark)()
|
||||
}
|
||||
|
||||
if (!document.startViewTransition) {
|
||||
return handler()
|
||||
}
|
||||
|
||||
const clipPath = [
|
||||
`circle(0px at ${clientX}px ${clientY}px)`,
|
||||
`circle(${Math.hypot(
|
||||
Math.max(clientX, window.innerWidth - clientX),
|
||||
Math.max(clientY, window.innerHeight - clientY),
|
||||
)}px at ${clientX}px ${clientY}px)`,
|
||||
]
|
||||
|
||||
await document.startViewTransition(handler).ready
|
||||
|
||||
document.documentElement.animate(
|
||||
{ clipPath: isDark.value ? clipPath.reverse() : clipPath },
|
||||
{
|
||||
duration: 500,
|
||||
easing: 'ease-in',
|
||||
pseudoElement: `::view-transition-${isDark.value ? 'old' : 'new'}(root)`,
|
||||
fill: 'both',
|
||||
},
|
||||
)
|
||||
}
|
||||
</script>
|
||||
@@ -1,5 +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'
|
||||
|
||||
@@ -7,35 +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-space wrap :size="[32, 16]">
|
||||
<slot />
|
||||
</n-space>
|
||||
<div class="flex-shrink-0">
|
||||
<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>
|
||||
</div>
|
||||
</form>
|
||||
</AppCard>
|
||||
<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 attr-type="submit" class="ml-20" type="primary">
|
||||
<i class="i-fe:search mr-4" />
|
||||
搜索
|
||||
</n-button>
|
||||
|
||||
<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>
|
||||
@@ -89,13 +106,28 @@ const props = defineProps({
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
/** 是否支持展开 */
|
||||
expand: Boolean,
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:queryItems', 'onChecked', 'onDataChange'])
|
||||
const loading = ref(false)
|
||||
const initQuery = { ...props.queryItems }
|
||||
const tableData = ref([])
|
||||
const pagination = reactive({ page: 1, pageSize: 10 })
|
||||
const pagination = reactive({
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
prefix({ itemCount }) {
|
||||
return `共 ${itemCount} 条数据`
|
||||
},
|
||||
})
|
||||
|
||||
// 是否展开
|
||||
const isExpanded = ref(false)
|
||||
|
||||
function toggleExpand() {
|
||||
isExpanded.value = !isExpanded.value
|
||||
}
|
||||
|
||||
async function handleQuery() {
|
||||
try {
|
||||
@@ -111,8 +143,13 @@ async function handleQuery() {
|
||||
})
|
||||
tableData.value = data?.pageData || data
|
||||
pagination.itemCount = data.total ?? data.length
|
||||
if (pagination.itemCount && !tableData.value.length && pagination.page > 1) {
|
||||
// 如果当前页数据为空,且总条数不为0,则返回上一页数据
|
||||
onPageChange(pagination.page - 1)
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
tableData.value = []
|
||||
pagination.itemCount = 0
|
||||
}
|
||||
@@ -121,9 +158,14 @@ async function handleQuery() {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
function handleSearch() {
|
||||
pagination.page = 1
|
||||
handleQuery()
|
||||
|
||||
function handleSearch(keepCurrentPage = false) {
|
||||
if (keepCurrentPage || !props.remote) {
|
||||
handleQuery()
|
||||
}
|
||||
else {
|
||||
onPageChange(1)
|
||||
}
|
||||
}
|
||||
async function handleReset() {
|
||||
const queryItems = { ...props.queryItems }
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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>
|
||||
@@ -144,11 +145,12 @@ async function handleOk(data) {
|
||||
// 调用onOk函数,传入data参数
|
||||
const res = await modalOptions.value.onOk(data)
|
||||
// 如果onOk函数的返回值不为false,则关闭模态框
|
||||
res !== false && close()
|
||||
if (res !== false)
|
||||
close()
|
||||
}
|
||||
catch (error) {
|
||||
okLoading.value = false
|
||||
console.error(error)
|
||||
okLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,11 +165,12 @@ async function handleCancel(data) {
|
||||
const res = await modalOptions.value.onCancel(data)
|
||||
|
||||
// 如果onCancel函数的返回值不为false,则关闭模态框
|
||||
res !== false && close()
|
||||
if (res !== false)
|
||||
close()
|
||||
}
|
||||
catch (error) {
|
||||
okLoading.value = false
|
||||
console.error(error)
|
||||
okLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export * from './useAliveData'
|
||||
export * from './useCrud'
|
||||
export * from './useForm'
|
||||
export * from './useModal'
|
||||
export * from './useAliveData'
|
||||
|
||||
@@ -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)) })
|
||||
}
|
||||
|
||||
/** 修改 */
|
||||
@@ -80,6 +81,7 @@ export function useCrud({ name, initForm = {}, doCreate, doDelete, doUpdate, ref
|
||||
data && refresh(data)
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
okLoading.value = false
|
||||
return false
|
||||
}
|
||||
@@ -100,9 +102,10 @@ export function useCrud({ name, initForm = {}, doCreate, doDelete, doUpdate, ref
|
||||
const data = await doDelete(id)
|
||||
$message.success('删除成功')
|
||||
d.loading = false
|
||||
refresh(data)
|
||||
refresh(data, true)
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
d.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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]])
|
||||
}
|
||||
|
||||
120
src/layouts/components/BeginnerGuide.vue
Normal file
120
src/layouts/components/BeginnerGuide.vue
Normal 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>
|
||||
14
src/layouts/components/Fullscreen.vue
Normal file
14
src/layouts/components/Fullscreen.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<i
|
||||
id="fullscreen"
|
||||
class="mr-16 cursor-pointer"
|
||||
:class="isFullscreen ? 'i-fe:minimize' : 'i-fe:maximize'"
|
||||
@click="toggle"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useFullscreen } from '@vueuse/core'
|
||||
|
||||
const { isFullscreen, toggle } = useFullscreen()
|
||||
</script>
|
||||
@@ -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'" />
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -1,7 +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 AppTab } from './tab/index.vue'
|
||||
export { default as UserAvatar } from './UserAvatar.vue'
|
||||
|
||||
@@ -119,7 +119,8 @@ function handleHideDropdown() {
|
||||
|
||||
function handleSelect(key) {
|
||||
const actionFn = actionMap.get(key)
|
||||
actionFn && actionFn()
|
||||
if (typeof actionFn === 'function')
|
||||
actionFn()
|
||||
handleHideDropdown()
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -13,16 +13,11 @@
|
||||
<BreadCrumb />
|
||||
|
||||
<div class="ml-auto flex flex-shrink-0 items-center px-12 text-18">
|
||||
<i
|
||||
class="mr-16 cursor-pointer"
|
||||
:class="isDark ? 'i-fe:moon' : 'i-fe:sun'"
|
||||
@click="toggleDark"
|
||||
/>
|
||||
<i
|
||||
class="mr-16 cursor-pointer"
|
||||
:class="isFullscreen ? 'i-fe:minimize' : 'i-fe:maximize'"
|
||||
@click="toggle"
|
||||
/>
|
||||
<BeginnerGuide />
|
||||
|
||||
<ToggleTheme />
|
||||
|
||||
<Fullscreen />
|
||||
|
||||
<i
|
||||
class="i-fe:github mr-16 cursor-pointer"
|
||||
@@ -41,18 +36,8 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useDark, useFullscreen, useToggle } from '@vueuse/core'
|
||||
import { BreadCrumb, MenuCollapse, UserAvatar } from '@/layouts/components'
|
||||
import { useAppStore } from '@/store'
|
||||
|
||||
const appStore = useAppStore()
|
||||
const isDark = useDark()
|
||||
function toggleDark() {
|
||||
appStore.toggleDark()
|
||||
useToggle(isDark)()
|
||||
}
|
||||
|
||||
const { isFullscreen, toggle } = useFullscreen()
|
||||
import { ToggleTheme } from '@/components'
|
||||
import { BeginnerGuide, BreadCrumb, Fullscreen, MenuCollapse, UserAvatar } from '@/layouts/components'
|
||||
|
||||
function handleLinkClick(link) {
|
||||
window.open(link)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -15,16 +15,11 @@
|
||||
<span class="mx-6 opacity-20">|</span>
|
||||
|
||||
<div class="flex flex-shrink-0 items-center px-12 text-18">
|
||||
<i
|
||||
class="mr-16 cursor-pointer"
|
||||
:class="isDark ? 'i-fe:moon' : 'i-fe:sun'"
|
||||
@click="toggleDark"
|
||||
/>
|
||||
<i
|
||||
class="mr-16 cursor-pointer"
|
||||
:class="isFullscreen ? 'i-fe:minimize' : 'i-fe:maximize'"
|
||||
@click="toggle"
|
||||
/>
|
||||
<BeginnerGuide />
|
||||
|
||||
<ToggleTheme />
|
||||
|
||||
<Fullscreen />
|
||||
|
||||
<i
|
||||
class="i-fe:github mr-16 cursor-pointer"
|
||||
@@ -43,24 +38,10 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useDark, useFullscreen, useToggle } from '@vueuse/core'
|
||||
import { AppTab, MenuCollapse, UserAvatar } from '@/layouts/components'
|
||||
import { useAppStore } from '@/store'
|
||||
|
||||
const appStore = useAppStore()
|
||||
const isDark = useDark()
|
||||
function toggleDark() {
|
||||
appStore.toggleDark()
|
||||
useToggle(isDark)()
|
||||
}
|
||||
|
||||
const { isFullscreen, toggle } = useFullscreen()
|
||||
import { ToggleTheme } from '@/components'
|
||||
import { AppTab, BeginnerGuide, Fullscreen, MenuCollapse, UserAvatar } from '@/layouts/components'
|
||||
|
||||
function handleLinkClick(link) {
|
||||
window.open(link)
|
||||
}
|
||||
|
||||
watchEffect(() => {
|
||||
appStore.setThemeColor(appStore.primaryColor, appStore.isDark)
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
10
src/main.js
10
src/main.js
@@ -8,16 +8,16 @@
|
||||
* Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
|
||||
**********************************/
|
||||
|
||||
import '@/styles/reset.css'
|
||||
import '@/styles/global.scss'
|
||||
import 'uno.css'
|
||||
|
||||
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)
|
||||
|
||||
@@ -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']
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -10,6 +10,9 @@ export const defaultLayout = 'normal'
|
||||
|
||||
export const defaultPrimaryColor = '#316C72'
|
||||
|
||||
// 控制 LayoutSetting 组件是否可见
|
||||
export const layoutSettingVisible = true
|
||||
|
||||
export const naiveThemeOverrides = {
|
||||
common: {
|
||||
primaryColor: '#316C72FF',
|
||||
@@ -33,7 +36,7 @@ export const basePermissions = [
|
||||
code: 'ShowDocs',
|
||||
name: '项目文档',
|
||||
type: 'MENU',
|
||||
path: 'https://docs.isme.top/web/#/624306705/188522224',
|
||||
path: 'https://isme.top',
|
||||
icon: 'i-me:docs',
|
||||
order: 1,
|
||||
enable: true,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -88,7 +88,7 @@ export const useTabStore = defineStore('tab', {
|
||||
},
|
||||
},
|
||||
persist: {
|
||||
paths: ['tabs'],
|
||||
pick: ['tabs'],
|
||||
storage: sessionStorage,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -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%);
|
||||
}
|
||||
|
||||
/* 自定义滚动条样式 */
|
||||
@@ -72,3 +72,20 @@ body {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 切换主题的动画效果 */
|
||||
::view-transition-old(root),
|
||||
::view-transition-new(root) {
|
||||
animation: none;
|
||||
mix-blend-mode: normal;
|
||||
}
|
||||
|
||||
::view-transition-old(root),
|
||||
.dark::view-transition-new(root) {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
::view-transition-new(root),
|
||||
.dark::view-transition-old(root) {
|
||||
z-index: 9999;
|
||||
}
|
||||
@@ -1,49 +1,389 @@
|
||||
/**********************************
|
||||
* @Author: Ronnie Zhang
|
||||
* @LastEditor: Ronnie Zhang
|
||||
* @LastEditTime: 2023/12/05 21:26:38
|
||||
* @Email: zclzone@outlook.com
|
||||
* Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
|
||||
**********************************/
|
||||
|
||||
html {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
/*
|
||||
1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
|
||||
2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
|
||||
2. [UnoCSS]: allow to override the default border color with css var `--un-default-border-color`
|
||||
*/
|
||||
|
||||
*,
|
||||
::before,
|
||||
::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: inherit;
|
||||
box-sizing: border-box; /* 1 */
|
||||
border-width: 0; /* 2 */
|
||||
border-style: solid; /* 2 */
|
||||
border-color: var(--un-default-border-color, #e5e7eb); /* 2 */
|
||||
}
|
||||
|
||||
::before,
|
||||
::after {
|
||||
--un-content: '';
|
||||
}
|
||||
|
||||
/*
|
||||
1. Use a consistent sensible line-height in all browsers.
|
||||
2. Prevent adjustments of font size after orientation changes in iOS.
|
||||
3. Use a more readable tab size.
|
||||
4. Use the user's configured `sans` font-family by default.
|
||||
5. Use the user's configured `sans` font-feature-settings by default.
|
||||
6. Use the user's configured `sans` font-variation-settings by default.
|
||||
7. Disable tap highlights on iOS.
|
||||
*/
|
||||
|
||||
html,
|
||||
:host {
|
||||
line-height: 1.5; /* 1 */
|
||||
-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-feature-settings: normal; /* 5 */
|
||||
font-variation-settings: normal; /* 6 */
|
||||
-webkit-tap-highlight-color: transparent; /* 7 */
|
||||
}
|
||||
|
||||
/*
|
||||
1. Remove the margin in all browsers.
|
||||
2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0; /* 1 */
|
||||
line-height: inherit; /* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
1. Add the correct height in Firefox.
|
||||
2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
|
||||
3. Ensure horizontal rules are visible by default.
|
||||
*/
|
||||
|
||||
hr {
|
||||
height: 0; /* 1 */
|
||||
color: inherit; /* 2 */
|
||||
border-top-width: 1px; /* 3 */
|
||||
}
|
||||
|
||||
/*
|
||||
Add the correct text decoration in Chrome, Edge, and Safari.
|
||||
*/
|
||||
|
||||
abbr:where([title]) {
|
||||
text-decoration: underline dotted;
|
||||
}
|
||||
|
||||
/*
|
||||
Remove the default font size and weight for headings.
|
||||
*/
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-size: inherit;
|
||||
font-weight: inherit;
|
||||
}
|
||||
|
||||
/*
|
||||
Reset links to optimize for opt-in styling instead of opt-out.
|
||||
*/
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
|
||||
a:hover,
|
||||
a:link,
|
||||
a:visited,
|
||||
a:active {
|
||||
text-decoration: none;
|
||||
/*
|
||||
Add the correct font weight in Edge and Safari.
|
||||
*/
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Use the user's configured `mono` font-family by default.
|
||||
2. Use the user's configured `mono` font-feature-settings by default.
|
||||
3. Use the user's configured `mono` font-variation-settings by default.
|
||||
4. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
code,
|
||||
kbd,
|
||||
samp,
|
||||
pre {
|
||||
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 */
|
||||
}
|
||||
|
||||
/*
|
||||
Add the correct font size in all browsers.
|
||||
*/
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
/*
|
||||
Prevent `sub` and `sup` elements from affecting the line height in all browsers.
|
||||
*/
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
|
||||
2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
|
||||
3. Remove gaps between table borders by default.
|
||||
*/
|
||||
|
||||
table {
|
||||
text-indent: 0; /* 1 */
|
||||
border-color: inherit; /* 2 */
|
||||
border-collapse: collapse; /* 3 */
|
||||
}
|
||||
|
||||
/*
|
||||
1. Change the font styles in all browsers.
|
||||
2. Remove the margin in Firefox and Safari.
|
||||
3. Remove default padding in all browsers.
|
||||
*/
|
||||
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
font-family: inherit; /* 1 */
|
||||
font-feature-settings: inherit; /* 1 */
|
||||
font-variation-settings: inherit; /* 1 */
|
||||
font-size: 100%; /* 1 */
|
||||
font-weight: inherit; /* 1 */
|
||||
line-height: inherit; /* 1 */
|
||||
color: inherit; /* 1 */
|
||||
margin: 0; /* 2 */
|
||||
padding: 0; /* 3 */
|
||||
}
|
||||
|
||||
/*
|
||||
Remove the inheritance of text transform in Edge and Firefox.
|
||||
*/
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Correct the inability to style clickable types in iOS and Safari.
|
||||
2. Remove default button styles.
|
||||
*/
|
||||
|
||||
button,
|
||||
[type='button'],
|
||||
[type='reset'],
|
||||
[type='submit'] {
|
||||
-webkit-appearance: button; /* 1 */
|
||||
/* background-color: transparent; */
|
||||
background-image: none; /* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
Use the modern Firefox focus style for all focusable elements.
|
||||
*/
|
||||
|
||||
:-moz-focusring {
|
||||
outline: auto;
|
||||
}
|
||||
|
||||
/*
|
||||
Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
|
||||
*/
|
||||
|
||||
:-moz-ui-invalid {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/*
|
||||
Add the correct vertical alignment in Chrome and Firefox.
|
||||
*/
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
/*
|
||||
Correct the cursor style of increment and decrement buttons in Safari.
|
||||
*/
|
||||
|
||||
::-webkit-inner-spin-button,
|
||||
::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Correct the odd appearance in Chrome and Safari.
|
||||
2. Correct the outline style in Safari.
|
||||
*/
|
||||
|
||||
[type='search'] {
|
||||
-webkit-appearance: textfield; /* 1 */
|
||||
outline-offset: -2px; /* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
Remove the inner padding in Chrome and Safari on macOS.
|
||||
*/
|
||||
|
||||
::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Correct the inability to style clickable types in iOS and Safari.
|
||||
2. Change font properties to `inherit` in Safari.
|
||||
*/
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
-webkit-appearance: button; /* 1 */
|
||||
font: inherit; /* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
Add the correct display in Chrome and Safari.
|
||||
*/
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
/*
|
||||
Removes the default spacing for appropriate elements.
|
||||
*/
|
||||
|
||||
blockquote,
|
||||
dl,
|
||||
dd,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
hr,
|
||||
figure,
|
||||
p,
|
||||
pre {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
legend {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul {
|
||||
ul,
|
||||
menu {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
input,
|
||||
textarea {
|
||||
outline: none;
|
||||
border: none;
|
||||
resize: none;
|
||||
dialog {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
Prevent resizing textareas horizontally by default.
|
||||
*/
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
|
||||
2. Set the default placeholder color to the user's configured gray 400 color.
|
||||
*/
|
||||
|
||||
input::placeholder,
|
||||
textarea::placeholder {
|
||||
opacity: 1; /* 1 */
|
||||
color: #9ca3af; /* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
Set the default cursor for buttons.
|
||||
*/
|
||||
|
||||
button,
|
||||
[role='button'] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/*
|
||||
Make sure disabled buttons don't get the pointer cursor.
|
||||
*/
|
||||
|
||||
:disabled {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
|
||||
2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
|
||||
This can trigger a poorly considered lint error in some tools but is included by design.
|
||||
*/
|
||||
|
||||
img,
|
||||
svg,
|
||||
video,
|
||||
canvas,
|
||||
audio,
|
||||
iframe,
|
||||
embed,
|
||||
object {
|
||||
display: block; /* 1 */
|
||||
vertical-align: middle; /* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
|
||||
*/
|
||||
|
||||
img,
|
||||
video {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/*
|
||||
Make elements with the HTML hidden attribute stay hidden by default.
|
||||
*/
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -10,45 +10,34 @@
|
||||
import { useAuthStore } from '@/store'
|
||||
|
||||
let isConfirming = false
|
||||
export function resolveResError(code, message) {
|
||||
|
||||
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)
|
||||
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)
|
||||
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
|
||||
@@ -62,5 +51,6 @@ export function resolveResError(code, message) {
|
||||
message = message ?? `【${code}】: 未知异常!`
|
||||
break
|
||||
}
|
||||
needTip && window.$message?.error(message)
|
||||
return message
|
||||
}
|
||||
|
||||
@@ -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.noNeedToken) {
|
||||
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
|
||||
@@ -39,34 +20,51 @@ export function setupInterceptors(axiosInstance) {
|
||||
}
|
||||
const code = data?.code ?? status
|
||||
|
||||
// 根据code处理对应的操作,并返回处理后的message
|
||||
const message = resolveResError(code, data?.message ?? statusText)
|
||||
const needTip = config?.needTip !== false
|
||||
|
||||
// 根据code处理对应的操作,并返回处理后的message
|
||||
const message = resolveResError(code, data?.message ?? statusText, needTip)
|
||||
|
||||
// 需要错误提醒
|
||||
!config?.noNeedTip && message && window.$message?.error(message)
|
||||
return Promise.reject({ code, message, error: data ?? response })
|
||||
}
|
||||
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)
|
||||
window.$message?.error(message)
|
||||
return Promise.reject({ code, message, error })
|
||||
}
|
||||
|
||||
const { data, status, config } = error.response
|
||||
const code = data?.code ?? status
|
||||
|
||||
const message = resolveResError(code, data?.message ?? error.message)
|
||||
/** 需要错误提醒 */
|
||||
!config?.noNeedTip && message && window.$message?.error(message)
|
||||
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 })
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -47,6 +47,7 @@ class Storage {
|
||||
return def
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
this.remove(key)
|
||||
return def
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@
|
||||
<script setup>
|
||||
import { sleep } from '@/utils'
|
||||
|
||||
const handleDelete = function () {
|
||||
function handleDelete() {
|
||||
$dialog.confirm({
|
||||
content: '确认删除?',
|
||||
confirm() {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -9,15 +9,24 @@
|
||||
<template>
|
||||
<CommonPage show-footer>
|
||||
<template #title-suffix>
|
||||
<n-tag class="ml-12" type="primary">
|
||||
feather图标集 + isme自定义图标
|
||||
</n-tag>
|
||||
<n-tooltip
|
||||
placement="bottom"
|
||||
trigger="hover"
|
||||
>
|
||||
<template #trigger>
|
||||
<a href="https://juejin.cn/post/7394789388154241033" target="_blank" class="ml-12 flex cursor-pointer items-center hover:underline">
|
||||
<i class="i-simple-icons:juejin text-#1E80FF" />
|
||||
<span class="ml-4">Unocss 图标</span>
|
||||
</a>
|
||||
</template>
|
||||
点击查看如何使用 Unocss 图标
|
||||
</n-tooltip>
|
||||
</template>
|
||||
<ul class="flex flex-wrap justify-between">
|
||||
<ul class="grid grid-cols-[repeat(auto-fill,minmax(160px,1fr))] justify-items-center gap-16">
|
||||
<li
|
||||
v-for="item in icons"
|
||||
:key="item"
|
||||
class="m-16 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(`<i class="${item}" />`)"
|
||||
>
|
||||
<i :class="`${item}?mask`" class="text-28 text-gray-600 hover:bg-primary" />
|
||||
@@ -28,13 +37,6 @@
|
||||
{{ item }}
|
||||
</span>
|
||||
</li>
|
||||
<li class="mx-16 h-0 w-160" />
|
||||
<li class="mx-16 h-0 w-160" />
|
||||
<li class="mx-16 h-0 w-160" />
|
||||
<li class="mx-16 h-0 w-160" />
|
||||
<li class="mx-16 h-0 w-160" />
|
||||
<li class="mx-16 h-0 w-160" />
|
||||
<li class="mx-16 h-0 w-160" />
|
||||
</ul>
|
||||
</CommonPage>
|
||||
</template>
|
||||
@@ -46,6 +48,7 @@ import icons from 'isme:icons'
|
||||
const { copy, copied } = useClipboard()
|
||||
|
||||
watch(copied, (val) => {
|
||||
val && $message.success('已复制到剪切板')
|
||||
if (val)
|
||||
$message.success('已复制到剪切板')
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -62,15 +62,11 @@ 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) => {
|
||||
val && $message.success('已复制到剪切板')
|
||||
if (val)
|
||||
$message.success('已复制到剪切板')
|
||||
})
|
||||
|
||||
function onBeforeUpload({ 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>
|
||||
@@ -49,7 +48,7 @@
|
||||
type="primary"
|
||||
ghost
|
||||
tag="a"
|
||||
href="https://docs.isme.top/web/#/624306705/188522224"
|
||||
href="https://isme.top"
|
||||
target="__blank"
|
||||
>
|
||||
开发文档
|
||||
@@ -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>
|
||||
|
||||
@@ -10,6 +10,6 @@ import { request } from '@/utils'
|
||||
|
||||
export default {
|
||||
toggleRole: data => request.post('/auth/role/toggle', data),
|
||||
login: data => request.post('/auth/login', data, { noNeedToken: true }),
|
||||
login: data => request.post('/auth/login', data, { needToken: false }),
|
||||
getUser: () => request.get('/user/detail'),
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -28,8 +28,8 @@
|
||||
:on-update:selected-keys="onSelect"
|
||||
key-field="code"
|
||||
label-field="name"
|
||||
default-expand-all
|
||||
block-line
|
||||
|
||||
block-line default-expand-all
|
||||
/>
|
||||
</n-space>
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -115,6 +115,7 @@ function handleDelete(item) {
|
||||
emit('update:currentMenu', null)
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
$message.destroy('deleteMenu')
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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)
|
||||
@@ -231,6 +231,7 @@ function handleDeleteBtn(id) {
|
||||
d.loading = false
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
d.loading = false
|
||||
}
|
||||
},
|
||||
@@ -248,6 +249,7 @@ async function handleEnable(item) {
|
||||
item.enableLoading = false
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
item.enableLoading = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,9 +74,8 @@
|
||||
:data="permissionTree"
|
||||
:checked-keys="modalForm.permissionIds"
|
||||
:on-update:checked-keys="(keys) => (modalForm.permissionIds = keys)"
|
||||
checkable
|
||||
check-on-click
|
||||
default-expand-all
|
||||
|
||||
checkable check-on-click default-expand-all
|
||||
class="cus-scroll max-h-200 w-full"
|
||||
/>
|
||||
</n-form-item>
|
||||
@@ -97,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' })
|
||||
|
||||
@@ -120,7 +119,7 @@ const { modalRef, modalFormRef, modalAction, modalForm, handleAdd, handleDelete,
|
||||
doDelete: api.delete,
|
||||
doUpdate: api.update,
|
||||
initForm: { enable: true },
|
||||
refresh: () => $table.value?.handleSearch(),
|
||||
refresh: (_, keepCurrentPage) => $table.value?.handleSearch(keepCurrentPage),
|
||||
})
|
||||
|
||||
const columns = [
|
||||
@@ -211,6 +210,7 @@ async function handleEnable(row) {
|
||||
$table.value?.handleSearch()
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
row.enableLoading = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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' }),
|
||||
},
|
||||
)
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
@@ -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),
|
||||
},
|
||||
@@ -279,6 +292,7 @@ async function handleEnable(row) {
|
||||
$table.value?.handleSearch()
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
row.enableLoading = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -6,21 +6,23 @@
|
||||
* 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,
|
||||
prefix: ['i-'],
|
||||
extraProperties: {
|
||||
display: 'inline-block',
|
||||
width: '1em',
|
||||
height: '1em',
|
||||
},
|
||||
collections: {
|
||||
me: FileSystemIconLoader('./src/assets/icons/isme'),
|
||||
|
||||
@@ -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)
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user