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

32 Commits

Author SHA1 Message Date
zclzone
fd9480e92f fix: logo 宽高异常 2024-03-12 09:38:08 +08:00
大脸怪
0520cd015a Merge pull request #73 from hanfengcan/2.x
feat: 增加必要的校验提示
2024-03-11 21:54:59 +08:00
hanfengcan
f5a26c32e9 U:增加必要的校验提示 2024-03-10 18:21:04 +08:00
zclzone
886ef9e11c feat: logo背景色根据主题色自动调整 2024-03-04 16:33:35 +08:00
zclzone
7d0a17b2b5 chore: update deps, close #I93HBV 2024-02-29 17:23:00 +08:00
zclzone
fa4967efc3 chore: 修改eslint设配置 2024-02-29 17:01:43 +08:00
zclzone
207150623e fix: 修复创建路由没有考虑 base 路径问题, close #70 2024-02-21 10:04:21 +08:00
zclzone
6981692c54 mod: appStore持久化存储改用sessionStorage 2024-02-21 09:06:04 +08:00
zclzone
b64e1c7595 chore: 移除 vite-plugin-vue-devtools 2024-02-11 17:00:25 +08:00
zclzone
efcbe3bea4 mod: 移除多余图标 2024-02-11 16:59:27 +08:00
zclzone
2c808c6d8b chore: 修改npm淘宝镜像地址 2024-02-01 11:19:32 +08:00
zclzone
447db11c52 chore: update eslintConfig 2024-02-01 10:24:52 +08:00
zclzone
a3d0e863cc fix: 修复本地代理apiFox云端mock接口不通问题 2024-01-30 22:51:50 +08:00
zclzone
a2827e4c0d Merge branch '2.x' of https://gitee.com/isme-admin/vue-naive-admin into 2.x 2024-01-25 15:54:05 +08:00
zclzone
f888c2fbfd fix: 移除跳转role-select页面逻辑 2024-01-25 15:53:35 +08:00
zclzone
1c37a38b92 chore: update deps 2024-01-21 18:06:25 +08:00
zclzone
f2eb40357d fix: submit导致reload问题 2024-01-21 18:01:05 +08:00
zclzone
58de3c1ad6 perf: 优化 crud query 2024-01-21 17:52:50 +08:00
zclzone
567e306a5c Merge branch '2.x' of https://github.com/zclzone/vue-naive-admin into 2.x 2024-01-21 17:26:00 +08:00
zclzone
065c6b50c6 fix: enter -> handleQuery 2024-01-21 16:53:43 +08:00
大脸怪
95a1ef654c !2 update src/views/pms/user/index.vue.
Merge pull request !2 from aiden/N/A
2024-01-21 08:51:51 +00:00
大脸怪
346da8772a Update README.md 2024-01-13 18:18:44 +08:00
zclzone
a6773cbfec feat: 增加MeModal拖拽功能 2024-01-13 17:43:48 +08:00
aiden
4c337d3aa8 update src/views/pms/user/index.vue.
修正用户管理回车不执行搜索的bug

Signed-off-by: aiden <792000767@qq.com>
2024-01-08 11:33:47 +00:00
zclzone
2c9604a829 fix: 解决vite5循环引用导致热更新失效问题 2024-01-06 18:47:40 +08:00
大脸怪
c4fd0459ab Update README.md 2024-01-04 09:12:51 +08:00
zclzone
aa92455dbb chore: update deps about vite & vue 2024-01-02 22:29:01 +08:00
zclzone
857381471e chore: update deps about unocss 2024-01-02 22:04:38 +08:00
zclzone
b780113f18 fix: message判空 2023-12-29 15:59:04 +08:00
zclzone
fe4bbded53 fix: 重置空字符 -> null 2023-12-28 17:09:12 +08:00
zclzone
d801cf28cc fix: 报错未及时清除loading message问题 2023-12-23 20:31:52 +08:00
大脸怪
334174d442 Update README.md 2023-12-21 14:26:12 +08:00
38 changed files with 2544 additions and 4484 deletions

2
.npmrc
View File

@@ -1,2 +1,2 @@
registry=https://registry.npm.taobao.org
registry=https://registry.npmmirror.com
shamefully-hoist=true

View File

@@ -18,5 +18,8 @@
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"eslint.validate": ["javascript", "typescript", "javascriptreact", "typescriptreact", "vue"]
"eslint.validate": ["javascript", "typescript", "javascriptreact", "typescriptreact", "vue"],
"eslint.options": {
"overrideConfigFile": "package.json"
}
}

View File

