区域组件,axios

This commit is contained in:
陈红丽 2024-11-09 16:55:04 +08:00
parent 19c09a116b
commit ac90e6984a
36 changed files with 402 additions and 968 deletions

View File

@ -42,7 +42,7 @@
"element-resize-detector": "^1.2.4", "element-resize-detector": "^1.2.4",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"mockjs": "^1.1.0", "mockjs": "^1.1.0",
"naive-ui": "^2.39.0", "naive-ui": "^2.40.0",
"perfect-scrollbar": "^1.5.5", "perfect-scrollbar": "^1.5.5",
"pinia": "^2.2.2", "pinia": "^2.2.2",
"print-js": "^1.6.0", "print-js": "^1.6.0",

View File

@ -6,5 +6,8 @@ export function upload(data) {
url: '/upload/uploadFile', url: '/upload/uploadFile',
method: 'post', method: 'post',
data, data,
headers: {
'Content-Type': 'multipart/form-data',
},
}); });
} }

View File

@ -0,0 +1,115 @@
<template>
<n-cascader label-field="name" value-field="areaCode" :options="optionsData" placeholder="请选择所属区域" remote ref="cascaderInstRef"
:on-load="loadData" clearable :disabled="disabled" v-model:value="selectedOptions" check-strategy="child" show-path
@update:value="handleUpdateValue"/>
</template>
<script setup lang="ts" name="china-area">
import { ref, onMounted, computed, watch } from 'vue'
import { getCityByList } from "@/api/system/user";
import type { CascaderOption } from 'naive-ui'
const emit = defineEmits(['update:modelValue', 'change']);
const optionsData = ref([]);
const tempData=ref([])
const cascaderInstRef = ref()
const props = defineProps({
//
modelValue: [String,Array],
// ()
type: {
type: Number,
default: 3,
},
//
disabled: {
type: Boolean,
default: () => false,
},
});
// watch(
// () => props,
// () => {
// props && setData(props.modelValue);
// },
// {
// deep: true,
// },
// );
const getCityData = async (pid: any) => {
const data = await getCityByList(pid)
data.map(item => {
item.isLeaf = false
})
return data
}
const changeFlag = ref(false)
const selectedOptions = computed({
get: () => {
return props.modelValue[3];
},
set: (val) => {
emit('update:modelValue', tempData.value);
},
});
const setData = async (data) => {
if (changeFlag.value) return
let index1 = findListChild(optionsData.value, data[0])
let data1 = await getCityByList(data[0])
optionsData.value[index1].children = data1
let index2 = findListChild(data1, data[1])
let data2 = await getCityByList(data[1])
optionsData.value[index1].children[index2].children = data2
let index3 = findListChild(data2, data[2])
let data3 = await getCityByList(data[2])
optionsData.value[index1].children[index2].children[index3].children = data3
}
const findListChild = (list: any, value: any) => {
let child = null
let index = 0
for (let i = 0; i < list.length; i++) {
if (list[i].areaCode == value) {
child = list[i]
index = i
break;
}
}
return index
}
const loadData = async (option: CascaderOption) => {
tempData.value[option.level]=option.areaCode
return new Promise<void>(async (resolve) => {
const data = await getCityByList(option.areaCode)
data.map(item => {
item.isLeaf = item.level==3?true:false
})
option.children = data
resolve()
})
};
const handleUpdateValue =(val)=>{
changeFlag.value = true
tempData.value[3] = val
}
onMounted(async () => {
const data = await getCityByList(0)
data.map(item => {
item.isLeaf = false
})
optionsData.value = data
if(props.modelValue.length>0) {
setData(props.modelValue)
}
});
</script>
<style lang="less">
.ant-cascader-menu {
background-color: #ffffff;
}
</style>

View File

