This commit is contained in:
sc 2021-04-20 16:27:22 +08:00
parent 7fbe8d10fd
commit 834b9b0756
31 changed files with 973 additions and 51 deletions

12
.editorconfig Normal file
View File

@ -0,0 +1,12 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = tab
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false

37
.gitignore vendored
View File

@ -1,18 +1,25 @@
# Build and Release Folders
bin-debug/
bin-release/
[Oo]bj/
[Bb]in/
.DS_Store
node_modules
/dist
# Other files and folders
.settings/
# Executables
*.swf
*.air
*.ipa
*.apk
# local env files
.env.local
.env.*.local
# Project files, i.e. `.project`, `.actionScriptProperties` and `.flexProperties`
# should NOT be excluded as they contain compiler settings and other important
# information for Eclipse / Flash Builder.
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
package-lock.json

View File

@ -1,36 +0,0 @@
# SCUI
#### Description
SCUI
#### Software Architecture
Software architecture description
#### Installation
1. xxxx
2. xxxx
3. xxxx
#### Instructions
1. xxxx
2. xxxx
3. xxxx
#### Contribution
1. Fork the repository
2. Create Feat_xxx branch
3. Commit your code
4. Create Pull Request
#### Gitee Feature
1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md
2. Gitee blog [blog.gitee.com](https://blog.gitee.com)
3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore)
4. The most valuable open source project [GVP](https://gitee.com/gvp)
5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help)
6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)

5
babel.config.js Normal file
View File

@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

54
package.json Normal file
View File

@ -0,0 +1,54 @@
{
"name": "element-plus-starter",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"axios": "^0.21.0",
"core-js": "^3.6.5",
"element-plus": "latest",
"less": "^3.12.2",
"less-loader": "^7.1.0",
"nprogress": "^0.2.0",
"vue": "^3.0.0",
"vue-router": "^4.0.0-0",
"vuex": "^4.0.0-0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"@vue/compiler-sfc": "^3.0.0",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^7.0.0-0"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/vue3-essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "babel-eslint"
},
"rules": {
"indent": 0,
"no-tabs": 0,
"no-mixed-spaces-and-tabs": 0,
"vue/no-unused-components": "off"
}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

18
public/index.html Normal file
View File

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>Welcome to Element Plus</title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript
enabled. Please enable it to continue.</strong>
</noscript>
<div id="app" class="aminui"></div>
<!-- built files will be auto injected -->
</body>
</html>

68
public/json/user.json Normal file
View File

@ -0,0 +1,68 @@
{
"code": 200,
"data": {
"userInfo" : {
"userName":"Admin"
},
"menuList" : [
{
"name": "组件",
"path": "/vab",
"meta": {
"icon":"el-icon-takeaway-box"
},
"children": [
{
"path": "/vab/list",
"name": "列表",
"meta": {},
"component": "list"
},
{
"path": "/vab/show",
"name": "详情",
"meta": {},
"component": "show"
},
{
"path": "/vab/lala",
"name": "404",
"meta": {},
"component": "lala"
}
]
},
{
"name": "其他",
"path": "/other",
"meta": {
"icon":"el-icon-more"
},
"children": [
{
"path": "/other/1",
"name": "other1",
"meta": {},
"component": "list"
}
]
},
{
"name": "配置",
"path": "/setting",
"meta": {
"icon":"el-icon-setting"
},
"children": [
{
"path": "/setting/1",
"name": "setting1",
"meta": {},
"component": "lala"
}
]
}
]
},
"message": ""
}

14
src/App.vue Normal file
View File

@ -0,0 +1,14 @@
<template>
<router-view></router-view>
</template>
<script>
export default {
name: 'App',
setup() {}
}
</script>
<style lang="less">
@import '@/style/style.less';
</style>

13
src/api/index.js Normal file
View File

@ -0,0 +1,13 @@
import config from "@/config";
import http from "@/utils/request";
const api = {
user: {
info: async function(){
return await http.get(`${config.apiUrl}/json/user.json`);
}
}
}
export default api;

BIN
src/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

View File

@ -0,0 +1,32 @@
<template>
<div class="hello">
<h1>{{ msg }}</h1>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>

14
src/config/index.js Normal file
View File

@ -0,0 +1,14 @@
module.exports = {
//标题
title: "VUE",
//版本号
version: "1.0",
//接口地址
apiUrl: "",
//是否开启多标签
tags: false,
//布局
layout: "default",
//主题
theme: "default",
};

View File