@@ -12,7 +12,7 @@ Vue Naive Admin 是一款极简风格的后台管理模板,包含前后端解
## 设计理念
Vue Naive Admin 2022年2月开始开源从 1.0 到现在的 2.0,一直秉持着`简单即正义`的理念,旨在帮助中小企业、在校大学生及个人开发者快速上手开发后台管理项目,为了降低使用者的学习成本,没有使用看似主流的 TypeScript前端这也使得 Vue Naive Admin 成为了市面上少有的 `使用 JavaScript 的 Vue3 后台管理模板`,而且还算优秀,得到了大量朋友的认可和喜爱,截至 2023-11-17 github `1.1k+` stargitee `260+` star
Vue Naive Admin 2022年2月开始开源从 1.0 到现在的 2.0,一直秉持着`简单即正义`的理念,旨在帮助中小企业、在校大学生及个人开发者快速上手开发后台管理项目,为了降低使用者的学习成本,没有使用看似主流的 TypeScript前端这也使得 Vue Naive Admin 成为了市面上少有的 `使用 JavaScript 的 Vue3 后台管理模板`,而且还算优秀,得到了大量朋友的认可和喜爱。
## 特性
@@ -52,6 +52,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)
- 接口文档: [apidoc | isme-nest-serve](https://apifox.com/apidoc/shared-ff4a4d32-c0d1-4caf-b0ee-6abc130f734a)
## 版权说明
@@ -64,4 +65,5 @@ 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。

View File

@@ -1,4 +1,4 @@
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
@@ -11,7 +11,7 @@
<div id="app">
<!-- 白屏时的loading效果 -->
<div class="loading-container">
<img src="/resource/logo.png" alt="logo" height="128" />
<img src="/resource/logo.png" alt="logo" />
<div class="loading-spin__container">
<div class="loading-spin">
<div class="left-0 top-0 loading-spin-item"></div>

View File

@@ -10,37 +10,36 @@
"lint:fix": "eslint --fix --ext .js,.vue ."
},
"dependencies": {
"@iconify/json": "^2.2.129",
"@iconify/utils": "^2.1.11",
"@unocss/eslint-config": "^0.58.0",
"@unocss/preset-rem-to-px": "^0.58.0",
"@vueuse/core": "^10.5.0",
"axios": "^1.5.1",
"@vueuse/core": "^10.9.0",
"axios": "^1.6.7",
"dayjs": "^1.11.10",
"echarts": "^5.4.3",
"echarts": "^5.5.0",
"lodash-es": "^4.17.21",
"naive-ui": "^2.35.0",
"naive-ui": "^2.38.1",
"pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^3.2.0",
"sass": "^1.69.3",
"unocss": "^0.58.0",
"vue": "^3.3.11",
"vue-echarts": "^6.6.1",
"vue-router": "^4.2.5",
"pinia-plugin-persistedstate": "^3.2.1",
"vue": "^3.4.21",
"vue-echarts": "^6.6.9",
"vue-router": "^4.3.0",
"xlsx": "^0.18.5"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.5.2",
"@iconify/json": "^2.2.188",
"@iconify/utils": "^2.1.22",
"@unocss/eslint-config": "^0.58.5",
"@unocss/preset-rem-to-px": "^0.58.5",
"@vitejs/plugin-vue": "^5.0.4",
"@zclzone/eslint-config": "^0.0.5",
"esno": "^0.17.0",
"fs-extra": "^11.1.1",
"esno": "^4.0.0",
"fs-extra": "^11.2.0",
"glob": "^10.3.10",
"rollup-plugin-visualizer": "^5.9.2",
"unplugin-auto-import": "^0.16.6",
"unplugin-vue-components": "^0.25.2",
"vite": "^5.0.7",
"vite-plugin-simple-html": "^0.1.1",
"vite-plugin-vue-devtools": "1.0.0-rc.7"
"rollup-plugin-visualizer": "^5.12.0",
"sass": "^1.71.1",
"unocss": "^0.58.5",
"unplugin-auto-import": "^0.17.5",
"unplugin-vue-components": "^0.26.0",
"vite": "^5.1.4",
"vite-plugin-simple-html": "^0.1.2"
},
"eslintConfig": {
"extends": [
@@ -48,13 +47,10 @@
"@unocss",
".eslint-global-variables.json"
],
"rules": {
"no-unused-vars": [
"error",
{
"varsIgnorePattern": "^_"
}
]
"parserOptions": {
"ecmaFeatures": {
"jsx": true
}
}
}
}