@ -2,14 +2,42 @@
<div class="img-cropper" :class="{ 'img-cropper-auto': $slots.default }"> <div class="img-cropper" :class="{ 'img-cropper-auto': $slots.default }">
<slot name="default"></slot> <slot name="default"></slot>
<template v-if="!$slots.default"> <template v-if="!$slots.default">
<div class="img-cropper-img" @click="openCropper"> <!-- <div class="img-cropper-img" @click="openCropper">
<img :src="src" :class="{ circled: circled }" /> <img :src="src" :class="{ circled: circled }" />
<div class="mask" :class="{ circled: circled }"> <div class="mask" :class="{ circled: circled }">
<n-icon> <n-icon>
<UploadOutlined /> <UploadOutlined />
</n-icon> </n-icon>
</div> </div>
</div> </div> -->
<template v-if="src">
<div class="img-cropper-img" @click="openCropper">
<div class="img-boxs">
<img :src="src" :class="{ circled: circled }" />
</div>
<div class="mask" :class="{ circled: circled }" @click.stop>
<div class="handle-icon" @click="openCropper">
<span class="icon">
<n-icon size="18"><FormOutlined /></n-icon></span>
</div>
<div class="handle-icon" @click="viewImg">
<span class="icon"><n-icon size="18"><ZoomInOutlined /></n-icon></span>
</div>
<div class="handle-icon" @click="deleteImg">
<span class="icon"><n-icon size="18"><DeleteOutlined /></n-icon></span>
</div>
</div>
</div>
</template>
<template v-else>
<div class="img-cropper-img" @click="openCropper">
<div class="addImg">
<span class="icon">
<n-icon><PlusOutlined /></n-icon>
</span>
</div>
</div>
</template>
</template> </template>
<CropperModal <CropperModal
ref="cropperRef" ref="cropperRef"
@ -17,6 +45,7 @@
subBtuText="确认上传" subBtuText="确认上传"
:uploadApi="uploadApi" :uploadApi="uploadApi"
:circled="circled" :circled="circled"
:name="name"
@upload-success="uploadSuccess" @upload-success="uploadSuccess"
/> />
</div> </div>
@ -25,7 +54,13 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from 'vue'; import { ref } from 'vue';
import CropperModal from './CropperModal.vue'; import CropperModal from './CropperModal.vue';
import { UploadOutlined } from '@vicons/antd'; import {
PlusOutlined,
ZoomInOutlined,
DeleteOutlined,
FormOutlined,
UploadOutlined
} from '@vicons/antd';
import { cssUnit } from '@/utils'; import { cssUnit } from '@/utils';
const cropperRef = ref(); const cropperRef = ref();
@ -36,7 +71,8 @@
title: { type: String, default: '图片上传' }, title: { type: String, default: '图片上传' },
src: { type: String, required: true }, src: { type: String, required: true },
circled: { type: Boolean, default: false }, circled: { type: Boolean, default: false },
width: { type: [String, Number], default: 200 }, width: { type: [String, Number], default: 120 },
name: { type: String, default: 'name' },
uploadApi: { uploadApi: {
type: Function as PropType<(params) => Promise<any>>, type: Function as PropType<(params) => Promise<any>>,
}, },
@ -54,6 +90,13 @@
emit('uploadSuccess', result); emit('uploadSuccess', result);
} }
const viewImg = () => {
window.open(props.src);
};
const deleteImg = () => {
emit('uploadSuccess', { fileUrl: '' });
};
defineExpose({ defineExpose({
openCropper, openCropper,
}); });
@ -72,7 +115,22 @@
width: 100%; width: 100%;
height: 100%; height: 100%;
border-radius: 50%; border-radius: 50%;
.img-boxs {
border: 1px dashed #999999;
padding: 5px;
}
.addImg {
width: v-bind(getWidth);
height: v-bind(getWidth);
border: 1px dashed #999999;
display: flex;
justify-content: center;
align-items: center;
.icon {
color: #999999;
font-size: v-bind(iconSize);
}
}
.mask { .mask {
position: absolute; position: absolute;
width: 100%; width: 100%;
@ -86,11 +144,19 @@
top: 0; top: 0;
opacity: 0; opacity: 0;
transition: opacity 0.4s; transition: opacity 0.4s;
.handle-icon {
.n-icon { display: flex;
color: #fff; flex-direction: column;
font-size: v-bind(iconSize); align-items: center;
justify-content: center;
padding: 0 6%;
color: aliceblue;
.n-icon {
color: #fff;
font-size: v-bind(iconSize);
}
} }
} }
&:hover { &:hover {

View File

@ -1,5 +1,5 @@
<template> <template>
<basicModal ref="modalRef" class="cropperModal" @register="modalRegister" @on-ok="handleOk"> <basicModal ref="modalRef" class="cropperModal" @register="modalRegister" @on-ok="handleOk" @on-close="handleClose">
<template #default> <template #default>
<div class="cropper-box"> <div class="cropper-box">
<div class="cropper-box-left"> <div class="cropper-box-left">
@ -184,7 +184,7 @@
import { useMessage } from 'naive-ui'; import { useMessage } from 'naive-ui';
import { basicModal, useModal } from '@/components/Modal'; import { basicModal, useModal } from '@/components/Modal';
import CropperImage from './CropperImage.vue'; import CropperImage from './CropperImage.vue';
import { dataURLtoBlob } from '@/utils/file/base64Conver'; import { dataURLtoBlob,base64ToFile } from '@/utils/file/base64Conver';
import { isFunction } from '@/utils/is'; import { isFunction } from '@/utils/is';
import { import {
UploadOutlined, UploadOutlined,
@ -200,6 +200,7 @@
const props = defineProps({ const props = defineProps({
title: { type: String, default: '图片上传' }, title: { type: String, default: '图片上传' },
circled: { type: Boolean, default: false }, circled: { type: Boolean, default: false },
name: { type: String, default: 'name' },
uploadApi: { uploadApi: {
type: Function as PropType<(params) => Promise<any>>, type: Function as PropType<(params) => Promise<any>>,
}, },
@ -261,18 +262,28 @@
return message.error('请先上传图片'); return message.error('请先上传图片');
} }
const uploadApi = props.uploadApi; const uploadApi = props.uploadApi;
const name = props.name
if (uploadApi && isFunction(uploadApi)) { if (uploadApi && isFunction(uploadApi)) {
const blob = dataURLtoBlob(previewSource.value); const file = base64ToFile(previewSource.value, filename);
console.log(file)
try { try {
setSubLoading(true); setSubLoading(true);
const result = await uploadApi({ name: 'file', file: blob, filename }); const formData = new window.FormData();
emit('uploadSuccess', { source: previewSource.value, data: result.data }); formData.append('file', file);
formData.append('name', name);
console.log(formData)
const result = await uploadApi(formData);
emit('uploadSuccess', result);
closeModal(); closeModal();
} finally { } finally {
setSubLoading(false); setSubLoading(false);
} }
} }
} }
function handleClose() {
src.value = '';
previewSource.value = '';
}
defineExpose({ defineExpose({
showModal, showModal,

View File

@ -2,9 +2,9 @@
* @description: * @description:
*/ */
export enum ResultEnum { export enum ResultEnum {
SUCCESS = 200, SUCCESS = 0,
ERROR = -1, ERROR = 1,
TIMEOUT = 10042, TIMEOUT = 401,
TYPE = 'success', TYPE = 'success',
} }

View File

@ -7,8 +7,6 @@ export enum PageEnum {
REDIRECT_NAME = 'Redirect', REDIRECT_NAME = 'Redirect',
// 首页 // 首页
BASE_HOME = '/dashboard', BASE_HOME = '/dashboard',
//首页跳转默认路由
BASE_HOME_REDIRECT = '/dashboard/console',
// 错误 // 错误
ERROR_PAGE_NAME = 'ErrorPage', ERROR_PAGE_NAME = 'ErrorPage',
} }

View File

@ -8,9 +8,12 @@ export function usePermission() {
* @param accesses * @param accesses
*/ */
function _somePermissions(accesses: string[]) { function _somePermissions(accesses: string[]) {
return userStore.getPermissions.some((item: any) => { const permissionsList = userStore.getPermissions;
const value = item?.value ?? item; if(permissionsList[0]=='*:*:*') {
return accesses.includes(value); return true
}
return accesses.some((item) => {
return permissionsList.includes(item);
}); });
} }
@ -29,8 +32,11 @@ export function usePermission() {
*/ */
function hasEveryPermission(accesses: string[]): boolean { function hasEveryPermission(accesses: string[]): boolean {
const permissionsList = userStore.getPermissions; const permissionsList = userStore.getPermissions;
if(permissionsList[0]=='*:*:*') {
return true
}
if (Array.isArray(accesses)) { if (Array.isArray(accesses)) {
return permissionsList.every((access: any) => accesses.includes(access.value)); return accesses.every((access: any) => permissionsList.includes(access));
} }
throw new Error(`[hasEveryPermission]: ${accesses} should be a array !`); throw new Error(`[hasEveryPermission]: ${accesses} should be a array !`);
} }
@ -42,8 +48,11 @@ export function usePermission() {
*/ */
function hasSomePermission(accesses: string[]): boolean { function hasSomePermission(accesses: string[]): boolean {
const permissionsList = userStore.getPermissions; const permissionsList = userStore.getPermissions;
if(permissionsList[0]=='*:*:*') {
return true
}
if (Array.isArray(accesses)) { if (Array.isArray(accesses)) {
return permissionsList.some((access: any) => accesses.includes(access.value)); return accesses.some((access: any) => permissionsList.includes(access));
} }
throw new Error(`[hasSomePermission]: ${accesses} should be a array !`); throw new Error(`[hasSomePermission]: ${accesses} should be a array !`);
} }

View File

@ -120,7 +120,7 @@
<div class="layout-header-trigger layout-header-trigger-min"> <div class="layout-header-trigger layout-header-trigger-min">
<n-dropdown :options="avatarOptions" trigger="hover" @select="avatarSelect"> <n-dropdown :options="avatarOptions" trigger="hover" @select="avatarSelect">
<div class="avatar"> <div class="avatar">
<n-avatar round :src="schoolboy" /> <n-avatar round :src="userAvatar?userAvatar:schoolboy" />
</div> </div>
</n-dropdown> </n-dropdown>
</div> </div>
@ -155,7 +155,8 @@
import { computed, ref, unref, watch, inject } from 'vue'; import { computed, ref, unref, watch, inject } from 'vue';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { useDialog, useMessage } from 'naive-ui'; import { useDialog, useMessage } from 'naive-ui';
import { TABS_ROUTES } from '@/store/mutation-types'; import { useTabsViewStore } from '@/store/modules/tabsView';
import { TABS_ROUTES,FIRST_ROUTE } from '@/store/mutation-types';
import { useUserStore } from '@/store/modules/user'; import { useUserStore } from '@/store/modules/user';
import { useLockscreenStore } from '@/store/modules/lockscreen'; import { useLockscreenStore } from '@/store/modules/lockscreen';
import { AsideMenu } from '@/layout/components/Menu'; import { AsideMenu } from '@/layout/components/Menu';
@ -210,7 +211,7 @@
const drawerSetting = ref(); const drawerSetting = ref();
const amendPwdRef = ref(); const amendPwdRef = ref();
const userAvatar = userStore.getAvatar
// const username = userStore?.info ? ref(userStore?.info.username) : ''; // const username = userStore?.info ? ref(userStore?.info.username) : '';
const collapsed = inject('collapsed'); const collapsed = inject('collapsed');
@ -238,6 +239,7 @@
const route = useRoute(); const route = useRoute();
const { isFullscreen, toggle } = useFullscreen(); const { isFullscreen, toggle } = useFullscreen();
const asyncRouteStore = useAsyncRouteStore(); const asyncRouteStore = useAsyncRouteStore();
const tabsViewStore = useTabsViewStore();
const generator: any = (routerMap) => { const generator: any = (routerMap) => {
return routerMap return routerMap
@ -295,7 +297,9 @@
userStore.logout().then(() => { userStore.logout().then(() => {
message.success('成功退出登录'); message.success('成功退出登录');
// //
tabsViewStore.closeAllTabs();
localStorage.removeItem(TABS_ROUTES); localStorage.removeItem(TABS_ROUTES);
localStorage.removeItem(FIRST_ROUTE);
asyncRouteStore.setDynamicAddedRoute(false); asyncRouteStore.setDynamicAddedRoute(false);
router.replace({ router.replace({
name: BASE_LOGIN_NAME, name: BASE_LOGIN_NAME,
@ -323,13 +327,13 @@
click: () => openAppSearch(), click: () => openAppSearch(),
}, },
}, },
{ // {
icon: LockOutlined, // icon: LockOutlined,
tips: '锁屏', // tips: '',
eventObject: { // eventObject: {
click: () => useLockscreen.setLock(true), // click: () => useLockscreen.setLock(true),
}, // },
}, // },
]; ];
const avatarOptions = [ const avatarOptions = [
{ {
@ -353,7 +357,7 @@
const avatarSelect = (key) => { const avatarSelect = (key) => {
switch (key) { switch (key) {
case 1: case 1:
router.push({ name: 'Setting' }); router.push({ name: 'setting' });
break; break;
case 2: case 2:
doLogout(); doLogout();

View File

@ -74,7 +74,7 @@
import { computed, ref, unref, provide, watch, onMounted, nextTick, toRaw, inject } from 'vue'; import { computed, ref, unref, provide, watch, onMounted, nextTick, toRaw, inject } from 'vue';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { storage } from '@/utils/Storage'; import { storage } from '@/utils/Storage';
import { TABS_ROUTES } from '@/store/mutation-types'; import { TABS_ROUTES,FIRST_ROUTE } from '@/store/mutation-types';
import { useTabsViewStore } from '@/store/modules/tabsView'; import { useTabsViewStore } from '@/store/modules/tabsView';
import { useAsyncRouteStore } from '@/store/modules/asyncRoute'; import { useAsyncRouteStore } from '@/store/modules/asyncRoute';
import { RouteItem } from '@/store/modules/tabsView'; import { RouteItem } from '@/store/modules/tabsView';
@ -335,7 +335,7 @@
// //
const closeAll = () => { const closeAll = () => {
tabsViewStore.closeAllTabs(); tabsViewStore.closeAllTabs();
router.replace(PageEnum.BASE_HOME_REDIRECT); router.replace(storage.get(FIRST_ROUTE));
updateNavScroll(); updateNavScroll();
}; };

View File

@ -1,14 +1,16 @@
import { adminMenus } from '@/api/system/menu'; import { adminMenus } from '@/api/system/menu';
import { constantRouterIcon } from './router-icons'; import { constantRouterIcon } from './router-icons';
import { RouteRecordRaw } from 'vue-router';
import { Layout, ParentLayout } from '@/router/constant'; import { Layout, ParentLayout } from '@/router/constant';
import type { AppRouteRecordRaw } from '@/router/types'; import type { AppRouteRecordRaw } from '@/router/types';
import { ProfileOutlined } from '@ant-design/icons-vue';
import { renderIcon } from '@/utils/index';
const Iframe = () => import('@/views/iframe/index.vue'); const Iframe = () => import('@/views/iframe/index.vue');
const LayoutMap = new Map<string, () => Promise<typeof import('*.vue')>>(); const LayoutMap = new Map<string, () => Promise<typeof import('*.vue')>>();
LayoutMap.set('LAYOUT', Layout); LayoutMap.set('LAYOUT', Layout);
LayoutMap.set('IFRAME', Iframe); LayoutMap.set('IFRAME', Iframe);
LayoutMap.set('ParentLayout', ParentLayout);
/** /**
* *
@ -16,34 +18,81 @@ LayoutMap.set('ParentLayout', ParentLayout);
* @param parent * @param parent
* @returns {*} * @returns {*}
*/ */
export const routerGenerator = (routerMap, parent?): AppRouteRecordRaw[] => { export const routerGenerator = (routerMap): any[] => {
return routerMap.map((item) => { return routerMap.map((item) => {
const names = /http(s)?:/.test(item.component)?item.component:item.path.replaceAll('/','')
item.meta = {
title:item.parentId==0 && item.children.length==0?'':item.name,
icon:constantRouterIcon[item.icon2]|| null,
sort:item.sort,
permissions:item.permission,
hidden: item.hide?true:false,
isRoot:item.parentId==0 && item.children.length==0?true:false,
alwaysShow: item.parentId==0 && item.children.length==0?true:false,
frameSrc: item.target==1?item.component:'',
target:item.target==2?true:false
}
let components = ''
if(item.parentId==0 && (item.children.length==0 || item.children.length>0) ) {
components ='LAYOUT'
} else if(item.target==0) {
components = item.component
} else if(item.target==1) {
components ='IFRAME'
}
const currentRouter: any = { const currentRouter: any = {
// 路由地址 动态拼接生成如 /dashboard/workplace path:item.target==2?'':item.path,
path: `${(parent && parent.path) || ''}/${item.path}`,
// 路由名称,建议唯一 // 路由名称,建议唯一
name: item.name || '', name: names,
// 该路由对应页面的 组件 // 该路由对应页面的 组件
component: item.component, component: components,
// meta: 页面标题, 菜单图标, 页面权限(供指令权限用,可去掉) // meta: 页面标题, 菜单图标, 页面权限(供指令权限用,可去掉)
meta: { meta: {
...item.meta, ...item.meta,
label: item.meta.title,
icon: constantRouterIcon[item.meta.icon] || null,
permissions: item.meta.permissions || null, permissions: item.meta.permissions || null,
}, },
}; };
// 为了防止出现后端返回结果不规范,处理有可能出现拼接出两个 反斜杠 // 为了防止出现后端返回结果不规范,处理有可能出现拼接出两个 反斜杠
currentRouter.path = currentRouter.path.replace('//', '/'); // currentRouter.path = currentRouter.path.replace('//', '/');
// 重定向 // 重定向
item.redirect && (currentRouter.redirect = item.redirect); item.redirect && (currentRouter.redirect = item.redirect);
// 是否有子菜单,并递归处理 // 是否有子菜单,并递归处理
if (item.children && item.children.length > 0) { if (item.children && item.children.length > 0) {
//如果未定义 redirect 默认第一个子路由为 redirect //如果未定义 redirect 默认第一个子路由为 redirect
!item.redirect && (currentRouter.redirect = `${item.path}/${item.children[0].path}`); !item.redirect && (currentRouter.redirect = `${item.children[0].path}`);
// Recursion // Recursion
currentRouter.children = routerGenerator(item.children, currentRouter); currentRouter.children = routerGenerator(item.children, currentRouter);
} else {
if(item.parentId==0 && item.children.length==0) {
currentRouter.children =[]
if(item.target==1 && (/http(s)?:/.test(item.component))){
currentRouter.children.push({
path: item.path,
name: names,
meta: {
title: item.name,
frameSrc: item.component,
icon:constantRouterIcon[item.icon2],
hidden: item.hide?true:false,
},
component: 'IFRAME',
})
} else {
currentRouter.children.push({
path: item.path,
name: names,
meta: {
title: item.name,
icon:constantRouterIcon[item.icon2],
activeMenu: names,
target:item.target==2?true:false,
hidden: item.hide?true:false,
},
component: item.component,
})
}
}
} }
return currentRouter; return currentRouter;
}); });
@ -53,11 +102,11 @@ export const routerGenerator = (routerMap, parent?): AppRouteRecordRaw[] => {
* *
* @returns {Promise<Router>} * @returns {Promise<Router>}
*/ */
export const generatorDynamicRouter = (): Promise<AppRouteRecordRaw[]> => { export const generatorDynamicRouter = (): Promise<RouteRecordRaw[]> => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
adminMenus() adminMenus()
.then((result) => { .then((result) => {
const routeList = routerGenerator(result); const routeList = routerGenerator(result)
asyncImportRoute(routeList); asyncImportRoute(routeList);
resolve(routeList); resolve(routeList);
}) })
@ -119,3 +168,9 @@ export const dynamicImport = (
return; return;
} }
}; };
/**
*
* */
export const findFirstRoutePath = (routes)=>{
return routes.length > 0?(routes[0].redirect?routes[0].redirect:routes[0].path):''
}

View File

@ -9,6 +9,11 @@ const modules: any = import.meta.glob('./modules/**/*.ts', { eager: true });
const routeModuleList: RouteRecordRaw[] = []; const routeModuleList: RouteRecordRaw[] = [];
const modulesVue = import.meta.glob('/src/views/**/*.vue')
export function getModulesKey() {
return Object.keys(modulesVue).map((item) => item.replace('/src/views', '').replace('.vue', ''));
}
Object.keys(modules).forEach((key) => { Object.keys(modules).forEach((key) => {
const mod = modules[key].default || {}; const mod = modules[key].default || {};
const modList = Array.isArray(mod) ? [...mod] : [mod]; const modList = Array.isArray(mod) ? [...mod] : [mod];
@ -41,8 +46,8 @@ export const RootRoute: RouteRecordRaw = {
export const LoginRoute: RouteRecordRaw = { export const LoginRoute: RouteRecordRaw = {
path: '/login', path: '/login',
name: 'Login', name: 'Login',
// component: () => import('@/views/login/index.vue'), //v1.x 模板 component: () => import('@/views/login/index.vue'), //v1.x 模板
component: () => import('@/views/login/newLogin.vue'), // 2.x新模板 // component: () => import('@/views/login/newLogin.vue'), // 2.x新模板
meta: { meta: {
title: '登录', title: '登录',
}, },

View File

@ -1,32 +0,0 @@
import { RouteRecordRaw } from 'vue-router';
import { Layout } from '@/router/constant';
import { ProjectOutlined } from '@vicons/antd';
import { renderIcon } from '@/utils/index';
const routes: Array<RouteRecordRaw> = [
{
path: '/about',
name: 'about',
component: Layout,
meta: {
sort: 11,
isRoot: true,
activeMenu: 'about_index',
alwaysShow: true,
icon: renderIcon(ProjectOutlined),
},
children: [
{
path: 'index',
name: `about_index`,
meta: {
title: '关于项目',
activeMenu: 'about_index',
},
component: () => import('@/views/about/index.vue'),
},
],
},
];
export default routes;

View File

@ -1,177 +0,0 @@
import { RouteRecordRaw } from 'vue-router';
import { Layout, ParentLayout } from '@/router/constant';
import { WalletOutlined } from '@vicons/antd';
import { renderIcon } from '@/utils';
const routeName = 'comp';
/**
* @param name , ,
* @param meta
* @param redirect , 访,
* @param meta.disabled
* @param meta.title
* @param meta.icon
* @param meta.keepAlive
* @param meta.sort
*
* */
const routes: Array<RouteRecordRaw> = [
{
path: '/comp',
name: routeName,
component: Layout,
redirect: '/comp/table',
meta: {
title: '组件示例',
icon: renderIcon(WalletOutlined),
sort: 8,
},
children: [
{
path: 'table',
name: `${routeName}_table`,
redirect: '/comp/table/basic',
component: ParentLayout,
meta: {
title: '表格',
},
children: [
{
path: 'basic',
name: `${routeName}_table_basic`,
meta: {
title: '基础表格',
},
component: () => import('@/views/comp/table/basic.vue'),
},
{
path: 'editCell',
name: `${routeName}_table_editCell`,
meta: {
title: '单元格编辑',
},
component: () => import('@/views/comp/table/editCell.vue'),
},
{
path: 'editRow',
name: `${routeName}_table_editRow`,
meta: {
title: '整行编辑',
},
component: () => import('@/views/comp/table/editRow.vue'),
},
],
},
{
path: 'form',
name: `${routeName}_form`,
redirect: '/comp/form/basic',
component: ParentLayout,
meta: {
title: '表单',
},
children: [
{
path: 'basic',
name: `${routeName}_form_basic`,
meta: {
title: '基础使用',
},
component: () => import('@/views/comp/form/basic.vue'),
},
{
path: 'useForm',
name: `useForm`,
meta: {
title: 'useForm',
},
component: () => import('@/views/comp/form/useForm.vue'),
},
],
},
{
path: 'upload',
name: `${routeName}_upload`,
meta: {
title: '上传图片',
},
component: () => import('@/views/comp/upload/index.vue'),
},
{
path: 'modal',
name: `${routeName}_modal`,
meta: {
title: '弹窗扩展',
},
component: () => import('@/views/comp/modal/index.vue'),
},
{
path: 'richtext',
name: `richtext`,
meta: {
title: '富文本',
},
component: () => import('@/views/comp/richtext/vue-quill.vue'),
},
{
path: 'drag',
name: `Drag`,
meta: {
title: '拖拽',
},
component: () => import('@/views/comp/drag/index.vue'),
},
{
path: 'region',
name: `Region`,
meta: {
title: '地区',
},
component: () => import('@/views/comp/region/index.vue'),
},
{
path: 'cropper',
name: `Cropper`,
meta: {
title: '图片裁剪',
},
component: () => import('@/views/comp/cropper/index.vue'),
},
{
path: 'qrcode',
name: `Qrcode`,
meta: {
title: '二维码',
},
component: () => import('@/views/comp/qrcode/index.vue'),
},
{
path: 'password',
name: `Password`,
meta: {
title: '密码强度',
},
component: () => import('@/views/comp/password/index.vue'),
},
{
path: 'select',
name: `Select`,
meta: {
title: '选择器',
},
component: () => import('@/views/comp/select/BasicSelect.vue'),
},
{
path: 'tableselect',
name: `TableSelect`,
meta: {
title: '表格选择器',
},
component: () => import('@/views/comp/tableSelect/tableSelect.vue'),
},
],
},
];
export default routes;

View File

@ -1,67 +0,0 @@
import { RouteRecordRaw } from 'vue-router';
import { Layout } from '@/router/constant';
import { DashboardOutlined } from '@vicons/antd';
import { renderIcon } from '@/utils/index';
const routeName = 'dashboard';
/**
* @param name , ,
* @param meta
* @param redirect , 访,
* @param meta.disabled
* @param meta.title
* @param meta.icon
* @param meta.keepAlive
* @param meta.sort
* @param meta.tagView false
* @param meta.breadcrumbView false
* @param meta.authEvery
* @param meta.documentTitle title
* */
const routes: Array<RouteRecordRaw> = [
{
path: '/dashboard',
name: routeName,
redirect: '/dashboard/console',
component: Layout,
meta: {
title: 'Dashboard',
icon: renderIcon(DashboardOutlined),
permissions: ['dashboard_console', 'dashboard_console', 'dashboard_workplace'],
sort: 0,
},
children: [
{
path: 'console',
name: `${routeName}_console`,
meta: {
title: '主控台',
permissions: ['dashboard_console'],
affix: true,
},
component: () => import('@/views/dashboard/console/console.vue'),
},
{
path: 'monitor',
name: `${routeName}_monitor`,
meta: {
title: '监控页',
},
component: () => import('@/views/dashboard/monitor/monitor.vue'),
},
{
path: 'workplace',
name: `${routeName}_workplace`,
meta: {
title: '工作台',
keepAlive: true,
permissions: ['dashboard_workplace'],
},
component: () => import('@/views/dashboard/workplace/workplace.vue'),
},
],
},
];
export default routes;

View File

@ -1,19 +0,0 @@
import { RouteRecordRaw } from 'vue-router';
import { Layout } from '@/router/constant';
import { DocumentTextOutline } from '@vicons/ionicons5';
import { renderIcon } from '@/utils/index';
const routes: Array<RouteRecordRaw> = [
{
path: '/external',
name: 'https://www.naiveadmin.com',
component: Layout,
meta: {
title: '项目文档',
icon: renderIcon(DocumentTextOutline),
sort: 11,
},
},
];
export default routes;

View File

@ -1,57 +0,0 @@
import { RouteRecordRaw } from 'vue-router';
import { Layout } from '@/router/constant';
import { ExclamationCircleOutlined } from '@vicons/antd';
import { renderIcon } from '@/utils/index';
/**
* @param name , ,
* @param meta
* @param redirect , 访,
* @param meta.disabled
* @param meta.title
* @param meta.icon
* @param meta.keepAlive
* @param meta.sort
*
* */
const routes: Array<RouteRecordRaw> = [
{
path: '/exception',
name: 'Exception',
redirect: '/exception/403',
component: Layout,
meta: {
title: '异常页面',
icon: renderIcon(ExclamationCircleOutlined),
sort: 3,
},
children: [
{
path: '403',
name: 'exception-403',
meta: {
title: '403',
},
component: () => import('@/views/exception/403.vue'),
},
{
path: '404',
name: 'exception-404',
meta: {
title: '404',
},
component: () => import('@/views/exception/404.vue'),
},
{
path: '500',
name: 'exception-500',
meta: {
title: '500',
},
component: () => import('@/views/exception/500.vue'),
},
],
},
];
export default routes;

View File

@ -1,103 +0,0 @@
import { RouteRecordRaw } from 'vue-router';
import { Layout, ParentLayout } from '@/router/constant';
import { ControlOutlined } from '@vicons/antd';
import { renderIcon } from '@/utils';
const routes: Array<RouteRecordRaw> = [
{
path: '/feature',
name: 'Feature',
component: Layout,
meta: {
title: '功能示例',
icon: renderIcon(ControlOutlined),
sort: 7,
},
children: [
{
path: 'authority',
name: 'Authority',
component: () => import('@/views/feature/authority/authority.vue'),
meta: {
title: '权限判断',
},
},
{
path: 'download',
name: 'Download',
component: () => import('@/views/feature/download/download.vue'),
meta: {
title: '文件下载',
},
},
{
path: 'context-menus',
name: 'ContextMenus',
component: () => import('@/views/feature/context-menus/context-menus.vue'),
meta: {
title: '右键菜单',
},
},
{
path: 'copy',
name: 'copy',
component: () => import('@/views/feature/copy/copy.vue'),
meta: {
title: '剪贴板',
},
},
{
path: 'print',
name: 'print',
component: () => import('@/views/feature/print/print.vue'),
meta: {
title: '打印',
},
},
{
path: 'scrollbar',
name: 'scrollbar',
component: () => import('@/views/feature/scrollbar/scrollbar.vue'),
meta: {
title: '滚动条',
},
},
{
path: 'excel',
name: 'Excel',
meta: {
title: 'Excel',
},
component: ParentLayout,
children: [
{
path: 'choiceExport',
name: 'choiceExport',
component: () => import('@/views/feature/excel/choiceExport.vue'),
meta: {
title: '选择导出格式',
},
},
{
path: 'jsonExport',
name: 'jsonExport',
component: () => import('@/views/feature/excel/jsonExport.vue'),
meta: {
title: 'JSON数据导出',
},
},
],
},
{
path: 'tagsAction',
name: 'TagsAction',
meta: {
title: '多页签操作',
},
component: () => import('@/views/feature/tags/tagsAction.vue'),
},
],
},
];
export default routes;

View File

@ -1,66 +0,0 @@
import { RouteRecordRaw } from 'vue-router';
import { Layout } from '@/router/constant';
import { ProfileOutlined } from '@vicons/antd';
import { renderIcon } from '@/utils/index';
/**
* @param name , ,
* @param meta
* @param redirect , 访,
* @param meta.disabled
* @param meta.title
* @param meta.icon
* @param meta.keepAlive
* @param meta.sort
*
* */
const routes: Array<RouteRecordRaw> = [
{
path: '/form',
name: 'Form',
redirect: '/form/basic-form',
component: Layout,
meta: {
title: '表单页面',
icon: renderIcon(ProfileOutlined),
sort: 3,
},
children: [
{
path: 'basic-form',
name: 'BasicForm',
meta: {
title: '基础表单',
keepAlive: true,
},
component: () => import('@/views/form/basicForm/index.vue'),
},
{
path: 'advanced-form',
name: 'form-advanced-form',
meta: {
title: '高级表单',
},
component: () => import('@/views/form/advancedForm/advancedForm.vue'),
},
{
path: 'step-form',
name: 'form-step-form',
meta: {
title: '分步表单',
},
component: () => import('@/views/form/stepForm/stepForm.vue'),
},
{
path: 'detail',
name: 'form-detail',
meta: {
title: '表单详情',
},
component: () => import('@/views/form/detail/index.vue'),
},
],
},
];
export default routes;

View File

@ -1,42 +0,0 @@
import { RouteRecordRaw } from 'vue-router';
import { Layout } from '@/router/constant';
import { DesktopOutline } from '@vicons/ionicons5';
import { renderIcon } from '@/utils/index';
const IFrame = () => import('@/views/iframe/index.vue');
const routes: Array<RouteRecordRaw> = [
{
path: '/frame',
name: 'Frame',
redirect: '/frame/docs',
component: Layout,
meta: {
title: '外部页面',
sort: 9,
icon: renderIcon(DesktopOutline),
},
children: [
{
path: 'docs',
name: 'frame-docs',
meta: {
title: '项目文档(内嵌)',
frameSrc: 'https://www.naiveadmin.com',
},
component: IFrame,
},
{
path: 'naive',
name: 'frame-naive',
meta: {
title: 'NaiveUi(内嵌)',
frameSrc: 'https://www.naiveui.com',
},
component: IFrame,
},
],
},
];
export default routes;

View File

@ -1,53 +0,0 @@
import { RouteRecordRaw } from 'vue-router';
import { Layout } from '@/router/constant';
import { TableOutlined } from '@vicons/antd';
import { renderIcon } from '@/utils/index';
/**
* @param name , ,
* @param meta
* @param redirect , 访,
* @param meta.disabled
* @param meta.title
* @param meta.icon
* @param meta.keepAlive
* @param meta.sort
*
* */
const routes: Array<RouteRecordRaw> = [
{
path: '/list',
name: 'List',
redirect: '/list/basic-list',
component: Layout,
meta: {
title: '列表页面',
icon: renderIcon(TableOutlined),
sort: 2,
permissions: ['basic_list'],
},
children: [
{
path: 'basic-list/:id?',
name: 'basic-list',
meta: {
title: '基础列表',
},
component: () => import('@/views/list/basicList/index.vue'),
},
{
path: 'basic-info/:id?',
name: 'BasicInfo',
meta: {
title: '基础详情',
hidden: true,
activeMenu: 'basic-list',
keepAlive: true,
},
component: () => import('@/views/list/basicList/info.vue'),
},
],
},
];
export default routes;

View File

@ -1,19 +0,0 @@
import { RouteRecordRaw } from 'vue-router';
import { Layout } from '@/router/constant';
import { DiamondOutline } from '@vicons/ionicons5';
import { renderIcon } from '@/utils/index';
const routes: Array<RouteRecordRaw> = [
{
path: '/mandate',
name: 'https://www.naiveadmin.com/authorize/index',
component: Layout,
meta: {
title: '获取授权',
icon: renderIcon(DiamondOutline),
sort: 12,
},
},
];
export default routes;

View File

@ -1,57 +0,0 @@
import { RouteRecordRaw } from 'vue-router';
import { Layout } from '@/router/constant';
import { CheckCircleOutlined } from '@vicons/antd';
import { renderIcon } from '@/utils/index';
/**
* @param name , ,
* @param meta
* @param redirect , 访,
* @param meta.disabled
* @param meta.title
* @param meta.icon
* @param meta.keepAlive
* @param meta.sort
*
* */
const routes: Array<RouteRecordRaw> = [
{
path: '/result',
name: 'Result',
redirect: '/result/success',
component: Layout,
meta: {
title: '结果页面',
icon: renderIcon(CheckCircleOutlined),
sort: 4,
},
children: [
{
path: 'success',
name: 'result-success',
meta: {
title: '成功页',
},
component: () => import('@/views/result/success.vue'),
},
{
path: 'fail',
name: 'result-fail',
meta: {
title: '失败页',
},
component: () => import('@/views/result/fail.vue'),
},
{
path: 'info',
name: 'result-info',
meta: {
title: '信息页',
},
component: () => import('@/views/result/info.vue'),
},
],
},
];
export default routes;

View File

@ -1,49 +0,0 @@
import { RouteRecordRaw } from 'vue-router';
import { Layout } from '@/router/constant';
import { SettingOutlined } from '@vicons/antd';
import { renderIcon } from '@/utils/index';
/**
* @param name , ,
* @param meta
* @param redirect , 访,
* @param meta.disabled
* @param meta.title
* @param meta.icon
* @param meta.keepAlive
* @param meta.sort
*
* */
const routes: Array<RouteRecordRaw> = [
{
path: '/setting',
name: 'Setting',
redirect: '/setting/account',
component: Layout,
meta: {
title: '设置页面',
icon: renderIcon(SettingOutlined),
sort: 5,
},
children: [
{
path: 'account',
name: 'setting-account',
meta: {
title: '个人设置',
},
component: () => import('@/views/setting/account/account.vue'),
},
{
path: 'system',
name: 'setting-system',
meta: {
title: '系统设置',
},
component: () => import('@/views/setting/system/system.vue'),
},
],
},
];
export default routes;

View File

@ -1,99 +0,0 @@
import { RouteRecordRaw } from 'vue-router';
import { Layout } from '@/router/constant';
import { OptionsSharp } from '@vicons/ionicons5';
import { renderIcon } from '@/utils/index';
import { NBadge } from 'naive-ui';
import { h } from 'vue';
/**
* @param name , ,
* @param meta
* @param redirect , 访,
* @param meta.disabled
* @param meta.title
* @param meta.icon
* @param meta.keepAlive
* @param meta.sort
*
* */
const routes: Array<RouteRecordRaw> = [
{
path: '/system',
name: 'System',
redirect: '/system/menu',
component: Layout,
meta: {
documentTitle: '系统管理',
title: () => [
h('span', {}, '系统管理'),
h(NBadge, {
dot: true,
type: 'error',
value: 1,
style: {
marginLeft: '5px',
},
color: '#ff3b30',
}),
],
icon: renderIcon(OptionsSharp),
sort: 1,
},
children: [
{
path: 'user',
name: 'system_user',
meta: {
title: '用户管理',
},
component: () => import('@/views/system/user/user.vue'),
},
{
path: 'menu',
name: 'system_menu',
meta: {
title: '菜单管理',
},
component: () => import('@/views/system/menu/index.vue'),
},
{
path: 'menu/table',
name: 'system_menu_table',
meta: {
documentTitle: '菜单管理2',
title: () => [
h('span', {}, '菜单管理2'),
h(NBadge, {
dot: true,
type: 'error',
value: 1,
style: {
marginLeft: '5px',
},
color: '#ff3b30',
}),
],
},
component: () => import('@/views/system/menu/table.vue'),
},
{
path: 'role',
name: 'system_role',
meta: {
title: '角色管理',
},
component: () => import('@/views/system/role/role.vue'),
},
{
path: 'dictionary',
name: 'system_dictionary',
meta: {
title: '字典管理',
},
component: () => import('@/views/system/dictionary/dictionary.vue'),
},
],
},
];
export default routes;

View File

@ -2,10 +2,12 @@ import type { RouteRecordRaw } from 'vue-router';
import { isNavigationFailure, Router } from 'vue-router'; import { isNavigationFailure, Router } from 'vue-router';
import { useUserStoreWidthOut } from '@/store/modules/user'; import { useUserStoreWidthOut } from '@/store/modules/user';
import { useAsyncRouteStoreWidthOut } from '@/store/modules/asyncRoute'; import { useAsyncRouteStoreWidthOut } from '@/store/modules/asyncRoute';
import { ACCESS_TOKEN } from '@/store/mutation-types'; import { ACCESS_TOKEN,FIRST_ROUTE } from '@/store/mutation-types';
import { storage } from '@/utils/Storage'; import { storage } from '@/utils/Storage';
import { PageEnum } from '@/enums/pageEnum'; import { PageEnum } from '@/enums/pageEnum';
import { ErrorPageRoute } from '@/router/base'; import { ErrorPageRoute } from '@/router/base';
import { findFirstRoutePath } from '@/router/generator-routers';
import {findTreeByPath} from "@/utils/auth";
import { useLoadingBar } from '@/hooks/web/useLoadingBar'; import { useLoadingBar } from '@/hooks/web/useLoadingBar';
const LOGIN_PATH = PageEnum.BASE_LOGIN; const LOGIN_PATH = PageEnum.BASE_LOGIN;
@ -19,10 +21,10 @@ export function createRouterGuards(router: Router) {
const asyncRouteStore = useAsyncRouteStoreWidthOut(); const asyncRouteStore = useAsyncRouteStoreWidthOut();
router.beforeEach(async (to, from, next) => { router.beforeEach(async (to, from, next) => {
loadingBar.start(); loadingBar.start();
if (from.path === LOGIN_PATH && to.name === 'errorPage') { // if (from.path === LOGIN_PATH && to.name === 'errorPage') {
next(PageEnum.BASE_HOME); // next(PageEnum.BASE_HOME);
return; // return;
} // }
// Whitelist can be directly entered // Whitelist can be directly entered
if (whitePathList.includes(to.path as PageEnum)) { if (whitePathList.includes(to.path as PageEnum)) {
@ -72,8 +74,14 @@ export function createRouterGuards(router: Router) {
if (isErrorPage === -1) { if (isErrorPage === -1) {
router.addRoute(ErrorPageRoute as unknown as RouteRecordRaw); router.addRoute(ErrorPageRoute as unknown as RouteRecordRaw);
} }
const firstRoutePath = findFirstRoutePath(routes)
const redirectPath = (from.query.redirect || to.path) as string; storage.set(FIRST_ROUTE,firstRoutePath)
if (from.path === LOGIN_PATH && (to.name === 'errorPage' || !to.name)) {
next(firstRoutePath);
return;
}
let formPath = findTreeByPath(routes,from.query.redirect).length > 0 ? from.query.redirect:to.path
const redirectPath = (formPath) as string;
const redirect = decodeURIComponent(redirectPath); const redirect = decodeURIComponent(redirectPath);
const nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect }; const nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect };
asyncRouteStore.setDynamicAddedRoute(true); asyncRouteStore.setDynamicAddedRoute(true);

View File

@ -1,8 +1,10 @@
import { renderIcon } from '@/utils/index'; import { renderIcon } from '@/utils/index';
import { DashboardOutlined, TableOutlined } from '@vicons/antd'; import * as AllIcons from '@vicons/antd';
const icons = Object.keys(AllIcons);
//前端路由图标映射表 //前端路由图标映射表
export const constantRouterIcon = { const constantRouterIcon = {};
DashboardOutlined: renderIcon(DashboardOutlined), icons.forEach(icon => {
TableOutlined: renderIcon(TableOutlined), constantRouterIcon[icon] = renderIcon(AllIcons[icon]);
}; });
export { constantRouterIcon };

View File

@ -20,6 +20,7 @@ export interface AppRouteRecordRaw extends Omit<RouteRecordRaw, 'meta'> {
export interface Meta { export interface Meta {
// 名称 // 名称
title: string; title: string;
label: string;
// 是否忽略权限 // 是否忽略权限
ignoreAuth?: boolean; ignoreAuth?: boolean;
//权限数组集合 //权限数组集合

View File

@ -2,15 +2,15 @@ export default {
table: { table: {
apiSetting: { apiSetting: {
// 当前页的字段名 // 当前页的字段名
pageField: 'page', pageField: 'pageNo',
// 每页数量字段名 // 每页数量字段名
sizeField: 'pageSize', sizeField: 'pageSize',
// 接口返回的数据字段名 // 接口返回的数据字段名
listField: 'list', listField: 'records',
// 接口返回总页数字段名 // 接口返回总页数字段名
totalField: 'pageCount', totalField: 'pages',
//总数字段名 //总数字段名
countField: 'itemCount', countField: 'total',
}, },
//默认分页数量 //默认分页数量
defaultPageSize: 10, defaultPageSize: 10,

View File

@ -49,7 +49,7 @@ const setting = {
showIcon: false, showIcon: false,
}, },
//菜单权限模式 FIXED 前端固定路由 BACK 动态获取 //菜单权限模式 FIXED 前端固定路由 BACK 动态获取
permissionMode: 'FIXED', permissionMode: 'BACK',
//是否开启路由动画 //是否开启路由动画
isPageAnimate: true, isPageAnimate: true,
//路由动画类型 默认消退 //路由动画类型 默认消退

View File

@ -5,7 +5,7 @@ import { ACCESS_TOKEN, CURRENT_USER, IS_LOCKSCREEN } from '@/store/mutation-type
import { ResultEnum } from '@/enums/httpEnum'; import { ResultEnum } from '@/enums/httpEnum';
const Storage = createStorage({ storage: localStorage }); const Storage = createStorage({ storage: localStorage });
import { getUserInfo, login, userLogout } from '@/api/system/user'; import { getUserInfo, login } from '@/api/system/user';
import { storage } from '@/utils/Storage'; import { storage } from '@/utils/Storage';
export interface IUserState { export interface IUserState {
@ -61,14 +61,12 @@ export const useUserStore = defineStore({
async login(userInfo) { async login(userInfo) {
try { try {
const response = await login(userInfo); const response = await login(userInfo);
const { result, code } = response; const {code,data } = response;
if (code === ResultEnum.SUCCESS) { if (code === ResultEnum.SUCCESS) {
const ex = 7 * 24 * 60 * 60 * 1000; const ex = 7 * 24 * 60 * 60 * 1000;
storage.set(ACCESS_TOKEN, result.token, ex); storage.set(ACCESS_TOKEN, data.access_token, ex);
storage.set(CURRENT_USER, result, ex);
storage.set(IS_LOCKSCREEN, false); storage.set(IS_LOCKSCREEN, false);
this.setToken(result.token); this.setToken(data.access_token);
this.setUserInfo(result);
} }
return Promise.resolve(response); return Promise.resolve(response);
} catch (e) { } catch (e) {
@ -98,19 +96,11 @@ export const useUserStore = defineStore({
// 登出 // 登出
async logout() { async logout() {
return new Promise((resolve, reject) => { this.setPermissions([]);
userLogout() this.setUserInfo('');
.then(() => { storage.remove(ACCESS_TOKEN);
this.setPermissions([]); storage.remove(CURRENT_USER);
this.setUserInfo({} as IUserState); return Promise.resolve('');
storage.remove(ACCESS_TOKEN);
storage.remove(CURRENT_USER);
return resolve(true);
})
.catch((error) => {
reject(error);
});
});
}, },
}, },
}); });

View File

@ -2,3 +2,4 @@ export const ACCESS_TOKEN = 'ACCESS-TOKEN'; // 用户token
export const CURRENT_USER = 'CURRENT-USER'; // 当前用户信息 export const CURRENT_USER = 'CURRENT-USER'; // 当前用户信息
export const IS_LOCKSCREEN = 'IS-LOCKSCREEN'; // 是否锁屏 export const IS_LOCKSCREEN = 'IS-LOCKSCREEN'; // 是否锁屏
export const TABS_ROUTES = 'TABS-ROUTES'; // 标签页 export const TABS_ROUTES = 'TABS-ROUTES'; // 标签页
export const FIRST_ROUTE = 'FIRST-ROUTE'; // 菜单第一个路由

View File

@ -39,3 +39,18 @@ export function urlToBase64(url: string, mineType?: string): Promise<string> {
img.src = url; img.src = url;
}); });
} }
/**
* base64 to file
* @param dataURL
*/
export function base64ToFile(dataurl:any, filename:any) {
let arr = dataurl.split(",");
let mime = arr[0].match(/:(.*?);/)[1];
let bstr = atob(arr[1]);
let len = bstr.length;
let u8arr = new Uint8Array(len);
while (len--) {
u8arr[len] = bstr.charCodeAt(len);
}
return new File([u8arr], filename, {type: mime});
}

View File

@ -53,35 +53,33 @@ const transform: AxiosTransform = {
return res.data; return res.data;
} }
const { data } = res;
const $dialog = useDialog(); const $dialog = useDialog();
const $message = useMessage(); const $message = useMessage();
if (!data) { if (!res.data) {
// return '[HTTP] Request has no return value'; // return '[HTTP] Request has no return value';
throw new Error('请求出错,请稍候重试'); throw new Error('请求出错,请稍候重试');
} }
// 这里 coderesultmessage为 后台统一的字段,需要修改为项目自己的接口返回格式 // 这里 coderesultmessage为 后台统一的字段,需要修改为项目自己的接口返回格式
const { code, result, message } = data; const { code, data, msg } = res.data;
// 请求成功 // 请求成功
const hasSuccess = data && Reflect.has(data, 'code') && code === ResultEnum.SUCCESS; const hasSuccess = data && code === ResultEnum.SUCCESS;
// 是否显示提示信息 // 是否显示提示信息
if (isShowMessage) { if (isShowMessage) {
if (hasSuccess && (successMessageText || isShowSuccessMessage)) { if (hasSuccess && (successMessageText || isShowSuccessMessage)) {
// 是否显示自定义信息提示 // 是否显示自定义信息提示
$dialog.success({ $dialog.success({
type: 'success', type: 'success',
content: successMessageText || message || '操作成功!', content: successMessageText || msg || '操作成功!',
}); });
} else if (!hasSuccess && (errorMessageText || isShowErrorMessage)) { } else if (!hasSuccess && (errorMessageText || isShowErrorMessage)) {
// 是否显示自定义信息提示 // 是否显示自定义信息提示
$message.error(message || errorMessageText || '操作失败!'); $message.error(msg || errorMessageText || '操作失败!');
} else if (!hasSuccess && options.errorMessageMode === 'modal') { } else if (!hasSuccess && options.errorMessageMode === 'modal') {
// errorMessageMode=custom-modal的时候会显示modal错误弹窗而不是消息提示用于一些比较重要的错误 // errorMessageMode=custom-modal的时候会显示modal错误弹窗而不是消息提示用于一些比较重要的错误
$dialog.info({ $dialog.info({
title: '提示', title: '提示',
content: message, content: msg,
positiveText: '确定', positiveText: '确定',
onPositiveClick: () => {}, onPositiveClick: () => {},
}); });
@ -90,10 +88,10 @@ const transform: AxiosTransform = {
// 接口请求成功,直接返回结果 // 接口请求成功,直接返回结果
if (code === ResultEnum.SUCCESS) { if (code === ResultEnum.SUCCESS) {
return result; return data;
} }
// 接口请求错误,统一提示错误信息 这里逻辑可以根据项目进行修改 // 接口请求错误,统一提示错误信息 这里逻辑可以根据项目进行修改
let errorMsg = message; let errorMsg = msg;
switch (code) { switch (code) {
// 请求失败 // 请求失败
case ResultEnum.ERROR: case ResultEnum.ERROR:
@ -242,7 +240,7 @@ function createAxios(opt?: Partial<CreateAxiosOptions>) {
deepMerge( deepMerge(
{ {
timeout: 10 * 1000, timeout: 10 * 1000,
authenticationScheme: '', authenticationScheme: 'Bearer',
// 接口前缀 // 接口前缀
prefixUrl: urlPrefix, prefixUrl: urlPrefix,
headers: { 'Content-Type': ContentTypeEnum.JSON }, headers: { 'Content-Type': ContentTypeEnum.JSON },

18
src/utils/useLockFn.ts Normal file
View File

@ -0,0 +1,18 @@
import { ref } from 'vue';
export function useLockFn(fn: (...args: any[]) => Promise<any>) {
const isLock = ref(false);
const lockFn = async (...args: any[]) => {
if (isLock.value) return;
isLock.value = true;
try {
await fn(...args);
} finally {
isLock.value = false;
}
};
return {
isLock,
lockFn,
};
}

View File

@ -4531,7 +4531,7 @@ mz@^2.7.0:
object-assign "^4.0.1" object-assign "^4.0.1"
thenify-all "^1.0.0" thenify-all "^1.0.0"
naive-ui@^2.39.0: naive-ui@^2.40.0:
version "2.40.1" version "2.40.1"
resolved "https://registry.npmmirror.com/naive-ui/-/naive-ui-2.40.1.tgz#64d9570dae4286b89979a582713f6f824c25230b" resolved "https://registry.npmmirror.com/naive-ui/-/naive-ui-2.40.1.tgz#64d9570dae4286b89979a582713f6f824c25230b"
integrity sha512-3NkL+vLRQZKQxCHXa+7xiD6oM74OrQELaehDkGYRYpr6kjT+JJB+Z7h+5LC70gn8VkbgCAETv0+uRWF+6MLlgQ== integrity sha512-3NkL+vLRQZKQxCHXa+7xiD6oM74OrQELaehDkGYRYpr6kjT+JJB+Z7h+5LC70gn8VkbgCAETv0+uRWF+6MLlgQ==
@ -5820,16 +5820,7 @@ string-argv@0.3.1:
resolved "https://registry.npmmirror.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da" resolved "https://registry.npmmirror.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da"
integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg== integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==
"string-width-cjs@npm:string-width@^4.2.0": "string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
version "4.2.3" version "4.2.3"
resolved "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" resolved "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@ -5863,14 +5854,7 @@ stringify-object@3.3.0:
is-obj "^1.0.1" is-obj "^1.0.1"
is-regexp "^1.0.0" is-regexp "^1.0.0"
"strip-ansi-cjs@npm:strip-ansi@^6.0.1": "strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1" version "6.0.1"
resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@ -6782,7 +6766,7 @@ word@~0.3.0:
resolved "https://registry.npmmirror.com/word/-/word-0.3.0.tgz#8542157e4f8e849f4a363a288992d47612db9961" resolved "https://registry.npmmirror.com/word/-/word-0.3.0.tgz#8542157e4f8e849f4a363a288992d47612db9961"
integrity sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA== integrity sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
version "7.0.0" version "7.0.0"
resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@ -6800,15 +6784,6 @@ wrap-ansi@^6.2.0:
string-width "^4.1.0" string-width "^4.1.0"
strip-ansi "^6.0.0" strip-ansi "^6.0.0"
wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"
wrap-ansi@^8.1.0: wrap-ansi@^8.1.0:
version "8.1.0" version "8.1.0"
resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"