@ -0,0 +1,27 @@
<template>
<label v-for="navMenu in navMenus" v-bind:key="navMenu">
<el-menu-item v-if="!navMenu.children" :index="navMenu.path">
<i v-if="navMenu.meta&&navMenu.meta.icon" :class="navMenu.meta.icon || 'el-icon-menu'"></i>
<span>{{navMenu.name}}</span>
</el-menu-item>
<el-submenu v-if="navMenu.children" :index="navMenu.path">
<template #title>
<i v-if="navMenu.meta&&navMenu.meta.icon" :class="navMenu.meta.icon || 'el-icon-menu'"></i>
<span> {{navMenu.name}}</span>
</template>
<NavMenu :navMenus="navMenu.children"></NavMenu>
</el-submenu>
</label>
</template>
<script>
export default {
name: 'NavMenu',
props: ['navMenus'],
data() {
return {}
},
methods: {}
}
</script>

View File

@ -0,0 +1,14 @@
<template>
<div class="adminui-header">
header
</div>
</template>
<script>
export default {
props: {},
setup() {
}
}
</script>

View File

@ -0,0 +1,80 @@
<template>
<div class="aminui-side-split">
<div class="adminui-side-split-scroll">
<ul>
<li v-for="item in menu" v-bind:key="item" :class="pmenu.path==item.path?'active':''" @click="showMenu(item)"><i :class="item.meta.icon || 'el-icon-menu'"></i>
<p>{{ item.name }}</p>
</li>
</ul>
</div>
</div>
<div class="aminui-side">
<div class="adminui-side-scroll">
<el-menu :default-active="$route.path" router>
<NavMenu :navMenus="nextMenu"></NavMenu>
</el-menu>
</div>
</div>
</template>
<script>
import NavMenu from './NavMenu.vue';
export default {
components: {
NavMenu
},
data() {
return {
menu: [],
nextMenu: [],
pmenu: {}
}
},
created: function() {
var menu = this.$TOOL.data.get("user").menuList;
var home = this.$router.options.routes[0].children[0];
menu.unshift(home);
this.menu = menu;
this.showThis()
},
setup() {
},
watch: {
$route() {
this.showThis()
}
},
methods: {
//
showThis(){
var menu = this.$TOOL.data.get("user").menuList;
var home = this.$router.options.routes[0].children[0];
menu.unshift(home);
var pl = this.$route.path.split("/");
var p = pl.length<=2?home.path:"/" + pl[1];
this.pmenu = p==home.path ? home:this.getRoute(p, menu);
this.nextMenu = this.pmenu.children;
},
//
showMenu(route) {
this.pmenu = route;
this.nextMenu = route.children;
},
//path
getRoute(path, menu){
for (var item of menu) {
if (item.path == path) {
return item;
}
if (item.children) {
this.getRoute(path, item.children);
}
}
}
}
}
</script>

View File