6396
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,21 +0,0 @@
<svg
t="1702480351321"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="11122"
width="200"
height="200"
>
<path
d="M509.9008 519.8336m-450.816 0a450.816 450.816 0 1 0 901.632 0 450.816 450.816 0 1 0-901.632 0Z"
fill="#C65EDB"
p-id="11123"
></path>
<path
d="M798.1568 512.512l-113.3056-78.3872a47.4112 47.4112 0 0 1-20.4288-39.0656l0.3584-137.7792c0.1024-39.2704-44.9024-61.5936-76.0832-37.7856l-109.568 83.5072a47.2832 47.2832 0 0 1-43.4688 7.3216l-130.9184-42.9056c-37.3248-12.2368-72.448 23.6544-59.4432 60.7232l45.568 129.9968A47.3088 47.3088 0 0 1 284.416 501.76l-81.2544 111.2576c-23.1424 31.6928 0.1024 76.2368 39.3728 75.3152l137.728-3.1744a47.3344 47.3344 0 0 1 39.4752 19.6096l80.6912 111.6672c22.9888 31.8464 72.4992 23.4496 83.7632-14.1824l37.9392-126.6176 126.5664 118.272a27.648 27.648 0 0 0 17.7664 7.4752c7.8848 0.3584 15.872-2.6112 21.7088-8.8064a27.91936 27.91936 0 0 0-1.3312-39.4752l-124.8768-116.6848 123.8016-39.8848c37.376-11.9808 44.6976-61.6448 12.3904-84.0192z m-389.6832-6.5024l-42.8032 77.824a27.86816 27.86816 0 0 1-37.9392 11.008 27.93984 27.93984 0 0 1-11.008-37.9392l40.0384-72.7552-19.0464-63.6928c-4.4032-14.7968 3.9936-30.3616 18.7392-34.7648 14.7456-4.4032 30.3616 3.9936 34.7648 18.7392l20.6848 69.2224c3.2256 10.752 1.9968 22.528-3.4304 32.3584z"
fill="#FFFFFF"
p-id="11124"
></path>
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

BIN
src/assets/images/isme.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,12 @@
<!--------------------------------
- @Author: Ronnie Zhang
- @LastEditor: Ronnie Zhang
- @LastEditTime: 2024/03/04 16:09:47
- @Email: zclzone@outlook.com
- Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
--------------------------------->
<template>
<div class="h-40 w-40 rounded-full bg-primary p-1/100">
<img src="@/assets/images/isme.png" alt="Logo" />
</div>
</template>

View File

