更新
This commit is contained in:
parent
dd6f59222f
commit
841a0dabcb
1
tansci-boot-ui/.env.development
Normal file
1
tansci-boot-ui/.env.development
Normal file
@ -0,0 +1 @@
|
||||
VITE_BASE_API = 'http://localhost:8081/'
|
||||
1
tansci-boot-ui/.env.production
Normal file
1
tansci-boot-ui/.env.production
Normal file
@ -0,0 +1 @@
|
||||
VITE_BASE_API = 'http://localhost:8081/'
|
||||
14
tansci-boot-ui/.gitignore
vendored
Normal file
14
tansci-boot-ui/.gitignore
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
.vite-ssg-temp
|
||||
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# lock
|
||||
yarn.lock
|
||||
package-lock.json
|
||||
pnpm-lock.yaml
|
||||
|
||||
*.log
|
||||
2
tansci-boot-ui/.npmrc
Normal file
2
tansci-boot-ui/.npmrc
Normal file
@ -0,0 +1,2 @@
|
||||
shamefully-hoist=true
|
||||
strict-peer-dependencies=false
|
||||
13
tansci-boot-ui/index.html
Normal file
13
tansci-boot-ui/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Tansci Boot</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
31
tansci-boot-ui/package.json
Normal file
31
tansci-boot-ui/package.json
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "tansci-boot-ui",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"generate": "vite-ssg build",
|
||||
"preview": "vite preview",
|
||||
"typecheck": "vue-tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.3.4",
|
||||
"element-plus": "^2.2.13",
|
||||
"vue": "^3.2.36",
|
||||
"vue-router": "^4.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify-json/ep": "^1.1.4",
|
||||
"@types/node": "^18.14.0",
|
||||
"@vitejs/plugin-vue": "^4.0.0",
|
||||
"sass": "^1.52.1",
|
||||
"typescript": "^4.7.2",
|
||||
"unocss": "^0.49.7",
|
||||
"unplugin-vue-components": "^0.24.0",
|
||||
"vite": "^4.1.2",
|
||||
"vite-ssg": "^0.22.1",
|
||||
"vue-tsc": "^1.1.3"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
BIN
tansci-boot-ui/public/favicon.ico
Normal file
BIN
tansci-boot-ui/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
5
tansci-boot-ui/src/App.vue
Normal file
5
tansci-boot-ui/src/App.vue
Normal file
@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<el-config-provider namespace="el">
|
||||
<router-view />
|
||||
</el-config-provider>
|
||||
</template>
|
||||
52
tansci-boot-ui/src/api/auth.ts
Normal file
52
tansci-boot-ui/src/api/auth.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// token key
|
||||
const tokenKey:string = 'tansci_boot_token'
|
||||
|
||||
// 获取token
|
||||
export function getToken() {
|
||||
return sessionStorage.getItem(tokenKey);
|
||||
}
|
||||
|
||||
// 存储token
|
||||
export function setToken(token:string) {
|
||||
sessionStorage.setItem(tokenKey, token);
|
||||
}
|
||||
|
||||
// 删除token
|
||||
export function removeToken() {
|
||||
sessionStorage.removeItem(tokenKey);
|
||||
}
|
||||
|
||||
// 登录
|
||||
export function login(data:any){
|
||||
return new Promise((resolve, reject) => {
|
||||
request({
|
||||
url: '/system/security/login',
|
||||
method: 'post',
|
||||
data: {
|
||||
username: data.username,
|
||||
password: data.password,
|
||||
code: data.code,
|
||||
uuid: data.uuid
|
||||
}
|
||||
}).then((res:any) => {
|
||||
var token = res.data
|
||||
setToken(token)
|
||||
resolve(token)
|
||||
}).catch((e:any) => {
|
||||
reject(e)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 登出
|
||||
export function logout(){
|
||||
request({
|
||||
url: '/system/security/logout',
|
||||
method: 'get'
|
||||
}).then(() => {
|
||||
removeToken()
|
||||
location.reload()
|
||||
})
|
||||
}
|
||||
BIN
tansci-boot-ui/src/assets/image/login-icon.png
Normal file
BIN
tansci-boot-ui/src/assets/image/login-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 92 KiB |
BIN
tansci-boot-ui/src/assets/image/logo.png
Normal file
BIN
tansci-boot-ui/src/assets/image/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
25
tansci-boot-ui/src/components/Submenu.vue
Normal file
25
tansci-boot-ui/src/components/Submenu.vue
Normal file
@ -0,0 +1,25 @@
|
||||
<script setup lang="ts">
|
||||
import {defineProps} from 'vue'
|
||||
const props = defineProps({
|
||||
data: Array
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<el-sub-menu :index="data.path">
|
||||
<template #title>
|
||||
<el-icon v-if="data.icon" style="vertical-align: middle;">
|
||||
<component :is="data.icon"></component>
|
||||
</el-icon>
|
||||
<span style="vertical-align: middle;">{{data.meta.title}}</span>
|
||||
</template>
|
||||
<template v-for="item in data.children" :key="item">
|
||||
<el-menu-item v-if="!item.children || item.children.length <= 1" :index="item.path">
|
||||
<el-icon v-if="item.icon" style="vertical-align: middle;">
|
||||
<component :is="item.icon"></component>
|
||||
</el-icon>
|
||||
<span style="vertical-align: middle;">{{item.meta.title}}</span>
|
||||
</el-menu-item>
|
||||
<Submenu v-else :data='item'></Submenu>
|
||||
</template>
|
||||
</el-sub-menu>
|
||||
</template>
|
||||
158
tansci-boot-ui/src/components/TabsMenu.vue
Normal file
158
tansci-boot-ui/src/components/TabsMenu.vue
Normal file
@ -0,0 +1,158 @@
|
||||
<script setup lang="ts">
|
||||
import { watch, reactive, toRefs } from "vue"
|
||||
import { TabsPaneContext } from "element-plus"
|
||||
import { useRoute, useRouter } from "vue-router"
|
||||
import { HOME_URL, TABS_BLACK_LIST } from "@/config/config"
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const state = reactive({
|
||||
tabsMenuValue: HOME_URL,
|
||||
tabsMenuList:[
|
||||
{title:'首页', path: HOME_URL, icon:'HomeFilled', close: false}
|
||||
]
|
||||
})
|
||||
|
||||
const {tabsMenuValue, tabsMenuList} = toRefs(state)
|
||||
|
||||
// 监听路由的变化(防止浏览器后退/前进不变化 tabsMenuValue)
|
||||
watch(
|
||||
() => route.path,
|
||||
() => {
|
||||
let params = {
|
||||
title: route.meta.title as string,
|
||||
path: route.path,
|
||||
close: true
|
||||
};
|
||||
onAddTabManu(params);
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
|
||||
function onAddTabManu(tabItem: any){
|
||||
if (TABS_BLACK_LIST.includes(tabItem.path)) return;
|
||||
|
||||
if (state.tabsMenuList.every(item => item.path !== tabItem.path)) {
|
||||
state.tabsMenuList.push(tabItem);
|
||||
}
|
||||
state.tabsMenuValue = tabItem.path;
|
||||
router.push(tabItem.path);
|
||||
}
|
||||
|
||||
const onTabMenuClick = (tabItem: TabsPaneContext) =>{
|
||||
let path = tabItem.props.name as string;
|
||||
router.push(path);
|
||||
}
|
||||
|
||||
const onTabMenuRemove = (tabItem: String) =>{
|
||||
let _tabsMenuValue = state.tabsMenuValue;
|
||||
let _tabsMenuList = state.tabsMenuList;
|
||||
if (_tabsMenuValue === tabItem) {
|
||||
_tabsMenuList.forEach((item, index) => {
|
||||
if (item.path !== tabItem) return;
|
||||
|
||||
let nextTab = _tabsMenuList[index + 1] || _tabsMenuList[index - 1];
|
||||
if (!nextTab) return;
|
||||
|
||||
_tabsMenuValue = nextTab.path;
|
||||
router.push(nextTab.path);
|
||||
});
|
||||
}
|
||||
state.tabsMenuValue = _tabsMenuValue;
|
||||
state.tabsMenuList = _tabsMenuList.filter(item => item.path !== tabItem);
|
||||
}
|
||||
|
||||
const onCloseCurrentTab = () =>{
|
||||
if (state.tabsMenuValue === HOME_URL) return;
|
||||
onTabMenuRemove(state.tabsMenuValue);
|
||||
}
|
||||
|
||||
const onCloseOtherTab = () =>{
|
||||
state.tabsMenuList = state.tabsMenuList.filter(item => {
|
||||
return item.path === state.tabsMenuValue || item.path === HOME_URL;
|
||||
});
|
||||
}
|
||||
|
||||
const onCloseAllTab = () =>{
|
||||
state.tabsMenuList = state.tabsMenuList.filter(item => {
|
||||
return item.path === HOME_URL;
|
||||
});
|
||||
router.push(HOME_URL);
|
||||
}
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<div class="tabs-menu">
|
||||
<el-tabs v-model="tabsMenuValue" type="card" @tab-click="onTabMenuClick" @tab-remove="onTabMenuRemove">
|
||||
<el-tab-pane v-for="item in tabsMenuList"
|
||||
:key="item.path"
|
||||
:path="item.path"
|
||||
:label="item.title"
|
||||
:name="item.path"
|
||||
:closable="item.close">
|
||||
<template #label>
|
||||
<el-icon v-if="item.icon" style="vertical-align: middle; padding-right: 0.2rem;">
|
||||
<component :is="item.icon"></component>
|
||||
</el-icon>
|
||||
<span style="vertical-align: middle">{{ item.title }}</span>
|
||||
</template>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<el-dropdown trigger="hover">
|
||||
<el-button size="small" type="primary">
|
||||
<span>更多</span>
|
||||
<el-icon class="el-icon--right"><arrow-down /></el-icon>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item icon="CircleCloseFilled" @click="onCloseCurrentTab">关闭当前</el-dropdown-item>
|
||||
<el-dropdown-item icon="CircleClose" @click="onCloseOtherTab">关闭其他</el-dropdown-item>
|
||||
<el-dropdown-item icon="CloseBold" @click="onCloseAllTab">关闭所有</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss" >
|
||||
.tabs-menu{
|
||||
position: relative;
|
||||
width: 100%;
|
||||
// border-top: 1px transparent solid;
|
||||
// border-image: linear-gradient(to right, var(--bg1),#DCDFE6, var(--bg1)) 1 10;
|
||||
// box-shadow: rgba(27, 31, 35, 0.04) 0px 1px 0px, rgba(255, 255, 255, 0.25) 0px 1px 0px inset;
|
||||
// margin-bottom: 0.2rem;
|
||||
|
||||
.el-dropdown {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 13px;
|
||||
}
|
||||
|
||||
.el-tabs__nav-wrap {
|
||||
position: absolute;
|
||||
width: calc(100% - 120px);
|
||||
}
|
||||
.el-tabs--card > .el-tabs__header {
|
||||
box-sizing: border-box;
|
||||
height: 40px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.el-tabs--card > .el-tabs__header .el-tabs__nav {
|
||||
border: none;
|
||||
}
|
||||
.el-tabs--card > .el-tabs__header .el-tabs__item {
|
||||
border: none;
|
||||
}
|
||||
.el-tabs--card > .el-tabs__header .el-tabs__item.is-active {
|
||||
color: var(--theme);
|
||||
border: none;
|
||||
}
|
||||
.el-tabs__item .is-icon-close svg {
|
||||
margin-top: 0.5px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
153
tansci-boot-ui/src/components/layout/Index.vue
Normal file
153
tansci-boot-ui/src/components/layout/Index.vue
Normal file
@ -0,0 +1,153 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, onMounted, onBeforeMount, onDeactivated } from "vue"
|
||||
import { isDark, toggleDark } from '@/composables'
|
||||
import { useRouter } from 'vue-router'
|
||||
import Submenu from "@/components/Submenu.vue"
|
||||
import TabsMenu from "@/components/TabsMenu.vue"
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const logo = new URL('../../assets/image/logo.png', import.meta.url).href
|
||||
const state = reactive({
|
||||
headerHeight: '52px',
|
||||
asideWidth: '180px',
|
||||
routers: [],
|
||||
defaultHeight: null,
|
||||
})
|
||||
|
||||
onBeforeMount(() => {
|
||||
state.defaultHeight = (document.body.clientHeight || document.documentElement.clientHeight);
|
||||
})
|
||||
|
||||
onMounted(()=>{
|
||||
// 获取菜单
|
||||
let routers:any = [];
|
||||
let _routes = router.options.routes;
|
||||
_routes.forEach((item:any)=>{
|
||||
if(item.children && item.type == 0){
|
||||
routers.push(item)
|
||||
}
|
||||
})
|
||||
state.routers = routers
|
||||
|
||||
window.addEventListener('resize', onDefaultHeight);
|
||||
})
|
||||
|
||||
onDeactivated(()=>{
|
||||
window.removeEventListener('resize', onDefaultHeight, false)
|
||||
})
|
||||
|
||||
function onDefaultHeight(){
|
||||
state.defaultHeight = window.innerHeight
|
||||
}
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<div class="layout-container">
|
||||
<el-container>
|
||||
<el-header :height="state.headerHeight">
|
||||
<div class="header-logo">
|
||||
<el-image :src="logo"></el-image>
|
||||
</div>
|
||||
<div class="header-content">
|
||||
<div class="header-dark">
|
||||
<el-button @click="toggleDark()" link type="primary">
|
||||
<template #icon>
|
||||
<el-icon><Sunny v-show="!isDark"/></el-icon>
|
||||
<el-icon><Moon v-show="isDark"/></el-icon>
|
||||
</template>
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="header-login">
|
||||
<el-button circle>
|
||||
<template #icon>
|
||||
<el-icon><UserFilled /></el-icon>
|
||||
</template>
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-header>
|
||||
<el-container>
|
||||
<el-aside :width="state.asideWidth" :style="{height: state.defaultHeight+'px'}">
|
||||
<el-menu router :default-active="$route.path">
|
||||
<template v-for="item in state.routers" :key="item">
|
||||
<el-menu-item v-if="!item.children || item.children.length <= 1" :index="item.path">
|
||||
<el-icon v-if="item.icon" style="vertical-align: middle;">
|
||||
<component :is="item.icon"></component>
|
||||
</el-icon>
|
||||
<span style="vertical-align: middle;">{{item.meta.title}}</span>
|
||||
</el-menu-item>
|
||||
<Submenu v-else :data="item"></Submenu>
|
||||
</template>
|
||||
</el-menu>
|
||||
</el-aside>
|
||||
<el-main>
|
||||
<TabsMenu></TabsMenu>
|
||||
<router-view />
|
||||
</el-main>
|
||||
</el-container>
|
||||
</el-container>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.layout-container{
|
||||
.el-header{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
line-height: 52px;
|
||||
background: var(--el-bg-color);
|
||||
.header-logo{
|
||||
display: flex;
|
||||
height: 52px;
|
||||
line-height: 52px;
|
||||
padding-left: 0.2rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
.header-content{
|
||||
display: flex;
|
||||
justify-content: right;
|
||||
}
|
||||
}
|
||||
.el-aside{
|
||||
background: var(--el-bg-color);
|
||||
::v-deep .el-menu{
|
||||
margin: 0 0.6rem;
|
||||
padding: 0 0.2rem;
|
||||
border-right: none;
|
||||
background: var(--el-bg-color);
|
||||
.el-menu-item, .el-sub-menu__title {
|
||||
border-radius: 0.2rem;
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
margin: 0.4rem 0;
|
||||
}
|
||||
.el-menu-item, .el-menu-item-group__title, .el-sub-menu, .el-sub-menu__title{
|
||||
padding-left: 0;
|
||||
}
|
||||
.el-sub-menu__title:hover{
|
||||
background: #fff !important;
|
||||
color: var(--theme) !important;
|
||||
}
|
||||
.el-menu-item:hover{
|
||||
background: #fff !important;
|
||||
color: var(--theme) !important;
|
||||
}
|
||||
.el-menu-item.is-active {
|
||||
background: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
.el-aside::-webkit-scrollbar{
|
||||
width: 0px;
|
||||
}
|
||||
.el-main{
|
||||
background: var(--el-bg-color);
|
||||
padding: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.el-main::-webkit-scrollbar{
|
||||
width: 0px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
4
tansci-boot-ui/src/composables/dark.ts
Normal file
4
tansci-boot-ui/src/composables/dark.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import { useDark, useToggle } from '@vueuse/core'
|
||||
|
||||
export const isDark = useDark()
|
||||
export const toggleDark = useToggle(isDark)
|
||||
1
tansci-boot-ui/src/composables/index.ts
Normal file
1
tansci-boot-ui/src/composables/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './dark'
|
||||
8
tansci-boot-ui/src/config/config.ts
Normal file
8
tansci-boot-ui/src/config/config.ts
Normal file
@ -0,0 +1,8 @@
|
||||
// 全局不动配置项 只做导出不做修改
|
||||
|
||||
// 首页地址(默认)
|
||||
export const HOME_URL: string = "/index";
|
||||
|
||||
// Tabs(黑名单地址,不需要添加到 tabs 的路由地址)
|
||||
export const TABS_BLACK_LIST: string[] = ["/404", "/500", "/login"];
|
||||
|
||||
8
tansci-boot-ui/src/env.d.ts
vendored
Normal file
8
tansci-boot-ui/src/env.d.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
declare module "*.vue" {
|
||||
import { DefineComponent } from "vue";
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
|
||||
const component: DefineComponent<{}, {}, any>;
|
||||
export default component;
|
||||
}
|
||||
20
tansci-boot-ui/src/main.ts
Normal file
20
tansci-boot-ui/src/main.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import ElementPlus from 'element-plus'
|
||||
import 'element-plus/dist/index.css'
|
||||
import './styles/index.scss'
|
||||
import * as ElIcons from '@element-plus/icons-vue'
|
||||
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
||||
import 'uno.css'
|
||||
|
||||
const app = createApp(App)
|
||||
app.use(router)
|
||||
app.use(ElementPlus,{
|
||||
locale: zhCn,
|
||||
size: 'default',
|
||||
})
|
||||
for (const icon in ElIcons) {
|
||||
app.component(icon, (ElIcons as any)[icon])
|
||||
}
|
||||
app.mount('#app');
|
||||
24
tansci-boot-ui/src/router/common.ts
Normal file
24
tansci-boot-ui/src/router/common.ts
Normal file
@ -0,0 +1,24 @@
|
||||
export default[
|
||||
{
|
||||
path: '/',
|
||||
redirect: 'login',
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
name: 'login',
|
||||
meta: {title: "登录"},
|
||||
component: () => import("@/views/common/Login.vue"),
|
||||
},
|
||||
{
|
||||
path: '/404',
|
||||
name: '404',
|
||||
meta: {title: "404"},
|
||||
component: () => import('@/views/common/404.vue')
|
||||
},
|
||||
{
|
||||
path: '/500',
|
||||
name: '500',
|
||||
meta: {title: "500"},
|
||||
component: () => import('@/views/common/500.vue')
|
||||
}
|
||||
]
|
||||
14
tansci-boot-ui/src/router/index.ts
Normal file
14
tansci-boot-ui/src/router/index.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { createRouter, createWebHistory } from "vue-router"
|
||||
|
||||
import routers from './routers'
|
||||
|
||||
import common from './common'
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes: [
|
||||
...common,
|
||||
...routers
|
||||
]
|
||||
})
|
||||
|
||||
export default router
|
||||
84
tansci-boot-ui/src/router/routers.ts
Normal file
84
tansci-boot-ui/src/router/routers.ts
Normal file
@ -0,0 +1,84 @@
|
||||
import Layout from '@/components/layout/Index.vue'
|
||||
export default[
|
||||
{
|
||||
path: '/index',
|
||||
name: 'index',
|
||||
type: 0,
|
||||
icon: 'HomeFilled',
|
||||
meta: { title: "首页" },
|
||||
component: () => Layout,
|
||||
children: [
|
||||
{
|
||||
path: '/index',
|
||||
name: 'index',
|
||||
type: 0,
|
||||
icon: 'HomeFilled',
|
||||
meta: { title: "首页" },
|
||||
component: () => import('@/views/Index.vue')
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/system',
|
||||
name: 'system',
|
||||
type: 0,
|
||||
icon: 'Grid',
|
||||
meta: { title: "系统管理" },
|
||||
children: [
|
||||
{
|
||||
path: '/menu',
|
||||
name: 'menu',
|
||||
type: 0,
|
||||
icon: 'Grid',
|
||||
meta: { title: "菜单管理" },
|
||||
component: () => Layout,
|
||||
children: [
|
||||
{
|
||||
path: '/menu',
|
||||
name: 'menu',
|
||||
type: 0,
|
||||
icon: 'Grid',
|
||||
meta: { title: "菜单管理" },
|
||||
component: () => import('@/views/system/Manu.vue')
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/org',
|
||||
name: 'org',
|
||||
type: 0,
|
||||
icon: 'QuestionFilled',
|
||||
meta: { title: "组织管理" },
|
||||
component: () => Layout,
|
||||
children: [
|
||||
{
|
||||
path: '/org',
|
||||
name: 'org',
|
||||
type: 0,
|
||||
icon: 'QuestionFilled',
|
||||
meta: { title: "组织管理" },
|
||||
component: () => import('@/views/system/Org.vue')
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/user',
|
||||
name: 'user',
|
||||
type: 0,
|
||||
icon: 'QuestionFilled',
|
||||
meta: { title: "用户管理" },
|
||||
component: () => Layout,
|
||||
children: [
|
||||
{
|
||||
path: '/user',
|
||||
name: 'user',
|
||||
type: 0,
|
||||
icon: 'QuestionFilled',
|
||||
meta: { title: "用户管理" },
|
||||
component: () => import('@/views/system/User.vue')
|
||||
}
|
||||
]
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
25
tansci-boot-ui/src/styles/element/dark.scss
Normal file
25
tansci-boot-ui/src/styles/element/dark.scss
Normal file
@ -0,0 +1,25 @@
|
||||
$--colors: (
|
||||
"primary": (
|
||||
"base": #2F9688,
|
||||
),
|
||||
"success": (
|
||||
"base": #21ba45,
|
||||
),
|
||||
"warning": (
|
||||
"base": #f2711c,
|
||||
),
|
||||
"danger": (
|
||||
"base": #db2828,
|
||||
),
|
||||
"error": (
|
||||
"base": #db2828,
|
||||
),
|
||||
"info": (
|
||||
"base": #42b8dd,
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
@forward "element-plus/theme-chalk/src/dark/var.scss" with (
|
||||
$colors: $--colors
|
||||
);
|
||||
33
tansci-boot-ui/src/styles/element/index.scss
Normal file
33
tansci-boot-ui/src/styles/element/index.scss
Normal file
@ -0,0 +1,33 @@
|
||||
$--colors: (
|
||||
"primary": (
|
||||
"base": #2F9688,
|
||||
),
|
||||
"success": (
|
||||
"base": #21ba45,
|
||||
),
|
||||
"warning": (
|
||||
"base": #f2711c,
|
||||
),
|
||||
"danger": (
|
||||
"base": #db2828,
|
||||
),
|
||||
"error": (
|
||||
"base": #db2828,
|
||||
),
|
||||
"info": (
|
||||
"base": #42b8dd,
|
||||
)
|
||||
);
|
||||
|
||||
@forward "element-plus/theme-chalk/src/mixins/config.scss" with (
|
||||
$namespace: "el"
|
||||
);
|
||||
|
||||
@forward "element-plus/theme-chalk/src/common/var.scss" with (
|
||||
$colors: $--colors,
|
||||
$button-padding-horizontal: ("default": 50px)
|
||||
);
|
||||
|
||||
@use "element-plus/theme-chalk/src/index.scss" as *;
|
||||
|
||||
@use "./dark.scss";
|
||||
51
tansci-boot-ui/src/styles/index.scss
Normal file
51
tansci-boot-ui/src/styles/index.scss
Normal file
@ -0,0 +1,51 @@
|
||||
// import dark theme
|
||||
@use "element-plus/theme-chalk/src/dark/css-vars.scss" as *;
|
||||
|
||||
:root {
|
||||
// 主题
|
||||
--theme: #2F9688;
|
||||
|
||||
// 局部背景
|
||||
--el-bg-color: #eff4f9;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB","Microsoft YaHei", "微软雅黑", Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/**
|
||||
* el-table 滚动条样式
|
||||
*/
|
||||
.el-table__body-wrapper::-webkit-scrollbar {
|
||||
width: 5px;
|
||||
height: 1px;
|
||||
}
|
||||
.el-table__body-wrapper::-webkit-scrollbar-thumb {
|
||||
background-color: #909399;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.el-table__body-wrapper::-webkit-scrollbar-track {
|
||||
border-radius: 5px;
|
||||
background: #ededed;
|
||||
}
|
||||
|
||||
/**
|
||||
* 滚动条样式
|
||||
*/
|
||||
.scroll-div::-webkit-scrollbar{
|
||||
width: 5px;
|
||||
height: 1px;
|
||||
}
|
||||
.scroll-div::-webkit-scrollbar-thumb {
|
||||
border-radius: 5px;
|
||||
background: #909399;
|
||||
}
|
||||
.scroll-div::-webkit-scrollbar-track {
|
||||
border-radius: 5px;
|
||||
background: #ededed;
|
||||
}
|
||||
61
tansci-boot-ui/src/utils/request.ts
Normal file
61
tansci-boot-ui/src/utils/request.ts
Normal file
@ -0,0 +1,61 @@
|
||||
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
|
||||
import { showMessage } from './status'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { logout, getToken } from '@/api/auth'
|
||||
import router from '../router'
|
||||
|
||||
// 根据环境获得不同的代理模式
|
||||
const baseURL = import.meta.env.VITE_BASE_URL as string;
|
||||
const axiosInstance: AxiosInstance = axios.create({
|
||||
baseURL: baseURL,
|
||||
timeout: 30 * 1000, // 超时时间
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
})
|
||||
|
||||
// axios实例拦截请求
|
||||
axiosInstance.interceptors.request.use((config: AxiosRequestConfig) => {
|
||||
// 设置token
|
||||
if (getToken()) {
|
||||
config.headers.Authorization = `Bearer ${getToken()}`
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(error: any) => {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
)
|
||||
|
||||
// axios实例拦截响应
|
||||
axiosInstance.interceptors.response.use(
|
||||
(response: AxiosResponse) => {
|
||||
if (response.status === 200 && response.data.code == 200) {
|
||||
return response;
|
||||
} else {
|
||||
ElMessage.warning(showMessage(response.status));
|
||||
if (response.data.code === 402) {
|
||||
logout()
|
||||
}
|
||||
|
||||
if (response.data.code == 403 || response.data.code == 401){
|
||||
sessionStorage.clear();
|
||||
router.push({path: 'login'});
|
||||
}
|
||||
return response;
|
||||
}
|
||||
},
|
||||
// 请求失败
|
||||
(error: any) => {
|
||||
const {response} = error;
|
||||
if (response) {
|
||||
// 请求已发出,但是不在2xx的范围
|
||||
ElMessage.warning(showMessage(response.status));
|
||||
return Promise.reject(response.data);
|
||||
} else {
|
||||
ElMessage.warning('网络连接异常,请稍后再试!');
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
export default axiosInstance
|
||||
42
tansci-boot-ui/src/utils/status.ts
Normal file
42
tansci-boot-ui/src/utils/status.ts
Normal file
@ -0,0 +1,42 @@
|
||||
export const showMessage = (status:number|string) : string => {
|
||||
let message:string = "";
|
||||
switch (status) {
|
||||
case 400:
|
||||
message = "请求错误(400)";
|
||||
break;
|
||||
case 401:
|
||||
// message = "未授权,请重新登录(401)";
|
||||
message = "用户名或密码错误";
|
||||
break;
|
||||
case 403:
|
||||
message = "拒绝访问(403)";
|
||||
break;
|
||||
case 404:
|
||||
message = "请求出错(404)";
|
||||
break;
|
||||
case 408:
|
||||
message = "请求超时(408)";
|
||||
break;
|
||||
case 500:
|
||||
message = "服务器错误(500)";
|
||||
break;
|
||||
case 501:
|
||||
message = "服务未实现(501)";
|
||||
break;
|
||||
case 502:
|
||||
message = "网络错误(502)";
|
||||
break;
|
||||
case 503:
|
||||
message = "服务不可用(503)";
|
||||
break;
|
||||
case 504:
|
||||
message = "网络超时(504)";
|
||||
break;
|
||||
case 505:
|
||||
message = "HTTP版本不受支持(505)";
|
||||
break;
|
||||
default:
|
||||
message = `未授权,请重新登录!`;
|
||||
}
|
||||
return `${message}`;
|
||||
};
|
||||
22
tansci-boot-ui/src/views/Index.vue
Normal file
22
tansci-boot-ui/src/views/Index.vue
Normal file
@ -0,0 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, onMounted } from "vue"
|
||||
|
||||
const state = reactive({
|
||||
shadow: 'always',
|
||||
})
|
||||
|
||||
onMounted(()=>{
|
||||
|
||||
})
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<el-card class="home-container" :shadow="state.shadow">
|
||||
首页
|
||||
</el-card>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.home-container{
|
||||
|
||||
}
|
||||
</style>
|
||||
5
tansci-boot-ui/src/views/common/404.vue
Normal file
5
tansci-boot-ui/src/views/common/404.vue
Normal file
@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
404
|
||||
</div>
|
||||
</template>
|
||||
5
tansci-boot-ui/src/views/common/500.vue
Normal file
5
tansci-boot-ui/src/views/common/500.vue
Normal file
@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
500
|
||||
</div>
|
||||
</template>
|
||||
159
tansci-boot-ui/src/views/common/Login.vue
Normal file
159
tansci-boot-ui/src/views/common/Login.vue
Normal file
@ -0,0 +1,159 @@
|
||||
<script setup lang="ts">
|
||||
import {onBeforeMount,reactive,ref,toRefs} from "vue"
|
||||
import type {FormInstance} from 'element-plus'
|
||||
import {useRouter} from 'vue-router'
|
||||
import {login} from '@/api/auth'
|
||||
|
||||
const router = useRouter()
|
||||
const loginFormRef = ref<FormInstance>()
|
||||
|
||||
const logo = new URL('../../assets/image/logo.png', import.meta.url).href
|
||||
const loginLogo = new URL('../../assets/image/login-icon.png', import.meta.url).href
|
||||
|
||||
const state = reactive({
|
||||
loading: false,
|
||||
loginStyle: {
|
||||
height: '',
|
||||
},
|
||||
loginForm: {
|
||||
username: '',
|
||||
password: '',
|
||||
},
|
||||
})
|
||||
|
||||
onBeforeMount(() => {
|
||||
state.loginStyle.height = (document.body.clientHeight || document.documentElement.clientHeight) + "px"
|
||||
})
|
||||
|
||||
function copyYear(){
|
||||
let date = new Date();
|
||||
return date.getFullYear();
|
||||
}
|
||||
|
||||
async function onSubmit(formEl: FormInstance | undefined) {
|
||||
if (!formEl) return;
|
||||
await formEl.validate((valid)=>{
|
||||
if(valid){
|
||||
// 登录成功后设置token到缓存
|
||||
let param:any = {
|
||||
username: state.loginForm.username,
|
||||
password: state.loginForm.password
|
||||
}
|
||||
state.loading = true;
|
||||
login(param).then((res:any) =>{
|
||||
if(res){
|
||||
state.loading = false;
|
||||
router.push({path: 'index'});
|
||||
}
|
||||
}).catch(()=>{
|
||||
state.loading = false;
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<div class="login-container" :style="state.loginStyle">
|
||||
<div class="login-header">
|
||||
<div>
|
||||
<el-image :src="logo" style="width: 100%; height: 100%;"></el-image>
|
||||
</div>
|
||||
<div>
|
||||
<span class="title">Tansci Boot</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="login-main">
|
||||
<div class="main-title">帐号登录</div>
|
||||
<div class="main-container">
|
||||
<div class="logo">
|
||||
<el-image :src="loginLogo" style="width: 100%; height: 100%;"></el-image>
|
||||
</div>
|
||||
<div class="form">
|
||||
<el-form :model="state.loginForm" :rules="rules" size="large" ref="loginFormRef">
|
||||
<el-form-item prop="username" :rules="[
|
||||
{required: true,message: '请输入账号',trigger: 'blur'},
|
||||
{pattern: /^[a-zA-Z]\w{4,17}$/,message: '账号有误,请重新输入',trigger: 'blur'}]">
|
||||
<el-input v-model="state.loginForm.username" prefix-icon="Avatar" placeholder="请输入账号" style="width:100%"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password" :rules="[
|
||||
{required: true,message: '请输入密码',trigger: 'blur'},
|
||||
{pattern: /^[a-zA-Z]\w{5,17}$/,message: '密码有误,请重新输入',trigger: 'blur'}]">
|
||||
<el-input type="password" v-model="state.loginForm.password" prefix-icon="Lock" show-password placeholder="请输入密码" style="width:100%"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="onSubmit(loginFormRef)" :loading="loading" style="width:100%">登录</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="login-footer">
|
||||
<div>
|
||||
<el-link href="https://typ1805.gitee.io" target="_blank">关于作者</el-link>
|
||||
<el-divider direction="vertical" />
|
||||
<el-link href="https://gitee.com/typ1805/tansci-boot" target="_blank">源码地址 Gitee & GitHub</el-link>
|
||||
<el-divider direction="vertical" />
|
||||
<el-link href="https://typ1805.gitee.io" target="_blank">联系作者</el-link>
|
||||
</div>
|
||||
<div class="copy">
|
||||
© {{copyYear()}} Tansci Boot 版权所有
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss" scoped="scoped">
|
||||
.login-container {
|
||||
background-image: radial-gradient( white 0%, #FAFDFE 10%, #ddf8e7 50%, #FAFDFE 90%, white 100%);
|
||||
// background-image: radial-gradient(#ddf8e7 00%, #FAFDFE 80%, white 100%);
|
||||
.login-header{
|
||||
width: 100%;
|
||||
height: 5rem;
|
||||
line-height: 5rem;
|
||||
display: flex;
|
||||
padding: 0 20%;
|
||||
.title{
|
||||
padding: 0 1rem;
|
||||
color: var(--t9);
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
.login-main{
|
||||
height: 80%;
|
||||
.main-title{
|
||||
font-size: 32px;
|
||||
text-align: center;
|
||||
padding: 6rem 0;
|
||||
}
|
||||
.main-container{
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
.logo{
|
||||
width: 36rem;
|
||||
padding-right: 2rem;
|
||||
// transition: all .2s;
|
||||
}
|
||||
// .logo:hover{
|
||||
// transform: scaleY(1.1) scaleX(1.1) translateZ(0);
|
||||
// }
|
||||
.form{
|
||||
width: 26rem;
|
||||
padding-left: 4rem;
|
||||
border-left: 1px solid #c7f6da;
|
||||
}
|
||||
}
|
||||
}
|
||||
.login-footer{
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
color: #606266;
|
||||
padding-top: 1.2rem;
|
||||
.copy{
|
||||
padding-top: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
5
tansci-boot-ui/src/views/system/Manu.vue
Normal file
5
tansci-boot-ui/src/views/system/Manu.vue
Normal file
@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
App
|
||||
</div>
|
||||
</template>
|
||||
5
tansci-boot-ui/src/views/system/Org.vue
Normal file
5
tansci-boot-ui/src/views/system/Org.vue
Normal file
@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
help
|
||||
</div>
|
||||
</template>
|
||||
5
tansci-boot-ui/src/views/system/User.vue
Normal file
5
tansci-boot-ui/src/views/system/User.vue
Normal file
@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
help
|
||||
</div>
|
||||
</template>
|
||||
31
tansci-boot-ui/tsconfig.json
Normal file
31
tansci-boot-ui/tsconfig.json
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"typeRoots": [
|
||||
"node_modules/@types", // 默认值
|
||||
"src/types"
|
||||
],
|
||||
"baseUrl": "./",
|
||||
"target": "esnext",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"strict": true,
|
||||
"jsx": "preserve",
|
||||
"sourceMap": true,
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
"lib": ["esnext", "dom"],
|
||||
"paths": {
|
||||
"@": ["src"],
|
||||
"@/*": ["src/*"]
|
||||
},
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.d.ts",
|
||||
"src/**/*.tsx",
|
||||
"src/**/*.vue",
|
||||
"src/**/**/*.vue"
|
||||
]
|
||||
}
|
||||
43
tansci-boot-ui/vite.config.ts
Normal file
43
tansci-boot-ui/vite.config.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import path from 'path'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import Unocss from 'unocss/vite'
|
||||
import {
|
||||
presetAttributify,
|
||||
presetIcons,
|
||||
presetUno,
|
||||
transformerDirectives,
|
||||
transformerVariantGroup,
|
||||
} from 'unocss'
|
||||
|
||||
export default defineConfig({
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, 'src'),
|
||||
},
|
||||
},
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
scss: {
|
||||
additionalData: `@use "@/styles/element/index.scss" as *;`,
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
vue(),
|
||||
Unocss({
|
||||
presets: [
|
||||
presetUno(),
|
||||
presetAttributify(),
|
||||
presetIcons({
|
||||
scale: 1.2,
|
||||
warn: true,
|
||||
}),
|
||||
],
|
||||
transformers: [
|
||||
transformerDirectives(),
|
||||
transformerVariantGroup(),
|
||||
]
|
||||
}),
|
||||
]
|
||||
})
|
||||
Loading…
Reference in New Issue
Block a user