@ -0,0 +1,170 @@
<template>
<div class="adminui-tags">
<ul>
<li v-for="tag in tagList" v-bind:key="tag" :class="isActive(tag) ? 'active' : '' " @contextmenu.prevent="openContextMenu($event, tag)">
<router-link :to="tag">
{{ tag.name }}
<i v-if="!tag.meta.affix" class="el-icon-close" @click.prevent.stop='closeSelectedTag(tag)'></i>
</router-link>
</li>
</ul>
</div>
<ul v-if="contextMenuVisible" :style="{left:left+'px',top:top+'px'}" class="contextmenu" id="contextmenu">
<li @click="refreshTab()">刷新</li>
<hr>
<li @click="closeTabs()" :class="contextMenuItem.meta.affix?'disabled':''">关闭标签</li>
<li @click="closeOtherTabs()">关闭其他标签</li>
</ul>
</template>
<style>
.contextmenu {
position: fixed;
margin:0;
border-radius: 4px;
background: #fff;
border: 1px solid #e4e7ed;
box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);
z-index: 3000;
list-style-type: none;
padding: 10px 0;
}
.contextmenu hr {
margin:5px 0;
border: none;
height: 1px;
font-size: 0px;
background-color: #ebeef5;
}
.contextmenu li {
margin:0;
cursor: pointer;
line-height: 30px;
padding: 0 17px;
color: #606266;
}
.contextmenu li:hover {
background-color: #ecf5ff;
color: #66b1ff;
}
.contextmenu li.disabled {
cursor: not-allowed;
color: #bbb;
background: transparent;
}
</style>
<script>
export default {
data() {
return {
contextMenuVisible: false,
contextMenuItem: null,
left: 0,
top: 0,
tagList: []
}
},
props: {},
setup() {
},
watch: {
$route(e) {
this.addViewTags(e)
},
contextMenuVisible(value) {
var _this = this;
var cm = function(e){
let sp = document.getElementById("contextmenu");
if(sp&&!sp.contains(e.target)){
_this.closeMenu()
}
}
if (value) {
document.body.addEventListener('click', e=>cm(e))
}else{
document.body.removeEventListener('click', e=>cm(e))
}
}
},
mounted() {
this.addViewTags(this.$router.options.routes[0].children[0].children[0]);
this.addViewTags(this.$route);
},
methods: {
//tag
addViewTags(route) {
var ishas = this.tagList.some(item=>{
if(item.path == route.path){
return true
}
})
if(!ishas){
this.tagList.push(route)
}
},
//tag
isActive(route) {
return route.path === this.$route.path
},
//tag
closeSelectedTag(tag) {
const newtagList = this.tagList.filter(item => item.path !== tag.path)
this.tagList = newtagList;
if (this.isActive(tag)) {
const latestView = newtagList.slice(-1)[0]
if (latestView) {
this.$router.push(latestView)
} else {
this.$router.push('/')
}
}
},
//tag
openContextMenu(e, tag){
this.contextMenuItem = tag;
this.contextMenuVisible = true;
this.left = e.clientX + 1;
this.top = e.clientY + 1;
},
//
closeMenu() {
this.contextMenuItem = null;
this.contextMenuVisible = false
},
//TAB
refreshTab(){
var nowTag = this.contextMenuItem;
this.$router.push({
path: nowTag.path
})
this.contextMenuVisible = false
},
//TAB
closeTabs(){
var nowTag = this.contextMenuItem;
if(!nowTag.meta.affix){
this.closeSelectedTag(nowTag)
this.contextMenuVisible = false
}
},
//TAB
closeOtherTabs(){
var nowTag = this.contextMenuItem;
var tags = this.tagList;
tags.forEach(tag => {
if(tag.meta&&tag.meta.affix || nowTag.path==tag.path){
return true
}else{
this.closeSelectedTag(tag)
}
})
this.contextMenuVisible = false
}
}
}
</script>

32
src/layout/index.vue Normal file
View File

@ -0,0 +1,32 @@
<template>
<Side></Side>
<div class="aminui-body">
<Head></Head>
<Tags></Tags>
<div class="adminui-main">
<router-view v-slot="{ Component }">
<keep-alive>
<component :is="Component" />
</keep-alive>
</router-view>
</div>
</div>
</template>
<script>
import Side from './components/side.vue';
import Head from './components/head.vue';
import Tags from './components/tags.vue';
export default {
name: 'index',
setup() {},
components: {
Side,
Head,
Tags
}
}
</script>

20
src/main.js Normal file
View File

@ -0,0 +1,20 @@
import ElementPlus from 'element-plus'
import 'element-plus/lib/theme-chalk/index.css'
import { createApp } from 'vue'
import App from './App.vue'
import config from "./config";
import router from './router'
import store from './store'
import tool from './utils/tool'
import api from './api'
const app = createApp(App);
app.config.globalProperties.$CONFIG = config;
app.config.globalProperties.$TOOL = tool;
app.config.globalProperties.$API = api;
app.use(store);
app.use(router);
app.use(ElementPlus, { size: 'small', zIndex: 3000 });
app.mount('#app');

126
src/router/index.js Normal file
View File

