mirror of
https://gitee.com/log4j/pig-ui.git
synced 2024-12-23 05:40:20 +08:00
✨ Introducing new features. 对接后端路由
This commit is contained in:
parent
8045001e8b
commit
aaf98bf771
@ -1,31 +1,19 @@
|
||||
import request from '/@/utils/request';
|
||||
|
||||
/**
|
||||
* 以下为模拟接口地址,gitee 的不通,就换自己的真实接口地址
|
||||
*
|
||||
* (不建议写成 request.post(xxx),因为这样 post 时,无法 params 与 data 同时传参)
|
||||
*
|
||||
* 后端控制菜单模拟json,路径在 https://gitee.com/lyt-top/vue-next-admin-images/tree/master/menu
|
||||
* 后端控制路由,isRequestRoutes 为 true,则开启后端控制路由
|
||||
* @method getAdminMenu 获取后端动态路由菜单(admin)
|
||||
* @method getTestMenu 获取后端动态路由菜单(test)
|
||||
*/
|
||||
export function useMenuApi() {
|
||||
return {
|
||||
getAdminMenu: (params?: object) => {
|
||||
return request({
|
||||
url: '/gitee/lyt-top/vue-next-admin-images/raw/master/menu/adminMenu.json',
|
||||
url: '/admin/menu',
|
||||
method: 'get',
|
||||
params,
|
||||
});
|
||||
},
|
||||
getTestMenu: (params?: object) => {
|
||||
return request({
|
||||
url: '/gitee/lyt-top/vue-next-admin-images/raw/master/menu/testMenu.json',
|
||||
method: 'get',
|
||||
params,
|
||||
});
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -65,7 +65,6 @@ const handleNodeClick = (item: any) => {
|
||||
}
|
||||
|
||||
const getDeptTree = () => {
|
||||
console.log('3333')
|
||||
if(query instanceof Function){
|
||||
state.localLoading = true
|
||||
const result = query(unref(searchName))
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { ElMessage } from "element-plus";
|
||||
import { useMessage } from "./message";
|
||||
import {ElMessage} from "element-plus";
|
||||
import {useMessage} from "./message";
|
||||
import request from "/@/utils/request";
|
||||
|
||||
export interface BasicTableProps {
|
||||
@ -87,7 +87,7 @@ export function useTable(options?: BasicTableProps) {
|
||||
state.dataList = state.isPage ? res.data.records : res.data
|
||||
state.pagination!.total = state.isPage ? res.data.total : 0
|
||||
}).catch(err => {
|
||||
ElMessage.error(err.msg)
|
||||
ElMessage.error(err.response.data.msg)
|
||||
}).finally(() => {
|
||||
state.loading = false;
|
||||
})
|
||||
@ -114,7 +114,7 @@ export function useTable(options?: BasicTableProps) {
|
||||
|
||||
// 排序
|
||||
const sortChangeHandle = (data: any) => {
|
||||
const { prop, order } = data
|
||||
const {prop, order} = data
|
||||
|
||||
if (prop && order) {
|
||||
state.pagination!.order = prop
|
||||
@ -138,7 +138,7 @@ export function useTable(options?: BasicTableProps) {
|
||||
method: 'get',
|
||||
responseType: 'blob',
|
||||
params: query
|
||||
}).then(response => {
|
||||
}).then((response: any) => {
|
||||
// 处理返回的文件流
|
||||
if (response && response.size === 0) {
|
||||
useMessage().error('内容为空,无法下载')
|
||||
|
14
src/i18n/pages/form/en.ts
Normal file
14
src/i18n/pages/form/en.ts
Normal file
@ -0,0 +1,14 @@
|
||||
// 定义通用内容
|
||||
export default {
|
||||
common: {
|
||||
queryBtn: 'query',
|
||||
addBtn: 'add',
|
||||
editBtn: 'edit',
|
||||
delBtn: 'delete',
|
||||
exportBtn: 'export',
|
||||
importBtn: 'import',
|
||||
importUserTip: 'import user',
|
||||
queryDeptTip: 'input deptName',
|
||||
resetBtn: 'reset'
|
||||
}
|
||||
};
|
13
src/i18n/pages/form/zh-cn.ts
Normal file
13
src/i18n/pages/form/zh-cn.ts
Normal file
@ -0,0 +1,13 @@
|
||||
// 定义通用内容
|
||||
export default {
|
||||
common: {
|
||||
queryBtn: '查询',
|
||||
addBtn: '新 增',
|
||||
editBtn: '修 改',
|
||||
delBtn: '删除',
|
||||
exportBtn: '导出',
|
||||
importBtn: '导入',
|
||||
queryDeptTip: '请输入部门名称',
|
||||
resetBtn: '重置'
|
||||
}
|
||||
};
|
@ -537,7 +537,7 @@ onBeforeMount(() => {
|
||||
// 监听布局配置开启 TagsView 共用,为了演示还原默认值
|
||||
mittBus.on('openShareTagsView', () => {
|
||||
if (getThemeConfig.value.isShareTagsView) {
|
||||
router.push('/home');
|
||||
router.push('/home/index');
|
||||
state.tagsViewList = [];
|
||||
state.tagsViewRoutesList.map((v: RouteItem) => {
|
||||
if (v.meta?.isAffix && !v.meta.isHide) {
|
||||
|
@ -19,7 +19,7 @@
|
||||
<el-menu-item :index="val.path" :key="val.path">
|
||||
<SvgIcon :name="val.meta.icon" />
|
||||
<template #title v-if="!val.meta.isLink || (val.meta.isLink && val.meta.isIframe)">
|
||||
<span>{{ $t(val.meta.title) }}</span>
|
||||
<span>{{ $t(val.meta.title) }} </span>
|
||||
</template>
|
||||
<template #title v-else>
|
||||
<a class="w100" @click.prevent="onALinkClick(val)">{{ $t(val.meta.title) }}</a>
|
||||
|
@ -1,15 +1,15 @@
|
||||
import { RouteRecordRaw } from 'vue-router';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import {RouteRecordRaw} from 'vue-router';
|
||||
import {storeToRefs} from 'pinia';
|
||||
import pinia from '/@/stores/index';
|
||||
import { useUserInfo } from '/@/stores/userInfo';
|
||||
import { useRequestOldRoutes } from '/@/stores/requestOldRoutes';
|
||||
import { Session } from '/@/utils/storage';
|
||||
import { NextLoading } from '/@/utils/loading';
|
||||
import { dynamicRoutes, notFoundAndNoPower } from '/@/router/route';
|
||||
import { formatTwoStageRoutes, formatFlatteningRoutes, router } from '/@/router/index';
|
||||
import { useRoutesList } from '/@/stores/routesList';
|
||||
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
|
||||
import { useMenuApi } from '/@/api/menu/index';
|
||||
import {useUserInfo} from '/@/stores/userInfo';
|
||||
import {useRequestOldRoutes} from '/@/stores/requestOldRoutes';
|
||||
import {Session} from '/@/utils/storage';
|
||||
import {NextLoading} from '/@/utils/loading';
|
||||
import {dynamicRoutes, notFoundAndNoPower} from '/@/router/route';
|
||||
import {formatTwoStageRoutes, formatFlatteningRoutes, router} from '/@/router/index';
|
||||
import {useRoutesList} from '/@/stores/routesList';
|
||||
import {useTagsViewRoutes} from '/@/stores/tagsViewRoutes';
|
||||
import {useMenuApi} from '/@/api/menu/index';
|
||||
|
||||
// 后端控制路由
|
||||
|
||||
@ -23,7 +23,7 @@ const menuApi = useMenuApi();
|
||||
*/
|
||||
const layouModules: any = import.meta.glob('../layout/routerView/*.{vue,tsx}');
|
||||
const viewsModules: any = import.meta.glob('../views/**/*.{vue,tsx}');
|
||||
const dynamicViewsModules: Record<string, Function> = Object.assign({}, { ...layouModules }, { ...viewsModules });
|
||||
const dynamicViewsModules: Record<string, Function> = Object.assign({}, {...layouModules}, {...viewsModules});
|
||||
|
||||
/**
|
||||
* 后端控制路由:初始化方法,防止刷新时路由丢失
|
||||
@ -34,26 +34,26 @@ const dynamicViewsModules: Record<string, Function> = Object.assign({}, { ...lay
|
||||
* @method setFilterMenuAndCacheTagsViewRoutes 设置路由到 pinia routesList 中(已处理成多级嵌套路由)及缓存多级嵌套数组处理后的一维数组
|
||||
*/
|
||||
export async function initBackEndControlRoutes() {
|
||||
// 界面 loading 动画开始执行
|
||||
if (window.nextLoading === undefined) NextLoading.start();
|
||||
// 无 token 停止执行下一步
|
||||
if (!Session.get('token')) return false;
|
||||
// 触发初始化用户信息 pinia
|
||||
// https://gitee.com/lyt-top/vue-next-admin/issues/I5F1HP
|
||||
await useUserInfo().setUserInfos();
|
||||
// 获取路由菜单数据
|
||||
const res = await getBackEndControlRoutes();
|
||||
// 无登录权限时,添加判断
|
||||
// https://gitee.com/lyt-top/vue-next-admin/issues/I64HVO
|
||||
if (res.data.length <= 0) return Promise.resolve(true);
|
||||
// 存储接口原始路由(未处理component),根据需求选择使用
|
||||
useRequestOldRoutes().setRequestOldRoutes(JSON.parse(JSON.stringify(res.data)));
|
||||
// 处理路由(component),替换 dynamicRoutes(/@/router/route)第一个顶级 children 的路由
|
||||
dynamicRoutes[0].children = await backEndComponent(res.data);
|
||||
// 添加动态路由
|
||||
await setAddRoute();
|
||||
// 设置路由到 pinia routesList 中(已处理成多级嵌套路由)及缓存多级嵌套数组处理后的一维数组
|
||||
await setFilterMenuAndCacheTagsViewRoutes();
|
||||
// 界面 loading 动画开始执行
|
||||
if (window.nextLoading === undefined) NextLoading.start();
|
||||
// 无 token 停止执行下一步
|
||||
if (!Session.get('token')) return false;
|
||||
// 触发初始化用户信息 pinia
|
||||
// https://gitee.com/lyt-top/vue-next-admin/issues/I5F1HP
|
||||
await useUserInfo().setUserInfos();
|
||||
// 获取路由菜单数据
|
||||
const res = await getBackEndControlRoutes();
|
||||
// 无登录权限时,添加判断
|
||||
// https://gitee.com/lyt-top/vue-next-admin/issues/I64HVO
|
||||
if (res.data.length <= 0) return Promise.resolve(true);
|
||||
// 存储接口原始路由(未处理component),根据需求选择使用
|
||||
useRequestOldRoutes().setRequestOldRoutes(JSON.parse(JSON.stringify(res.data)));
|
||||
// 处理路由(component),替换 dynamicRoutes(/@/router/route)第一个顶级 children 的路由
|
||||
dynamicRoutes[0].children = await backEndComponent(res.data);
|
||||
// 添加动态路由
|
||||
await setAddRoute();
|
||||
// 设置路由到 pinia routesList 中(已处理成多级嵌套路由)及缓存多级嵌套数组处理后的一维数组
|
||||
await setFilterMenuAndCacheTagsViewRoutes();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -62,9 +62,9 @@ export async function initBackEndControlRoutes() {
|
||||
* @description 用于 tagsView、菜单搜索中:未过滤隐藏的(isHide)
|
||||
*/
|
||||
export async function setFilterMenuAndCacheTagsViewRoutes() {
|
||||
const storesRoutesList = useRoutesList(pinia);
|
||||
storesRoutesList.setRoutesList(dynamicRoutes[0].children as any);
|
||||
setCacheTagsViewRoutes();
|
||||
const storesRoutesList = useRoutesList(pinia);
|
||||
storesRoutesList.setRoutesList(dynamicRoutes[0].children as any);
|
||||
setCacheTagsViewRoutes();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -72,8 +72,8 @@ export async function setFilterMenuAndCacheTagsViewRoutes() {
|
||||
* @description 用于 tagsView、菜单搜索中:未过滤隐藏的(isHide)
|
||||
*/
|
||||
export function setCacheTagsViewRoutes() {
|
||||
const storesTagsView = useTagsViewRoutes(pinia);
|
||||
storesTagsView.setTagsViewRoutes(formatTwoStageRoutes(formatFlatteningRoutes(dynamicRoutes))[0].children);
|
||||
const storesTagsView = useTagsViewRoutes(pinia);
|
||||
storesTagsView.setTagsViewRoutes(formatTwoStageRoutes(formatFlatteningRoutes(dynamicRoutes))[0].children);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -82,11 +82,11 @@ export function setCacheTagsViewRoutes() {
|
||||
* @returns 返回替换后的路由数组
|
||||
*/
|
||||
export function setFilterRouteEnd() {
|
||||
let filterRouteEnd: any = formatTwoStageRoutes(formatFlatteningRoutes(dynamicRoutes));
|
||||
// notFoundAndNoPower 防止 404、401 不在 layout 布局中,不设置的话,404、401 界面将全屏显示
|
||||
// 关联问题 No match found for location with path 'xxx'
|
||||
filterRouteEnd[0].children = [...filterRouteEnd[0].children, ...notFoundAndNoPower];
|
||||
return filterRouteEnd;
|
||||
let filterRouteEnd: any = formatTwoStageRoutes(formatFlatteningRoutes(dynamicRoutes));
|
||||
// notFoundAndNoPower 防止 404、401 不在 layout 布局中,不设置的话,404、401 界面将全屏显示
|
||||
// 关联问题 No match found for location with path 'xxx'
|
||||
filterRouteEnd[0].children = [...filterRouteEnd[0].children, ...notFoundAndNoPower];
|
||||
return filterRouteEnd;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -96,9 +96,9 @@ export function setFilterRouteEnd() {
|
||||
* @link 参考:https://next.router.vuejs.org/zh/api/#addroute
|
||||
*/
|
||||
export async function setAddRoute() {
|
||||
await setFilterRouteEnd().forEach((route: RouteRecordRaw) => {
|
||||
router.addRoute(route);
|
||||
});
|
||||
await setFilterRouteEnd().forEach((route: RouteRecordRaw) => {
|
||||
router.addRoute(route);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -107,23 +107,16 @@ export async function setAddRoute() {
|
||||
* @returns 返回后端路由菜单数据
|
||||
*/
|
||||
export function getBackEndControlRoutes() {
|
||||
// 模拟 admin 与 test
|
||||
const stores = useUserInfo(pinia);
|
||||
const { userInfos } = storeToRefs(stores);
|
||||
const auth = userInfos.value.roles[0];
|
||||
// 管理员 admin
|
||||
if (auth === 'admin') return menuApi.getAdminMenu();
|
||||
// 其它用户 test
|
||||
else return menuApi.getTestMenu();
|
||||
return menuApi.getAdminMenu();
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新请求后端路由菜单接口
|
||||
* @description 用于菜单管理界面刷新菜单(未进行测试)
|
||||
* @description 路径:/src/views/system/menu/component/addMenu.vue
|
||||
* @description 路径:/src/views/admin/menu/component/addMenu.vue
|
||||
*/
|
||||
export async function setBackEndControlRefreshRoutes() {
|
||||
await getBackEndControlRoutes();
|
||||
await getBackEndControlRoutes();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -132,12 +125,12 @@ export async function setBackEndControlRefreshRoutes() {
|
||||
* @returns 返回处理成函数后的 component
|
||||
*/
|
||||
export function backEndComponent(routes: any) {
|
||||
if (!routes) return;
|
||||
return routes.map((item: any) => {
|
||||
if (item.component) item.component = dynamicImport(dynamicViewsModules, item.component as string);
|
||||
item.children && backEndComponent(item.children);
|
||||
return item;
|
||||
});
|
||||
if (!routes) return;
|
||||
return routes.map((item: any) => {
|
||||
if (item.path) item.component = dynamicImport(dynamicViewsModules, item.path as string);
|
||||
item.children && backEndComponent(item.children);
|
||||
return item;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -147,16 +140,18 @@ export function backEndComponent(routes: any) {
|
||||
* @returns 返回处理成函数后的 component
|
||||
*/
|
||||
export function dynamicImport(dynamicViewsModules: Record<string, Function>, component: string) {
|
||||
const keys = Object.keys(dynamicViewsModules);
|
||||
const matchKeys = keys.filter((key) => {
|
||||
const k = key.replace(/..\/views|../, '');
|
||||
return k.startsWith(`${component}`) || k.startsWith(`/${component}`);
|
||||
});
|
||||
if (matchKeys?.length === 1) {
|
||||
const matchKey = matchKeys[0];
|
||||
return dynamicViewsModules[matchKey];
|
||||
}
|
||||
if (matchKeys?.length > 1) {
|
||||
return false;
|
||||
}
|
||||
const keys = Object.keys(dynamicViewsModules);
|
||||
|
||||
|
||||
const matchKeys = keys.filter((key) => {
|
||||
const k = key.replace(/..\/views|../, '');
|
||||
return k.startsWith(`${component}`) || k.startsWith(`/${component}`);
|
||||
});
|
||||
if (matchKeys?.length === 1) {
|
||||
const matchKey = matchKeys[0];
|
||||
return dynamicViewsModules[matchKey];
|
||||
}
|
||||
if (matchKeys?.length > 1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -5,10 +5,8 @@ import pinia from '/@/stores/index';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useKeepALiveNames } from '/@/stores/keepAliveNames';
|
||||
import { useRoutesList } from '/@/stores/routesList';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
import { Session } from '/@/utils/storage';
|
||||
import { staticRoutes, notFoundAndNoPower } from '/@/router/route';
|
||||
import { initFrontEndControlRoutes } from '/@/router/frontEnd';
|
||||
import { initBackEndControlRoutes } from '/@/router/backEnd';
|
||||
|
||||
/**
|
||||
@ -20,11 +18,6 @@ import { initBackEndControlRoutes } from '/@/router/backEnd';
|
||||
* 2、后端控制:路由菜单由后端返回(有菜单管理界面、有角色管理界面)
|
||||
*/
|
||||
|
||||
// 读取 `/src/stores/themeConfig.ts` 是否开启后端控制路由配置
|
||||
const storesThemeConfig = useThemeConfig(pinia);
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
const { isRequestRoutes } = themeConfig.value;
|
||||
|
||||
/**
|
||||
* 创建一个可以被 Vue 应用程序使用的路由实例
|
||||
* @method createRouter(options: RouterOptions): Router
|
||||
@ -110,17 +103,9 @@ router.beforeEach(async (to, from, next) => {
|
||||
const storesRoutesList = useRoutesList(pinia);
|
||||
const { routesList } = storeToRefs(storesRoutesList);
|
||||
if (routesList.value.length === 0) {
|
||||
if (isRequestRoutes) {
|
||||
// 后端控制路由:路由数据初始化,防止刷新时丢失
|
||||
await initBackEndControlRoutes();
|
||||
// 解决刷新时,一直跳 404 页面问题,关联问题 No match found for location with path 'xxx'
|
||||
// to.query 防止页面刷新时,普通路由带参数时,参数丢失。动态路由(xxx/:id/:name")isDynamic 无需处理
|
||||
next({ path: to.path, query: to.query });
|
||||
} else {
|
||||
// https://gitee.com/lyt-top/vue-next-admin/issues/I5F1HP
|
||||
await initFrontEndControlRoutes();
|
||||
next({ path: to.path, query: to.query });
|
||||
}
|
||||
// 后端控制路由:路由数据初始化,防止刷新时丢失
|
||||
await initBackEndControlRoutes();
|
||||
next({ path: to.path, query: to.query });
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { RouteRecordRaw } from 'vue-router';
|
||||
import {RouteRecordRaw} from 'vue-router';
|
||||
|
||||
/**
|
||||
* 建议:路由 path 路径与文件夹名称相同,找文件可浏览器地址找,方便定位文件位置
|
||||
@ -18,16 +18,16 @@ import { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
// 扩展 RouteMeta 接口
|
||||
declare module 'vue-router' {
|
||||
interface RouteMeta {
|
||||
title?: string;
|
||||
isLink?: string;
|
||||
isHide?: boolean;
|
||||
isKeepAlive?: boolean;
|
||||
isAffix?: boolean;
|
||||
isIframe?: boolean;
|
||||
roles?: string[];
|
||||
icon?: string;
|
||||
}
|
||||
interface RouteMeta {
|
||||
title?: string;
|
||||
isLink?: string;
|
||||
isHide?: boolean;
|
||||
isKeepAlive?: boolean;
|
||||
isAffix?: boolean;
|
||||
isIframe?: boolean;
|
||||
roles?: string[];
|
||||
icon?: string;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -38,155 +38,16 @@ declare module 'vue-router' {
|
||||
* @returns 返回路由菜单数据
|
||||
*/
|
||||
export const dynamicRoutes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: '/',
|
||||
name: '/',
|
||||
component: () => import('/@/layout/index.vue'),
|
||||
redirect: '/home',
|
||||
meta: {
|
||||
isKeepAlive: true,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '/home',
|
||||
name: 'home',
|
||||
component: () => import('/@/views/home/index.vue'),
|
||||
meta: {
|
||||
title: 'router.home',
|
||||
isLink: '',
|
||||
isHide: false,
|
||||
isKeepAlive: true,
|
||||
isAffix: true,
|
||||
isIframe: false,
|
||||
roles: ['admin', 'common'],
|
||||
icon: 'iconfont icon-shouye',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/system',
|
||||
name: 'system',
|
||||
component: () => import('/@/layout/routerView/parent.vue'),
|
||||
redirect: '/system/menu',
|
||||
meta: {
|
||||
title: 'router.system',
|
||||
isLink: '',
|
||||
isHide: false,
|
||||
isKeepAlive: true,
|
||||
isAffix: false,
|
||||
isIframe: false,
|
||||
roles: ['admin', 'common'],
|
||||
icon: 'iconfont icon-xitongshezhi',
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '/system/menu',
|
||||
name: 'systemMenu',
|
||||
component: () => import('/@/views/system/menu/index.vue'),
|
||||
meta: {
|
||||
title: 'router.systemMenu',
|
||||
isLink: '',
|
||||
isHide: false,
|
||||
isKeepAlive: true,
|
||||
isAffix: false,
|
||||
isIframe: false,
|
||||
roles: ['admin', 'common'],
|
||||
icon: 'iconfont icon-caidan',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/system/role',
|
||||
name: 'systemRole',
|
||||
component: () => import('/@/views/system/role/index.vue'),
|
||||
meta: {
|
||||
title: 'router.systemRole',
|
||||
isLink: '',
|
||||
isHide: false,
|
||||
isKeepAlive: true,
|
||||
isAffix: false,
|
||||
isIframe: false,
|
||||
roles: ['admin', 'common'],
|
||||
icon: 'ele-ColdDrink',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/system/user',
|
||||
name: 'systemUser',
|
||||
component: () => import('/@/views/system/user/index.vue'),
|
||||
meta: {
|
||||
title: 'router.systemUser',
|
||||
isLink: '',
|
||||
isHide: false,
|
||||
isKeepAlive: true,
|
||||
isAffix: false,
|
||||
isIframe: false,
|
||||
roles: ['admin', 'common'],
|
||||
icon: 'iconfont icon-icon-',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/system/dept',
|
||||
name: 'systemDept',
|
||||
component: () => import('/@/views/system/dept/index.vue'),
|
||||
meta: {
|
||||
title: 'router.systemDept',
|
||||
isLink: '',
|
||||
isHide: false,
|
||||
isKeepAlive: true,
|
||||
isAffix: false,
|
||||
isIframe: false,
|
||||
roles: ['admin', 'common'],
|
||||
icon: 'ele-OfficeBuilding',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/system/dic',
|
||||
name: 'systemDic',
|
||||
component: () => import('/@/views/system/dic/index.vue'),
|
||||
meta: {
|
||||
title: 'router.systemDic',
|
||||
isLink: '',
|
||||
isHide: false,
|
||||
isKeepAlive: true,
|
||||
isAffix: false,
|
||||
isIframe: false,
|
||||
roles: ['admin', 'common'],
|
||||
icon: 'ele-SetUp',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/personal',
|
||||
name: 'personal',
|
||||
component: () => import('/@/views/personal/index.vue'),
|
||||
meta: {
|
||||
title: 'router.personal',
|
||||
isLink: '',
|
||||
isHide: false,
|
||||
isKeepAlive: true,
|
||||
isAffix: false,
|
||||
isIframe: false,
|
||||
roles: ['admin', 'common'],
|
||||
icon: 'iconfont icon-gerenzhongxin',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/tools',
|
||||
name: 'tools',
|
||||
component: () => import('/@/views/tools/index.vue'),
|
||||
meta: {
|
||||
title: 'router.tools',
|
||||
isLink: '',
|
||||
isHide: false,
|
||||
isKeepAlive: true,
|
||||
isAffix: false,
|
||||
isIframe: false,
|
||||
roles: ['admin', 'common'],
|
||||
icon: 'iconfont icon-gongju',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
name: '/',
|
||||
component: () => import('/@/layout/index.vue'),
|
||||
redirect: '/home/index',
|
||||
meta: {
|
||||
isKeepAlive: true,
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
@ -194,24 +55,24 @@ export const dynamicRoutes: Array<RouteRecordRaw> = [
|
||||
* @link 参考:https://next.router.vuejs.org/zh/guide/essentials/history-mode.html#netlify
|
||||
*/
|
||||
export const notFoundAndNoPower = [
|
||||
{
|
||||
path: '/:path(.*)*',
|
||||
name: 'notFound',
|
||||
component: () => import('/@/views/error/404.vue'),
|
||||
meta: {
|
||||
title: 'staticRoutes.notFound',
|
||||
isHide: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/401',
|
||||
name: 'noPower',
|
||||
component: () => import('/@/views/error/401.vue'),
|
||||
meta: {
|
||||
title: 'staticRoutes.noPower',
|
||||
isHide: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/:path(.*)*',
|
||||
name: 'notFound',
|
||||
component: () => import('/@/views/error/404.vue'),
|
||||
meta: {
|
||||
title: 'staticRoutes.notFound',
|
||||
isHide: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/401',
|
||||
name: 'noPower',
|
||||
component: () => import('/@/views/error/401.vue'),
|
||||
meta: {
|
||||
title: 'staticRoutes.noPower',
|
||||
isHide: true,
|
||||
},
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
@ -221,12 +82,12 @@ export const notFoundAndNoPower = [
|
||||
* @returns 返回路由菜单数据
|
||||
*/
|
||||
export const staticRoutes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: '/login',
|
||||
name: 'login',
|
||||
component: () => import('/@/views/login/index.vue'),
|
||||
meta: {
|
||||
title: '登录',
|
||||
},
|
||||
}
|
||||
{
|
||||
path: '/login',
|
||||
name: 'login',
|
||||
component: () => import('/@/views/login/index.vue'),
|
||||
meta: {
|
||||
title: '登录',
|
||||
},
|
||||
}
|
||||
];
|
||||
|
@ -131,7 +131,7 @@ export const useThemeConfig = defineStore('themeConfig', {
|
||||
* 后端控制路由
|
||||
*/
|
||||
// 是否开启后端控制路由
|
||||
isRequestRoutes: false,
|
||||
isRequestRoutes: true,
|
||||
|
||||
/**
|
||||
* 全局网站标题 / 副标题
|
||||
|
@ -1,51 +1,50 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import {defineStore} from 'pinia';
|
||||
import Cookies from 'js-cookie';
|
||||
import { Session } from '/@/utils/storage';
|
||||
import { login } from '/@//api/login/index'
|
||||
import other from '/@/utils/other'
|
||||
import {Session} from '/@/utils/storage';
|
||||
import {login} from '/@//api/login/index'
|
||||
import other from '/@/utils/other'
|
||||
|
||||
/**
|
||||
* 用户信息
|
||||
* @methods setUserInfos 设置用户信息
|
||||
*/
|
||||
export const useUserInfo = defineStore('userInfo', {
|
||||
state: (): UserInfosState => ({
|
||||
userInfos: {
|
||||
userName: '',
|
||||
photo: '',
|
||||
time: 0,
|
||||
roles: [],
|
||||
authBtnList: [],
|
||||
},
|
||||
}),
|
||||
actions: {
|
||||
async login(data: any){
|
||||
data.grant_type = 'password'
|
||||
data.scope = 'server'
|
||||
// 密码加密
|
||||
const user = other.encryption({
|
||||
data: data,
|
||||
key: 'pigxpigxpigxpigx',
|
||||
param: ['password']
|
||||
})
|
||||
return new Promise((resolve, reject) => {
|
||||
login(user).then(res =>{
|
||||
// 存储token 信息
|
||||
Session.set('token', res.access_token);
|
||||
// 模拟数据,对接接口时,记得删除多余代码及对应依赖的引入。用于 `/src/stores/userInfo.ts` 中不同用户登录判断(模拟数据)
|
||||
Cookies.set('userName', res.username);
|
||||
resolve(res)
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
state: (): UserInfosState => ({
|
||||
userInfos: {
|
||||
userName: '',
|
||||
photo: '',
|
||||
time: 0,
|
||||
roles: [],
|
||||
authBtnList: [],
|
||||
},
|
||||
}),
|
||||
actions: {
|
||||
async login(data: any) {
|
||||
data.grant_type = 'password'
|
||||
data.scope = 'server'
|
||||
// 密码加密
|
||||
const user = other.encryption({
|
||||
data: data,
|
||||
key: 'pigxpigxpigxpigx',
|
||||
param: ['password']
|
||||
})
|
||||
return new Promise((resolve, reject) => {
|
||||
login(user).then(res => {
|
||||
// 存储token 信息
|
||||
Session.set('token', res.access_token);
|
||||
// 模拟数据,对接接口时,记得删除多余代码及对应依赖的引入。用于 `/src/stores/userInfo.ts` 中不同用户登录判断(模拟数据)
|
||||
Cookies.set('userName', res.username);
|
||||
resolve(res)
|
||||
}).catch((err) => {
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
|
||||
},
|
||||
async setUserInfos() {
|
||||
// 存储用户信息到浏览器缓存
|
||||
if (Session.get('userInfo')) {
|
||||
this.userInfos = Session.get('userInfo');
|
||||
},
|
||||
async setUserInfos() {
|
||||
// 存储用户信息到浏览器缓存
|
||||
if (Session.get('userInfo')) {
|
||||
this.userInfos = Session.get('userInfo');
|
||||
} else {
|
||||
const userInfos: any = await this.getApiUserInfo();
|
||||
this.userInfos = userInfos;
|
||||
@ -76,7 +75,7 @@ export const useUserInfo = defineStore('userInfo', {
|
||||
} else {
|
||||
defaultRoles = testRoles;
|
||||
defaultAuthBtnList = testAuthBtnList;
|
||||
}
|
||||
}
|
||||
// 用户信息模拟数据
|
||||
const userInfos = {
|
||||
userName: userName,
|
||||
@ -91,6 +90,7 @@ export const useUserInfo = defineStore('userInfo', {
|
||||
resolve(userInfos);
|
||||
}, 0);
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
},
|
||||
});
|
||||
|
13
src/utils/errorCode.ts
Normal file
13
src/utils/errorCode.ts
Normal file
@ -0,0 +1,13 @@
|
||||
export default {
|
||||
'000': '操作太频繁,请勿重复请求',
|
||||
'401': '当前操作没有权限',
|
||||
'403': '当前操作没有权限',
|
||||
'404': '资源不存在',
|
||||
'417': '未绑定登录账号,请使用密码登录后绑定',
|
||||
'423': '演示环境不能操作,如需了解联系我们',
|
||||
'426': '用户名不存在或密码错误',
|
||||
'428': '验证码错误,请重新输入',
|
||||
'429': '请求过频繁',
|
||||
'479': '演示环境,没有权限操作',
|
||||
'default': '系统未知错误,请反馈给管理员'
|
||||
}
|
@ -1,66 +1,63 @@
|
||||
import axios, {AxiosInstance, AxiosRequestConfig, InternalAxiosRequestConfig} from 'axios';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { Session } from '/@/utils/storage';
|
||||
import errorCode from './errorCode'
|
||||
import {ElMessage, ElMessageBox} from 'element-plus';
|
||||
import {Session} from '/@/utils/storage';
|
||||
import qs from 'qs';
|
||||
|
||||
// 配置新建一个 axios 实例
|
||||
const service: AxiosInstance = axios.create({
|
||||
baseURL: import.meta.env.VITE_API_URL,
|
||||
timeout: 50000,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
paramsSerializer: {
|
||||
serialize(params) {
|
||||
return qs.stringify(params, { allowDots: true });
|
||||
},
|
||||
},
|
||||
baseURL: import.meta.env.VITE_API_URL,
|
||||
timeout: 50000,
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
paramsSerializer: {
|
||||
serialize(params) {
|
||||
return qs.stringify(params, {allowDots: true});
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// 添加请求拦截器
|
||||
service.interceptors.request.use((config: InternalAxiosRequestConfig) => {
|
||||
// 在发送请求之前做些什么 token
|
||||
if (Session.get('token')) {
|
||||
config.headers!['Authorization'] = `Bearer ${Session.get('token')}`;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
// 对请求错误做些什么
|
||||
return Promise.reject(error);
|
||||
}
|
||||
// 在发送请求之前做些什么 token
|
||||
if (Session.get('token')) {
|
||||
config.headers!['Authorization'] = `Bearer ${Session.get('token')}`;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
// 对请求错误做些什么
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// 添加响应拦截器
|
||||
service.interceptors.response.use(
|
||||
(response) => {
|
||||
// 对响应数据做点什么
|
||||
const res = response.data;
|
||||
if (res.code && res.code !== 0) {
|
||||
// `token` 过期或者账号已在别处登录
|
||||
if (res.code === 401 || res.code === 4001 || res.code === 402) {
|
||||
Session.clear(); // 清除浏览器全部临时缓存
|
||||
window.location.href = '/'; // 去登录页
|
||||
ElMessageBox.alert('你已被登出,请重新登录', '提示', {})
|
||||
.then(() => {})
|
||||
.catch(() => {});
|
||||
}
|
||||
return Promise.reject(service.interceptors.response);
|
||||
} else {
|
||||
return response.data;
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
// 对响应错误做点什么
|
||||
if (error.message.indexOf('timeout') != -1) {
|
||||
ElMessage.error('网络超时');
|
||||
} else if (error.message == 'Network Error') {
|
||||
ElMessage.error('网络连接错误');
|
||||
} else {
|
||||
if (error.response.data) ElMessage.error(error.response.statusText);
|
||||
else ElMessage.error('接口路径找不到');
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
service.interceptors.response.use((res: any) => {
|
||||
return res.data;
|
||||
}, error => {
|
||||
const status = Number(error.response.status) || 200
|
||||
const message = error.response.data.msg || errorCode[status] || errorCode['default']
|
||||
if (status === 424) {
|
||||
ElMessageBox.confirm('令牌状态已过期,请点击重新登录', '系统提示', {
|
||||
confirmButtonText: '重新登录',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(() => {
|
||||
Session.clear(); // 清除浏览器全部临时缓存
|
||||
window.location.href = '/'; // 去登录页
|
||||
return
|
||||
})
|
||||
.catch(() => {
|
||||
})
|
||||
}
|
||||
|
||||
if (status !== 200 || error.response.data.code === 1) {
|
||||
ElMessage.error(message)
|
||||
return Promise.reject(new Error(message))
|
||||
}
|
||||
return Promise.reject(error);
|
||||
})
|
||||
|
||||
|
||||
// 导出 axios 实例
|
||||
export default service;
|
||||
|
@ -56,7 +56,7 @@ import { defineAsyncComponent, ref, reactive, onMounted } from 'vue';
|
||||
import { ElMessageBox, ElMessage } from 'element-plus';
|
||||
|
||||
// 引入组件
|
||||
const DeptDialog = defineAsyncComponent(() => import('/@/views/system/dept/dialog.vue'));
|
||||
const DeptDialog = defineAsyncComponent(() => import('/@/views/admin/dept/dialog.vue'));
|
||||
|
||||
// 定义变量内容
|
||||
const deptDialogRef = ref();
|
@ -58,7 +58,7 @@ import { defineAsyncComponent, reactive, onMounted, ref } from 'vue';
|
||||
import { ElMessageBox, ElMessage } from 'element-plus';
|
||||
|
||||
// 引入组件
|
||||
const DicDialog = defineAsyncComponent(() => import('/@/views/system/dic/dialog.vue'));
|
||||
const DicDialog = defineAsyncComponent(() => import('/@/views/admin/dict/dialog.vue'));
|
||||
|
||||
// 定义变量内容
|
||||
const dicDialogRef = ref();
|
@ -12,7 +12,7 @@
|
||||
<el-row style="margin-top: 20px">
|
||||
<div class="mb15" style="width: 100%">
|
||||
<el-button size="default" icon="folder-add" type="success" class="ml10" @click="roleDialogRef.openDialog('add')">
|
||||
新增用户
|
||||
新增
|
||||
</el-button>
|
||||
<right-toolbar v-model:showSearch="showSearch" class="ml10" style="float: right;margin-right: 20px" @queryTable="getDataList"></right-toolbar>
|
||||
</div>
|
@ -1,63 +1,54 @@
|
||||
<template>
|
||||
<el-form size="large" class="login-content-form">
|
||||
<el-form-item class="login-animation1">
|
||||
<el-input text :placeholder="$t('account.accountPlaceholder1')" v-model="state.ruleForm.username" clearable autocomplete="off">
|
||||
<el-input text :placeholder="$t('account.accountPlaceholder1')" v-model="state.ruleForm.username" clearable
|
||||
autocomplete="off">
|
||||
<template #prefix>
|
||||
<el-icon class="el-input__icon"><ele-User /></el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item class="login-animation2">
|
||||
<el-input
|
||||
:type="state.isShowPassword ? 'text' : 'password'"
|
||||
:placeholder="$t('account.accountPlaceholder2')"
|
||||
v-model="state.ruleForm.password"
|
||||
autocomplete="off"
|
||||
>
|
||||
<el-input :type="state.isShowPassword ? 'text' : 'password'"
|
||||
:placeholder="$t('account.accountPlaceholder2')" v-model="state.ruleForm.password" autocomplete="off">
|
||||
<template #prefix>
|
||||
<el-icon class="el-input__icon"><ele-Unlock /></el-icon>
|
||||
</template>
|
||||
<template #suffix>
|
||||
<i
|
||||
class="iconfont el-input__icon login-content-password"
|
||||
<i class="iconfont el-input__icon login-content-password"
|
||||
:class="state.isShowPassword ? 'icon-yincangmima' : 'icon-xianshimima'"
|
||||
@click="state.isShowPassword = !state.isShowPassword"
|
||||
>
|
||||
@click="state.isShowPassword = !state.isShowPassword">
|
||||
</i>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<Verify
|
||||
@success="verifySuccess"
|
||||
:mode="'pop'"
|
||||
:captchaType="'blockPuzzle'"
|
||||
:imgSize="{ width: '330px', height: '155px' }"
|
||||
ref="verifyref"
|
||||
/>
|
||||
</el-form-item>
|
||||
<!-- <el-form-item class="login-animation3">-->
|
||||
<!-- <el-col :span="15">-->
|
||||
<!-- <el-input-->
|
||||
<!-- text-->
|
||||
<!-- maxlength="4"-->
|
||||
<!-- :placeholder="$t('account.accountPlaceholder3')"-->
|
||||
<!-- v-model="state.ruleForm.code"-->
|
||||
<!-- clearable-->
|
||||
<!-- autocomplete="off"-->
|
||||
<!-- >-->
|
||||
<!-- <template #prefix>-->
|
||||
<!-- <el-icon class="el-input__icon"><ele-Position /></el-icon>-->
|
||||
<!-- </template>-->
|
||||
<!-- </el-input>-->
|
||||
<!-- </el-col>-->
|
||||
<!-- <el-col :span="1"></el-col>-->
|
||||
<!-- <el-col :span="8">-->
|
||||
<!-- <el-button class="login-content-code" v-waves>1234</el-button>-->
|
||||
<!-- </el-col>-->
|
||||
<!-- </el-form-item>-->
|
||||
<el-form-item>
|
||||
<Verify @success="verifySuccess" :mode="'pop'" :captchaType="'blockPuzzle'"
|
||||
:imgSize="{ width: '330px', height: '155px' }" ref="verifyref" />
|
||||
</el-form-item>
|
||||
<!-- <el-form-item class="login-animation3">-->
|
||||
<!-- <el-col :span="15">-->
|
||||
<!-- <el-input-->
|
||||
<!-- text-->
|
||||
<!-- maxlength="4"-->
|
||||
<!-- :placeholder="$t('account.accountPlaceholder3')"-->
|
||||
<!-- v-model="state.ruleForm.code"-->
|
||||
<!-- clearable-->
|
||||
<!-- autocomplete="off"-->
|
||||
<!-- >-->
|
||||
<!-- <template #prefix>-->
|
||||
<!-- <el-icon class="el-input__icon"><ele-Position /></el-icon>-->
|
||||
<!-- </template>-->
|
||||
<!-- </el-input>-->
|
||||
<!-- </el-col>-->
|
||||
<!-- <el-col :span="1"></el-col>-->
|
||||
<!-- <el-col :span="8">-->
|
||||
<!-- <el-button class="login-content-code" v-waves>1234</el-button>-->
|
||||
<!-- </el-col>-->
|
||||
<!-- </el-form-item>-->
|
||||
<el-form-item class="login-animation4">
|
||||
<el-button type="primary" class="login-content-submit" round v-waves @click="handleLogin" :loading="state.loading.signIn">
|
||||
<el-button type="primary" class="login-content-submit" round v-waves @click="handleLogin"
|
||||
:loading="state.loading.signIn">
|
||||
<span>{{ $t('account.accountBtnText') }}</span>
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
@ -65,7 +56,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="loginAccount">
|
||||
import {reactive, computed, defineAsyncComponent, ref} from 'vue';
|
||||
import { reactive, computed, defineAsyncComponent, ref } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
@ -89,10 +80,10 @@ const router = useRouter();
|
||||
const state = reactive({
|
||||
isShowPassword: false,
|
||||
ruleForm: {
|
||||
username: 'admin',
|
||||
username: 'admin',
|
||||
password: '123456',
|
||||
code: '',
|
||||
randomStr: 'blockPuzzle'
|
||||
randomStr: 'blockPuzzle'
|
||||
},
|
||||
loading: {
|
||||
signIn: false,
|
||||
@ -107,26 +98,18 @@ const currentTime = computed(() => {
|
||||
});
|
||||
|
||||
const handleLogin = () => {
|
||||
verifyref.value.show();
|
||||
verifyref.value.show();
|
||||
}
|
||||
// 登录
|
||||
const onSignIn = async () => {
|
||||
state.loading.signIn = true;
|
||||
// 存储 token 到浏览器缓存
|
||||
await useUserInfo().login(state.ruleForm)
|
||||
// 进行登录
|
||||
await useUserInfo().login(state.ruleForm)
|
||||
// 进行登录
|
||||
|
||||
if (!themeConfig.value.isRequestRoutes) {
|
||||
// 前端控制路由,2、请注意执行顺序
|
||||
const isNoPower = await initFrontEndControlRoutes();
|
||||
signInSuccess(isNoPower);
|
||||
} else {
|
||||
// 模拟后端控制路由,isRequestRoutes 为 true,则开启后端控制路由
|
||||
// 添加完动态路由,再进行 router 跳转,否则可能报错 No match found for location with path "/"
|
||||
const isNoPower = await initBackEndControlRoutes();
|
||||
// 执行完 initBackEndControlRoutes,再执行 signInSuccess
|
||||
signInSuccess(isNoPower);
|
||||
}
|
||||
const isNoPower = await initBackEndControlRoutes();
|
||||
// 执行完 initBackEndControlRoutes,再执行 signInSuccess
|
||||
signInSuccess(isNoPower);
|
||||
};
|
||||
// 登录成功后的跳转
|
||||
const signInSuccess = (isNoPower: boolean | undefined) => {
|
||||
@ -156,14 +139,15 @@ const signInSuccess = (isNoPower: boolean | undefined) => {
|
||||
};
|
||||
|
||||
const verifySuccess = (params: any) => {
|
||||
state.ruleForm.code = params.captchaVerification;
|
||||
onSignIn()
|
||||
state.ruleForm.code = params.captchaVerification;
|
||||
onSignIn()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.login-content-form {
|
||||
margin-top: 20px;
|
||||
|
||||
@for $i from 1 through 4 {
|
||||
.login-animation#{$i} {
|
||||
opacity: 0;
|
||||
@ -173,20 +157,24 @@ const verifySuccess = (params: any) => {
|
||||
animation-delay: calc($i/10) + s;
|
||||
}
|
||||
}
|
||||
|
||||
.login-content-password {
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: #909399;
|
||||
}
|
||||
}
|
||||
|
||||
.login-content-code {
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
font-weight: bold;
|
||||
letter-spacing: 5px;
|
||||
}
|
||||
|
||||
.login-content-submit {
|
||||
width: 100%;
|
||||
letter-spacing: 2px;
|
||||
|
Loading…
Reference in New Issue
Block a user