@@ -7,25 +7,22 @@
--------------------------------->
<template>
<AppCard
v-if="$slots.default"
bordered
bg="#fafafc dark:black"
class="mb-30 min-h-60 flex justify-between rounded-4 p-16"
>
<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 class="ml-20" type="primary" @click="handleSearch">
<i class="i-fe:search mr-4" />
搜索
</n-button>
</div>
<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>
<n-data-table
@@ -129,7 +126,7 @@ function handleSearch() {
async function handleReset() {
const queryItems = { ...props.queryItems }
for (const key in queryItems) {
queryItems[key] = ''
queryItems[key] = null
}
emit('update:queryItems', { ...queryItems, ...initQuery })
await nextTick()

View File

@@ -1,7 +1,7 @@
<!--------------------------------
- @Author: Ronnie Zhang
- @LastEditor: Ronnie Zhang
- @LastEditTime: 2023/12/16 18:50:02
- @LastEditTime: 2024/01/13 17:41:38
- @Email: zclzone@outlook.com
- Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
--------------------------------->
@@ -9,17 +9,17 @@
<template>
<n-modal
v-model:show="show"
class="modal-box"
:style="{ width: modalOptions.width, ...modalOptions.modalStyle }"
:preset="undefined"
size="huge"
:bordered="false"
@after-leave="onAfterLeave"
>
<n-card
:title="modalOptions.title"
:style="modalOptions.contentStyle"
:closable="modalOptions.closable"
@close="close()"
>
<n-card :style="modalOptions.contentStyle" :closable="modalOptions.closable" @close="close()">
<template #header>
<header class="modal-header">{{ modalOptions.title }}</header>
</template>
<slot></slot>
<!-- 底部按钮 -->
@@ -45,6 +45,7 @@
</template>
<script setup>
import { initDrag } from './utils'
const props = defineProps({
width: {
type: String,
@@ -101,12 +102,17 @@ const show = ref(false)
const modalOptions = ref({})
// 打开模态框
function open(options = {}) {
async function open(options = {}) {
// 将props和options合并赋值给modalOptions
modalOptions.value = { ...props, ...options }
// 将show的值设置为true
show.value = true
await nextTick()
initDrag(
Array.prototype.at.call(document.querySelectorAll('.modal-header'), -1),
Array.prototype.at.call(document.querySelectorAll('.modal-box'), -1)
)
}
// 定义一个close函数用于关闭模态框
@@ -149,6 +155,14 @@ async function handleCancel(data) {
}
}
async function onAfterLeave() {
await nextTick()
initDrag(
Array.prototype.at.call(document.querySelectorAll('.modal-header'), -1),
Array.prototype.at.call(document.querySelectorAll('.modal-box'), -1)
)
}
const okLoading = computed({
get() {
return !!modalOptions.value?.okLoading

View File

@@ -0,0 +1,94 @@
/**********************************
* @Author: Ronnie Zhang
* @LastEditor: Ronnie Zhang
* @LastEditTime: 2024/01/13 17:41:26
* @Email: zclzone@outlook.com
* Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
**********************************/
// 获取元素的CSS样式
function getCss(element, key) {
return element.currentStyle
? element.currentStyle[key]
: window.getComputedStyle(element, null)[key]
}
const params = {
left: 0,
top: 0,
currentX: 0,
currentY: 0,
flag: false,
}
// 初始化拖拽
export function initDrag(bar, box) {
if (!bar || !box) return
const screenWidth = document.body.clientWidth // 页面宽度
const screenHeight = document.documentElement.clientHeight // 页面可见区域高度
const dragDomWidth = box.offsetWidth // 盒子宽度
const dragDomHeight = box.offsetHeight // 盒子高度
const minDomLeft = box.offsetLeft // 盒子相对于父元素的左偏移量
const minDomTop = box.offsetTop // 盒子相对于父元素的上偏移量
const maxDragDomLeft = screenWidth - minDomLeft - dragDomWidth // 盒子在水平方向上可拖拽的最大距离
const maxDragDomTop = screenHeight - minDomTop - dragDomHeight // 盒子在垂直方向上可拖拽的最大距离
if (getCss(box, 'left') !== 'auto') {
params.left = getCss(box, 'left')
}
if (getCss(box, 'top') !== 'auto') {
params.top = getCss(box, 'top')
}
// 设置触发拖动元素的鼠标样式为移动图标
bar.style.cursor = 'move'
// 鼠标按下事件处理函数
bar.onmousedown = function (e) {
params.flag = true // 设置拖拽标志为true
e.preventDefault() // 阻止默认事件
params.currentX = e.clientX // 鼠标当前位置的X坐标
params.currentY = e.clientY // 鼠标当前位置的Y坐标
}
document.onmouseup = function () {
params.flag = false // 设置拖拽标志为false
if (getCss(box, 'left') !== 'auto') {
params.left = getCss(box, 'left')
}
if (getCss(box, 'top') !== 'auto') {
params.top = getCss(box, 'top')
}
}
document.onmousemove = function (e) {
e.preventDefault() // 阻止默认事件
// 如果拖拽标志为true
if (params.flag) {
const nowX = e.clientX // 鼠标当前位置的X坐标
const nowY = e.clientY // 鼠标当前位置的Y坐标
const disX = nowX - params.currentX // 鼠标移动的X距离
const disY = nowY - params.currentY // 鼠标移动的Y距离
let left = parseInt(params.left) + disX // 盒子元素的新left值
let top = parseInt(params.top) + disY // 盒子元素的新top值
// 拖出屏幕边缘
if (-left > minDomLeft) {
left = -minDomLeft
} else if (left > maxDragDomLeft) {
left = maxDragDomLeft
}
if (-top > minDomTop) {
top = -minDomTop
} else if (top > maxDragDomTop) {
top = maxDragDomTop
}
box.style.left = left + 'px'
box.style.top = top + 'px'
}
}
}

View File

@@ -62,7 +62,8 @@ function open(options) {
async function setCurrentRole() {
try {
okLoading.value = true
await userStore.switchCurrentRole(roleCode.value)
const { data } = await api.switchCurrentRole(roleCode.value)
await authStore.switchCurrentRole(data)
okLoading.value = false
$message.success('切换成功')
modalRef.value?.handleOk()

View File

@@ -8,7 +8,8 @@
<template>
<router-link class="h-60 f-c-c" to="/">
<img src="@/assets/images/logo.png" class="h-40" />
<!-- <img src="@/assets/images/logo.png" class="h-40" /> -->
<TheLogo class="rounded-8!" />
<h2
v-show="!appStore.collapsed"
class="ml-10 max-w-140 flex-shrink-0 text-16 color-primary font-bold"

View File

@@ -0,0 +1,40 @@
export const basicRoutes = [
{
name: 'Login',
path: '/login',
component: () => import('@/views/login/index.vue'),
meta: {
title: '登录页',
layout: 'empty',
},
},
{
name: 'Home',
path: '/',
component: () => import('@/views/home/index.vue'),
meta: {
title: '首页',
},
},
{
name: '404',
path: '/404',
component: () => import('@/views/error-page/404.vue'),
meta: {
title: '页面飞走了',
layout: 'empty',
},
},
{
name: '403',
path: '/403',
component: () => import('@/views/error-page/403.vue'),
meta: {
title: '没有权限',
layout: 'empty',
},
},
]

View File

@@ -9,7 +9,7 @@
import { useAuthStore } from '@/store'
import api from '@/api'
const WHITE_LIST = ['/login', '/404', '/role-select']
const WHITE_LIST = ['/login', '/404']
export function createPermissionGuard(router) {
router.beforeEach(async (to) => {
const authStore = useAuthStore()

View File

@@ -8,7 +8,7 @@
import { useTabStore } from '@/store'
export const EXCLUDE_TAB = ['/404', '/403', '/login', '/role-select']
export const EXCLUDE_TAB = ['/404', '/403', '/login']
export function createTabGuard(router) {
router.afterEach((to) => {

View File

@@ -9,51 +9,14 @@
import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router'
import { setupRouterGuards } from './guards'
import { useAuthStore, usePermissionStore, useUserStore } from '@/store'
export const basicRoutes = [
{
name: 'Login',
path: '/login',
component: () => import('@/views/login/index.vue'),
meta: {
title: '登录页',
layout: 'empty',
},
},
{
name: 'Home',
path: '/',
component: () => import('@/views/home/index.vue'),
meta: {
title: '首页',
},
},
{
name: '404',
path: '/404',
component: () => import('@/views/error-page/404.vue'),
meta: {
title: '页面飞走了',
layout: 'empty',
},
},
{
name: '403',
path: '/403',
component: () => import('@/views/error-page/403.vue'),
meta: {
title: '没有权限',
layout: 'empty',
},
},
]
import { getPermissions, getUserInfo } from '@/store/helper'
import { basicRoutes } from './basic-routes'
export const router = createRouter({
history:
import.meta.env.VITE_USE_HASH === 'true' ? createWebHashHistory('/') : createWebHistory('/'),
import.meta.env.VITE_USE_HASH === 'true'
? createWebHashHistory(import.meta.env.VITE_PUBLIC_PATH || '/')
: createWebHistory(import.meta.env.VITE_PUBLIC_PATH || '/'),
routes: basicRoutes,
scrollBehavior: () => ({ left: 0, top: 0 }),
})
@@ -74,32 +37,21 @@ export async function initUserAndPermissions() {
const authStore = useAuthStore()
if (!authStore.accessToken) {
authStore.toLogin()
const route = unref(router.currentRoute)
if (route.path !== '/login') {
router.replace({
path: '/login',
query: route.query,
})
}
return
}
await Promise.all([userStore.getUserInfo(), permissionStore.initPermissions()])
const [user, permissions] = await Promise.all([getUserInfo(), getPermissions()])
userStore.setUser(user)
permissionStore.setPermissions(permissions)
const routeComponents = import.meta.glob('@/views/**/*.vue')
permissionStore.accessRoutes.forEach((route) => {
route.component = routeComponents[route.component] || undefined
!router.hasRoute(route.name) && router.addRoute(route)
})
}
export async function resetRouter() {
const basicRouteNames = getRouteNames(basicRoutes)
router.getRoutes().forEach((route) => {
const name = route.name
if (!basicRouteNames.includes(name)) {
router.removeRoute(name)
}
})
}
export function getRouteNames(routes) {
const names = []
for (const route of routes) {
names.push(route.name)
if (route.children?.length) {
names.push(...getRouteNames(route.children))
}
}
return names
}

29
src/store/helper.js Normal file
View File

@@ -0,0 +1,29 @@
import { basePermissions } from '@/settings'
import api from '@/api'
export async function getUserInfo() {
const res = await api.getUser()
const { id, username, profile, roles, currentRole } = res.data || {}
return {
id,
username,
avatar: profile?.avatar,
nickName: profile?.nickName,
gender: profile?.gender,
address: profile?.address,
email: profile?.email,
roles,
currentRole,
}
}
export async function getPermissions() {
let asyncPermissions = []
try {
const res = await api.getRolePermissions()
asyncPermissions = res?.data || []
} catch (error) {
console.error(error)
}
return basePermissions.concat(asyncPermissions)
}

View File

@@ -33,6 +33,6 @@ export const useAppStore = defineStore('app', {
},
persist: {
paths: ['collapsed', 'naiveThemeOverrides'],
storage: localStorage,
storage: sessionStorage,
},
})

View File

@@ -7,8 +7,7 @@
**********************************/
import { defineStore } from 'pinia'
import { useUserStore, usePermissionStore, useTabStore } from '@/store'
import { resetRouter, router } from '@/router'
import { useUserStore, usePermissionStore, useTabStore, useRouterStore } from '@/store'
export const useAuthStore = defineStore('auth', {
state: () => ({
@@ -22,24 +21,30 @@ export const useAuthStore = defineStore('auth', {
this.$reset()
},
toLogin() {
const currentRoute = unref(router.currentRoute)
const { router, route } = useRouterStore()
router.replace({
path: '/login',
query: currentRoute.query,
query: route.query,
})
},
async switchCurrentRole(data) {
this.resetLoginState()
await nextTick()
this.setToken(data)
},
resetLoginState() {
const { resetUser } = useUserStore()
const { resetPermission } = usePermissionStore()
const { resetRouter } = useRouterStore()
const { resetPermission, accessRoutes } = usePermissionStore()
const { resetTabs } = useTabStore()
// 重置路由
resetRouter(accessRoutes)
// 重置用户
resetUser()
// 重置权限
resetPermission()
// 重置Tabs
resetTabs()
// 重置路由
resetRouter()
// 重置token
this.resetToken()
},

View File

@@ -3,3 +3,4 @@ export * from './auth'
export * from './permission'
export * from './tab'
export * from './user'
export * from './router'

View File

@@ -7,27 +7,16 @@
**********************************/
import { defineStore } from 'pinia'
import { isExternal } from '@/utils'
import { basePermissions } from '@/settings'
import api from '@/api'
const routeComponents = import.meta.glob('@/views/**/*.vue')
export const usePermissionStore = defineStore('permission', {
state: () => ({
menus: [],
accessRoutes: [],
asyncPermissions: [],
permissions: [],
menus: [],
}),
getters: {
permissions() {
return basePermissions.concat(this.asyncPermissions)
},
},
actions: {
async initPermissions() {
const { data } = (await api.getRolePermissions()) || []
this.asyncPermissions = data
setPermissions(permissions) {
this.permissions = permissions
this.menus = this.permissions
.filter((item) => item.type === 'MENU')
.map((item) => this.getMenuItem(item))
@@ -36,7 +25,7 @@ export const usePermissionStore = defineStore('permission', {
},
getMenuItem(item, parent) {
const route = this.generateRoute(item, item.show ? null : parent?.key)
if (item.enable && route.path && !isExternal(route.path)) this.accessRoutes.push(route)
if (item.enable && route.path && !route.path.startsWith('http')) this.accessRoutes.push(route)
if (!item.show) return null
const menuItem = {
label: route.meta.title,
@@ -60,7 +49,7 @@ export const usePermissionStore = defineStore('permission', {
name: item.code,
path: item.path,
redirect: item.redirect,
component: routeComponents[item.component] || undefined,
component: item.component,
meta: {
icon: item.icon,
title: item.name,

View File

@@ -0,0 +1,26 @@
/**********************************
* @Author: Ronnie Zhang
* @LastEditor: Ronnie Zhang
* @LastEditTime: 2024/01/06 17:18:40
* @Email: zclzone@outlook.com
* Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
**********************************/
import { defineStore } from 'pinia'
export const useRouterStore = defineStore('router', () => {
const router = useRouter()
const route = useRoute()
function resetRouter(accessRoutes) {
accessRoutes.forEach((item) => {
router.hasRoute(item.name) && router.removeRoute(item.name)
})
}
return {
router,
route,
resetRouter,
}
})

View File

@@ -7,7 +7,7 @@
**********************************/
import { defineStore } from 'pinia'
import { router } from '@/router'
import { useRouterStore } from './router'
export const useTabStore = defineStore('tab', {
state: () => ({
@@ -55,13 +55,13 @@ export const useTabStore = defineStore('tab', {
async removeTab(path) {
this.setTabs(this.tabs.filter((tab) => tab.path !== path))
if (path === this.activeTab) {
router.push(this.tabs[this.tabs.length - 1].path)
useRouterStore().router?.push(this.tabs[this.tabs.length - 1].path)
}
},
removeOther(curPath = this.activeTab) {
this.setTabs(this.tabs.filter((tab) => tab.path === curPath))
if (curPath !== this.activeTab) {
router.push(this.tabs[this.tabs.length - 1].path)
useRouterStore().router?.push(this.tabs[this.tabs.length - 1].path)
}
},
removeLeft(curPath) {
@@ -69,7 +69,7 @@ export const useTabStore = defineStore('tab', {
const filterTabs = this.tabs.filter((item, index) => index >= curIndex)
this.setTabs(filterTabs)
if (!filterTabs.find((item) => item.path === this.activeTab)) {
router.push(filterTabs[filterTabs.length - 1].path)
useRouterStore().router?.push(filterTabs[filterTabs.length - 1].path)
}
},
removeRight(curPath) {
@@ -77,7 +77,7 @@ export const useTabStore = defineStore('tab', {
const filterTabs = this.tabs.filter((item, index) => index <= curIndex)
this.setTabs(filterTabs)
if (!filterTabs.find((item) => item.path === this.activeTab.value)) {
router.push(filterTabs[filterTabs.length - 1].path)
useRouterStore().router?.push(filterTabs[filterTabs.length - 1].path)
}
},
resetTabs() {

View File

@@ -7,8 +7,6 @@
**********************************/
import { defineStore } from 'pinia'
import api from '@/api'
import { useAuthStore } from '@/store'
export const useUserStore = defineStore('user', {
state: () => ({
@@ -35,32 +33,8 @@ export const useUserStore = defineStore('user', {
},
},
actions: {
async getUserInfo() {
try {
const res = await api.getUser()
const { id, username, profile, roles, currentRole } = res.data || {}
this.userInfo = {
id,
username,
avatar: profile?.avatar,
nickName: profile?.nickName,
gender: profile?.gender,
address: profile?.address,
email: profile?.email,
roles,
currentRole,
}
return Promise.resolve(res.data)
} catch (error) {
return Promise.reject(error)
}
},
async switchCurrentRole(roleCode) {
const { data } = await api.switchCurrentRole(roleCode)
const authStore = useAuthStore()
authStore.resetLoginState()
await nextTick()
authStore.setToken(data)
setUser(user) {
this.userInfo = user
},
resetUser() {
this.$reset()

View File

@@ -41,3 +41,9 @@ textarea {
border: none;
resize: none;
}
img,
video {
max-width: 100%;
height: auto;
}

View File

@@ -43,7 +43,7 @@ export function setupInterceptors(axiosInstance) {
const message = resolveResError(code, data?.message ?? statusText)
//需要错误提醒
!config.noNeedTip && window.$message?.error(message)
!config?.noNeedTip && message && window.$message?.error(message)
return Promise.reject({ code, message, error: data ?? response })
}
return Promise.resolve(data ?? response)

View File

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

View File

@@ -1,7 +1,7 @@
<!--------------------------------
- @Author: Ronnie Zhang
- @LastEditor: Ronnie Zhang
- @LastEditTime: 2023/12/16 18:51:56
- @LastEditTime: 2024/01/13 17:41:47
- @Email: zclzone@outlook.com
- Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
--------------------------------->
@@ -55,7 +55,7 @@ function openModal2() {
$modal2.value?.open({
cancelText: '关闭当前',
okText: '关闭所有弹窗',
modalStyle: { width: '320px', padding: '12px', top: '100px' },
width: '400px',
async onOk() {
okLoading2.value = true
$message.loading('正在关闭...', { key: 'modal2' })

View File

@@ -17,7 +17,7 @@
<div class="w-320 flex-col px-20 py-32">
<h2 class="f-c-c text-24 text-#6a6a6a font-normal">
<img src="@/assets/images/logo.png" height="50" class="mr-12" />
<img src="@/assets/images/logo.png" class="mr-12 h-50" />
{{ title }}
</h2>
<n-input
@@ -104,42 +104,30 @@
import { throttle, lStorage } from '@/utils'
import { useStorage } from '@vueuse/core'
import api from './api'
import { useUserStore, useAuthStore } from '@/store'
import { useAuthStore } from '@/store'
import { initUserAndPermissions } from '@/router'
const userStore = useUserStore()
const authStore = useAuthStore()
const router = useRouter()
const route = useRoute()
const title = import.meta.env.VITE_TITLE
const isLogined = computed(() => {
return authStore.accessToken && userStore.roles
})
const loginInfo = ref({
username: '',
password: '',
})
function initLoginInfo() {
const localLoginInfo = lStorage.get('loginInfo')
if (localLoginInfo) {
loginInfo.value.username = localLoginInfo.username || ''
loginInfo.value.password = localLoginInfo.password || ''
}
}
const captchaUrl = ref('')
const initCaptcha = throttle(() => {
captchaUrl.value = '/api/auth/captcha?' + Date.now()
}, 500)
if (isLogined.value) {
router.push({ path: '/role-select', query: route.query })
} else {
initLoginInfo()
initCaptcha()
const localLoginInfo = lStorage.get('loginInfo')
if (localLoginInfo) {
loginInfo.value.username = localLoginInfo.username || ''
loginInfo.value.password = localLoginInfo.password || ''
}
initCaptcha()
function quickLogin() {
loginInfo.value.username = 'admin'

View File

@@ -106,10 +106,14 @@ function handleDelete(item) {
$dialog.confirm({
content: `确认删除【${item.name}】?`,
async confirm() {
$message.loading('正在删除', { key: 'deleteMenu' })
await api.deletePermission(item.id)
$message.success('删除成功', { key: 'deleteMenu' })
emit('refresh')
try {
$message.loading('正在删除', { key: 'deleteMenu' })
await api.deletePermission(item.id)
$message.success('删除成功', { key: 'deleteMenu' })
emit('refresh')
} catch (error) {
$message.destroy('deleteMenu')
}
},
})
}

View File

@@ -38,7 +38,21 @@
</template>
<n-input v-model:value="modalForm.code" />
</n-form-item-gi>
<n-form-item-gi :span="12" path="path">
<n-form-item-gi
:span="12"
path="path"
:rule="{
trigger: ['blur', 'change'],
type: 'string',
message: '必须是/、http、https开头',
validator(rule, value) {
if (value) {
return /\/|http|https/.test(value)
}
return true
},
}"
>
<template #label>
<QuestionLabel label="路由地址" content="父级菜单可不填" />
</template>

View File

@@ -23,13 +23,7 @@
:get-data="api.read"
>
<MeQueryItem label="角色名" :label-width="50">
<n-input
v-model:value="queryItems.name"
type="text"
placeholder="请输入角色名"
clearable
@keydown.enter="() => $table?.handleSearch()"
/>
<n-input v-model:value="queryItems.name" type="text" placeholder="请输入角色名" clearable />
</MeQueryItem>
<MeQueryItem label="状态" :label-width="50">
<n-select

View File

@@ -43,7 +43,6 @@
type="text"
placeholder="请输入用户名"
clearable
@keydown.enter="() => $table?.handleSearch"
/>
</MeQueryItem>

View File

@@ -28,7 +28,6 @@
type="text"
placeholder="请输入用户名"
clearable
@keydown.enter="() => $table?.handleSearch"
/>
</MeQueryItem>

View File

@@ -100,9 +100,10 @@
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 userStore = useUserStore()
const required = {
required: true,
message: '此为必填项',
@@ -116,7 +117,7 @@ async function handlePwdSave() {
await pwdValidation()
await api.changePassword(pwdForm.value)
$message.success('密码修改成功')
userStore.getUserInfo()
refreshUserInfo()
}
const newAvatar = ref(userStore.avatar)
@@ -128,7 +129,7 @@ async function handleAvatarSave() {
}
await api.updateProfile({ id: userStore.userId, avatar: newAvatar.value })
$message.success('头像修改成功')
userStore.getUserInfo()
refreshUserInfo()
}
const genders = [
@@ -148,6 +149,11 @@ async function handleProfileSave() {
await profileValidation()
await api.updateProfile(profileForm.value)
$message.success('资料修改成功')
userStore.getUserInfo()
refreshUserInfo()
}
async function refreshUserInfo() {
const user = await getUserInfo()
userStore.setUser(user)
}
</script>

View File

@@ -14,7 +14,6 @@ import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers'
import simpleHtmlPlugin from 'vite-plugin-simple-html'
import VueDevTools from 'vite-plugin-vue-devtools'
import { pluginPagePathes, pluginIcons } from './build/plugin-isme'
export default defineConfig(({ command, mode }) => {
@@ -25,7 +24,6 @@ export default defineConfig(({ command, mode }) => {
return {
base: VITE_PUBLIC_PATH || '/',
plugins: [
VueDevTools(),
Vue(),
Unocss(),
AutoImport({
@@ -64,6 +62,7 @@ export default defineConfig(({ command, mode }) => {
target: VITE_PROXY_TARGET,
changeOrigin: true,
rewrite: (path) => path.replace(new RegExp('^/api'), ''),
secure: false,
configure: (proxy, options) => {
// 配置此项可在响应头中看到请求的真实地址
proxy.on('proxyRes', (proxyRes, req) => {