@ -0,0 +1,126 @@
import {createRouter, createWebHashHistory} from 'vue-router';
import { ElNotification } from 'element-plus';
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import tool from '@/utils/tool';
//系统路由
const routes = [{
path: "/",
name: "layout",
component: () => import(/* webpackChunkName: "layout" */ '@/layout'),
redirect: '/dashboard',
children: [
{
path: "/home",
name: "首页",
component: () => import(`@/views/other/empty`),
meta: {
icon: "el-icon-platform-eleme"
},
children: [
{
path: "/dashboard",
name: "控制台",
meta: {
affix: true
},
component: () => import(/* webpackChunkName: "home" */ '@/views/home'),
}
]
}
]
},
{
path: "/login",
name: "登录",
component: () => import(/* webpackChunkName: "login" */ '@/views/login'),
}
]
//系统路由
const routes_404 = {
path: "/:pathMatch(.*)*",
name: "404",
hidden: true,
component: () => import(/* webpackChunkName: "404" */ '@/views/other/404'),
}
const router = createRouter({
history: createWebHashHistory(),
routes: routes
})
//判断是否已加载过API路由
var isGetApiRouter = false;
router.beforeEach(async (to, from, next) => {
NProgress.start()
let userInfo = tool.data.get("user");
if(to.path === "/login"){
next();
return false;
}
if(!userInfo){
next({
path: '/login'
});
return false;
}
//加载API路由
if(!isGetApiRouter){
var apiRouter = filterAsyncRouter(userInfo.menuList);
apiRouter.forEach(item => {
router.addRoute("layout", item)
})
router.addRoute(routes_404)
if (to.matched.length == 0) {
router.push(to.path);
}
isGetApiRouter = true;
}
next();
});
router.afterEach(() => {
NProgress.done()
});
router.onError((error) => {
NProgress.done();
ElNotification.error({
title: '路由错误',
message: error
});
});
//转换
function filterAsyncRouter(routerMap) {
const accessedRouters = []
routerMap.filter(itemRouter => {
accessedRouters.push({
path: itemRouter.path,
name: itemRouter.name,
meta: itemRouter.meta,
children: filterAsyncRouter(itemRouter.children || []),
component: loadComponent(itemRouter.component)
})
})
return accessedRouters
}
function loadComponent(component){
if(component){
return () => import(/* webpackChunkName: "[request]" */ `@/views/${component}`)
}else{
return () => import(`@/views/other/empty`)
}
}
export default router

8
src/store/index.js Normal file
View File

@ -0,0 +1,8 @@
import { createStore } from 'vuex';
import tagsView from './modules/tagsView';
export default createStore({
modules: {
tagsView
}
});

View File

@ -0,0 +1,24 @@
export default {
state: {
visitedViews: [],
cachedViews: []
},
mutations: {
ADD_VISITED_VIEWS: (state, view) => {
if (state.visitedViews.some(v => v.path === view.path)) return
state.visitedViews.push(Object.assign({}, view, {
title: view.meta.title || 'no-name'
}))
if (!view.meta.noCache) {
state.cachedViews.push(view.name)
}
},
},
actions: {
addVisitedViews({
commit
}, view) {
commit('ADD_VISITED_VIEWS', view)
},
}
}

47
src/style/style.less Normal file
View File

@ -0,0 +1,47 @@
#app, body, html {width: 100%;height: 100%;background-color: #f8f8f8;font-size: 12px;}
* {margin: 0;padding: 0;box-sizing: border-box;outline: none;}
/* 全局滚动条样式 */
.scrollable {-webkit-overflow-scrolling: touch;}
::-webkit-scrollbar {width: 5px;height: 5px;}
::-webkit-scrollbar-thumb {background-color: rgba(50, 50, 50, 0.3);}
::-webkit-scrollbar-thumb:hover {background-color: rgba(50, 50, 50, 0.6);}
::-webkit-scrollbar-track {background-color: rgba(50, 50, 50, 0.1);}
::-webkit-scrollbar-track:hover {background-color: rgba(50, 50, 50, 0.2);}
.aminui-side-split {position: absolute;top:0px;left:0px;bottom:0px;width:65px;background: #333;z-index: 90;}
.adminui-side-split-scroll {position: absolute;top:0px;left:0px;bottom:0px;right:0px;overflow: auto;overflow-x:hidden;}
.aminui-side-split li {cursor: pointer;width: 65px;height: 65px;color: #fff;text-align: center;display: flex;flex-direction: column;align-items: center;justify-content: center;}
.aminui-side-split li i {font-size: 16px;}
.aminui-side-split li p {margin-top:5px;}
.aminui-side-split li:hover {background: #444;}
.aminui-side-split li.active {background: #09f;}
.adminui-side-split-scroll::-webkit-scrollbar-thumb {background-color: rgba(255, 255, 255, 0.4);border-radius:5px;}
.adminui-side-split-scroll::-webkit-scrollbar-thumb:hover {background-color: rgba(255, 255, 255, 0.5);}
.adminui-side-split-scroll::-webkit-scrollbar-track {background-color: rgba(255, 255, 255, 0);}
.adminui-side-split-scroll::-webkit-scrollbar-track:hover {background-color: rgba(255, 255, 255, 0);}
.aminui-side {position: absolute;z-index: 80;top:0px;left:65px;bottom:0px;width:210px;background: #fff;box-shadow: 2px 0 8px 0 rgba(29,35,41,.05);border-right: 1px solid #e6e6e6;}
.adminui-side-scroll {position: absolute;top:0px;left:0px;bottom:0px;right:0px;overflow: auto;overflow-x:hidden;}
.aminui-body {position: absolute;z-index: 70;top:0px;left:275px;bottom:0px;right:0px;}
.adminui-header {height: 50px;border-bottom: 1px solid #ebeef5;background: #fff;box-shadow: 0 1px 4px rgba(0,21,41,.08);}
.adminui-tags {height:35px;background: #fff;border-bottom: 1px solid #e6e6e6;box-shadow: 0 1px 4px rgba(0,21,41,.08);}
.adminui-tags li {cursor: pointer;display: inline-block;float: left;height:34px;line-height: 34px;}
.adminui-tags li a {display: inline-block;padding:0 10px;width:100%;height:100%;color: #999;text-decoration:none;}
.adminui-tags li i {margin-left:10px;padding:3px;}
.adminui-tags li i:hover {background: #ff5722;color: #fff;}
.adminui-tags li:hover {background: #ecf5ff;}
.adminui-tags li.active {background: #09f;}
.adminui-tags li.active a {color: #fff;}
.adminui-main {position: absolute;top:85px;left:0px;right:0px;bottom:0px;overflow: auto;}
.el-menu {border: none!important;}
.el-menu-item.is-active {background: #ecf5ff;}

78
src/utils/request.js Normal file
View File

@ -0,0 +1,78 @@
import axios from 'axios';
import { ElNotification } from 'element-plus';
axios.defaults.baseURL = ''
axios.defaults.timeout = 10000
// HTTP request 拦截器
axios.interceptors.request.use(
(config) => {
return config;
},
(error) => {
return Promise.reject(error);
}
);
// HTTP response 拦截器
axios.interceptors.response.use(
(response) => {
return response;
},
(error) => {
if(error.response.status == 404){
ElNotification.error({
title: '请求错误',
message: "Status:404正在请求不存在的服务器记录"
});
}
if(error.response.status == 500){
ElNotification.error({
title: '请求错误',
message: "Status:500服务器发生错误"
});
}
return Promise.reject(error.response);
}
);
var http = {
/** get
* @param {接口地址} url
* @param {请求参数} params
*/
get: function(url, params) {
return new Promise((resolve, reject) => {
axios.get(url, {
params: params
})
.then((response) => {
resolve(response.data);
})
.catch((error) => {
reject(error);
});
})
},
/** post
* @param {接口地址} url
* @param {请求参数} params
*/
post: function(url, params) {
return new Promise((resolve, reject) => {
axios.post(url, params)
.then((response) => {
resolve(response.data);
})
.catch((error) => {
reject(error);
});
})
}
}
export default http;

20
src/utils/tool.js Normal file
View File

@ -0,0 +1,20 @@
const tool = {
/* localStorage */
data : {
set : function(table, settings){
var _set = JSON.stringify(settings)
return localStorage.setItem(table, _set);
},
get : function(table){
return JSON.parse(localStorage.getItem(table));
},
remove : function(table){
return localStorage.removeItem(table);
},
clear : function(){
return localStorage.clear();
}
}
}
export default tool

9
src/views/home.vue Normal file
View File

@ -0,0 +1,9 @@
<template>
<div>HOME</div>
</template>
<script>
</script>
<style>
</style>

10
src/views/list.vue Normal file
View File

@ -0,0 +1,10 @@
<template>
<div>LIST</div>
<div style="height:1800px"></div>
</template>
<script>
</script>
<style>
</style>

24
src/views/login.vue Normal file
View File

@ -0,0 +1,24 @@
<template>
<h2>LOGIN</h2>
<el-button type="primary" @click='login()'>模拟登录</el-button>
</template>
<script>
export default {
data() {
return {
}
},
created: function() {
this.$TOOL.data.remove("user")
},
methods: {
login: async function() {
var userInfo = await this.$API.user.info();
this.$TOOL.data.set("user", userInfo.data);
this.$router.push({path: '/'});
}
}
}
</script>

9
src/views/other/404.vue Normal file
View File

@ -0,0 +1,9 @@
<template>
<div>404</div>
</template>
<script>
</script>
<style>
</style>

View File

@ -0,0 +1,3 @@
<template>
<router-view></router-view>
</template>

20
src/views/show.vue Normal file
View File

@ -0,0 +1,20 @@
<template>
<div>SHOW</div>
<div class="main">
<el-input v-model="input" placeholder="请输入内容" clearable></el-input>
</div>
</template>
<script>
export default {
data() {
return {
input: ''
};
}
}
</script>
<style>
.main {width:200px;}
</style>