This commit is contained in:
sakuya 2021-05-16 23:05:19 +08:00
parent cbbd57511b
commit be880650e2
15 changed files with 352 additions and 84 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "SCUI-Admin", "name": "SCUI-Admin",
"version": "1.0.4", "version": "1.0.9",
"private": true, "private": true,
"scripts": { "scripts": {
"serve": "vue-cli-service serve", "serve": "vue-cli-service serve",

View File

@ -10,14 +10,16 @@
"path": "/vab", "path": "/vab",
"meta": { "meta": {
"title": "组件", "title": "组件",
"icon": "el-icon-takeaway-box" "icon": "el-icon-takeaway-box",
"type": "menu"
}, },
"children": [ "children": [
{ {
"path": "/vab/drag", "path": "/vab/drag",
"name": "drag", "name": "drag",
"meta": { "meta": {
"title": "拖拽排序" "title": "拖拽排序",
"type": "menu"
}, },
"component": "vab/drag" "component": "vab/drag"
}, },
@ -25,7 +27,8 @@
"path": "/vab/chart", "path": "/vab/chart",
"name": "chart", "name": "chart",
"meta": { "meta": {
"title": "图表 Echarts" "title": "图表 Echarts",
"type": "menu"
}, },
"component": "vab/chart" "component": "vab/chart"
}, },
@ -33,7 +36,8 @@
"path": "/vab/filterbar", "path": "/vab/filterbar",
"name": "filterBar", "name": "filterBar",
"meta": { "meta": {
"title": "过滤器" "title": "过滤器",
"type": "menu"
}, },
"component": "vab/filterBar" "component": "vab/filterBar"
}, },
@ -41,7 +45,8 @@
"path": "/vab/print", "path": "/vab/print",
"name": "print", "name": "print",
"meta": { "meta": {
"title": "打印" "title": "打印",
"type": "menu"
}, },
"component": "vab/print" "component": "vab/print"
} }
@ -52,14 +57,16 @@
"path": "/template", "path": "/template",
"meta": { "meta": {
"title": "模板", "title": "模板",
"icon": "el-icon-files" "icon": "el-icon-files",
"type": "menu"
}, },
"children": [ "children": [
{ {
"path": "/template/blank", "path": "/template/blank",
"name": "blank", "name": "blank",
"meta": { "meta": {
"title": "空白模板" "title": "空白模板",
"type": "menu"
}, },
"component": "template/blank" "component": "template/blank"
}, },
@ -67,7 +74,8 @@
"path": "/template/list", "path": "/template/list",
"name": "list", "name": "list",
"meta": { "meta": {
"title": "普通列表" "title": "普通列表",
"type": "menu"
}, },
"component": "template/list" "component": "template/list"
}, },
@ -75,7 +83,8 @@
"path": "/template/sideList", "path": "/template/sideList",
"name": "sideList", "name": "sideList",
"meta": { "meta": {
"title": "分栏列表" "title": "分栏列表",
"type": "menu"
}, },
"component": "template/sideList" "component": "template/sideList"
}, },
@ -84,6 +93,7 @@
"name": "show", "name": "show",
"meta": { "meta": {
"title": "详情", "title": "详情",
"type": "menu",
"hidden": true "hidden": true
}, },
"component": "template/show" "component": "template/show"
@ -92,7 +102,8 @@
"path": "/template/itemList", "path": "/template/itemList",
"name": "itemList", "name": "itemList",
"meta": { "meta": {
"title": "个性列表" "title": "个性列表",
"type": "menu"
}, },
"component": "template/itemList" "component": "template/itemList"
} }
@ -103,7 +114,8 @@
"path": "/other", "path": "/other",
"meta": { "meta": {
"title": "其他", "title": "其他",
"icon": "el-icon-more" "icon": "el-icon-more",
"type": "menu"
}, },
"children": [ "children": [
{ {
@ -111,7 +123,8 @@
"name": "外部链接", "name": "外部链接",
"meta": { "meta": {
"title": "外部链接", "title": "外部链接",
"icon": "el-icon-link" "icon": "el-icon-link",
"type": "menu"
}, },
"children": [ "children": [
{ {
@ -119,7 +132,7 @@
"name": "百度", "name": "百度",
"meta": { "meta": {
"title": "百度", "title": "百度",
"target": "_blank" "type": "link"
} }
}, },
{ {
@ -127,7 +140,7 @@
"name": "谷歌", "name": "谷歌",
"meta": { "meta": {
"title": "谷歌", "title": "谷歌",
"target": "_blank" "type": "link"
} }
} }
] ]
@ -137,21 +150,24 @@
"name": "Iframe", "name": "Iframe",
"meta": { "meta": {
"title": "Iframe", "title": "Iframe",
"icon": "el-icon-position" "icon": "el-icon-position",
"type": "menu"
}, },
"children": [ "children": [
{ {
"path": "https://vue3js.cn/docs/zh/", "path": "https://vue3js.cn/docs/zh/",
"name": "VUE 3", "name": "vue3",
"meta": { "meta": {
"title": "VUE 3" "title": "VUE 3",
"type": "iframe"
} }
}, },
{ {
"path": "https://element-plus.org?id=1", "path": "https://element-plus.org?id=1",
"name": "Element Plus", "name": "elementplus",
"meta": { "meta": {
"title": "Element Plus" "title": "Element Plus",
"type": "iframe"
} }
} }
] ]
@ -163,23 +179,25 @@
"path": "/test", "path": "/test",
"meta": { "meta": {
"title": "实验室", "title": "实验室",
"icon": "el-icon-folder" "icon": "el-icon-folder",
}, "type": "menu"
"children": [] }
}, },
{ {
"name": "setting", "name": "setting",
"path": "/setting", "path": "/setting",
"meta": { "meta": {
"title": "配置", "title": "配置",
"icon": "el-icon-setting" "icon": "el-icon-setting",
"type": "menu"
}, },
"children": [ "children": [
{ {
"path": "/setting/system", "path": "/setting/system",
"name": "system", "name": "system",
"meta": { "meta": {
"title": "系统设置" "title": "系统设置",
"type": "menu"
}, },
"component": "setting/system" "component": "setting/system"
}, },
@ -187,7 +205,8 @@
"path": "/setting/user", "path": "/setting/user",
"name": "user", "name": "user",
"meta": { "meta": {
"title": "用户管理" "title": "用户管理",
"type": "menu"
}, },
"component": "setting/user" "component": "setting/user"
}, },
@ -195,7 +214,8 @@
"path": "/setting/role", "path": "/setting/role",
"name": "role", "name": "role",
"meta": { "meta": {
"title": "角色管理" "title": "角色管理",
"type": "menu"
}, },
"component": "setting/role" "component": "setting/role"
}, },
@ -203,7 +223,8 @@
"path": "/setting/dic", "path": "/setting/dic",
"name": "dic", "name": "dic",
"meta": { "meta": {
"title": "字典管理" "title": "字典管理",
"type": "menu"
}, },
"component": "setting/dic" "component": "setting/dic"
}, },
@ -211,7 +232,8 @@
"path": "/setting/menu", "path": "/setting/menu",
"name": "settingMenu", "name": "settingMenu",
"meta": { "meta": {
"title": "菜单管理" "title": "菜单管理",
"type": "menu"
}, },
"component": "setting/menu" "component": "setting/menu"
}, },
@ -219,7 +241,8 @@
"path": "/setting/client", "path": "/setting/client",
"name": "client", "name": "client",
"meta": { "meta": {
"title": "应用管理" "title": "应用管理",
"type": "menu"
}, },
"component": "setting/client" "component": "setting/client"
}, },
@ -227,7 +250,8 @@
"path": "/setting/log", "path": "/setting/log",
"name": "log", "name": "log",
"meta": { "meta": {
"title": "系统日志" "title": "系统日志",
"type": "menu"
}, },
"component": "setting/log" "component": "setting/log"
} }

View File

@ -81,9 +81,21 @@ const api = {
} }
} }
}, },
menu: {
list: {
url: `${config.API_URL}/json/login.json`,
name: "菜单管理",
get: async function(){
// 这里接口对象偷懒重复了登录接口
var res = await http.get(this.url);
res.data = res.data.menuList;
return res;
}
}
},
log: { log: {
list: { list: {
url: `${config.API_URL}/json/log.json`, url: `${config.MOCK_URL}/loglist`,
name: "日志列表", name: "日志列表",
get: async function(){ get: async function(){
return await http.get(this.url); return await http.get(this.url);
@ -92,7 +104,7 @@ const api = {
}, },
demo: { demo: {
upload: { upload: {
url: `https://www.fastmock.site/mock/44c807475f7eeba73409792255781935/api/upload`, url: `${config.MOCK_URL}/upload`,
name: "文件上传接口" name: "文件上传接口"
}, },
select: { select: {

View File

@ -19,7 +19,9 @@
</el-table> </el-table>
</div> </div>
<div class="scTable-page"> <div class="scTable-page">
<el-pagination background :small="true" :layout="paginationLayout" :total="total" :page-size="pageSize" v-model:currentPage="currentPage" @current-change="reload"></el-pagination> <div class="scTable-pagination">
<el-pagination v-if="!hidePagination" background :small="true" :layout="paginationLayout" :total="total" :page-size="pageSize" v-model:currentPage="currentPage" @current-change="reload"></el-pagination>
</div>
<div class="scTable-do" v-if="!hideDo"> <div class="scTable-do" v-if="!hideDo">
<el-button @click="refresh" icon="el-icon-refresh" circle style="margin-left:15px"></el-button> <el-button @click="refresh" icon="el-icon-refresh" circle style="margin-left:15px"></el-button>
<el-popover placement="top" title="设置" :width="500" trigger="click"> <el-popover placement="top" title="设置" :width="500" trigger="click">
@ -47,6 +49,7 @@
data: { type: Object, default: () => {} }, data: { type: Object, default: () => {} },
rowKey: { type: String, default: "" }, rowKey: { type: String, default: "" },
column: { type: Object, default: () => {} }, column: { type: Object, default: () => {} },
hidePagination: { type: Boolean, default: false },
hideDo: { type: Boolean, default: false }, hideDo: { type: Boolean, default: false },
stripe: { type: Boolean, default: false }, stripe: { type: Boolean, default: false },
highlightCurrentRow: { type: Boolean, default: false }, highlightCurrentRow: { type: Boolean, default: false },

View File

@ -3,10 +3,13 @@ const DEFAULT_CONFIG = {
APP_NAME: "SCUI", APP_NAME: "SCUI",
//版本号 //版本号
APP_VER: "1.0.4", APP_VER: "1.0.9",
//接口地址 //接口地址
API_URL: "", API_URL: "",
//MOCK接口地址
MOCK_URL: "https://www.fastmock.site/mock/44c807475f7eeba73409792255781935/api",
//是否开启多标签 //是否开启多标签
LAYOUT_TAGS: true, LAYOUT_TAGS: true,

View File

@ -4,7 +4,7 @@
</div> </div>
<template v-for="navMenu in navMenus" v-bind:key="navMenu"> <template v-for="navMenu in navMenus" v-bind:key="navMenu">
<el-menu-item v-if="!navMenu.children" :index="navMenu.path"> <el-menu-item v-if="!navMenu.children" :index="navMenu.path">
<a v-if="navMenu.meta&&navMenu.meta.target" :href="navMenu.path" :target="navMenu.meta.target" @click.stop='a'></a> <a v-if="navMenu.meta&&navMenu.meta.type=='link'" :href="navMenu.path" target="_blank" @click.stop='a'></a>
<i v-if="navMenu.meta&&navMenu.meta.icon" :class="navMenu.meta.icon || 'el-icon-menu'"></i> <i v-if="navMenu.meta&&navMenu.meta.icon" :class="navMenu.meta.icon || 'el-icon-menu'"></i>
<span>{{navMenu.meta.title}}</span> <span>{{navMenu.meta.title}}</span>
</el-menu-item> </el-menu-item>

View File

@ -67,15 +67,15 @@
// //
filterUrl(map){ filterUrl(map){
var newMap = [] var newMap = []
map.forEach(item => { map && map.forEach(item => {
item.meta = item.meta?item.meta:{}; item.meta = item.meta?item.meta:{};
// //
if(item.meta.hidden){ if(item.meta.hidden){
return false return false
} }
//http //http
if(item.path.startsWith('http') && item.meta.target!='_blank'){ if(item.meta.type=='iframe'){
item.path = `/${encodeURIComponent(item.path)}`; item.path = `/i/${item.name}`;
} }
// //
if(item.children&&item.children.length > 0){ if(item.children&&item.children.length > 0){

View File

@ -56,8 +56,8 @@
map.splice(index, 1); map.splice(index, 1);
} }
//http //http
if(item.path.startsWith('http') && item.meta.target!='_blank'){ if(item.meta.type=='iframe'){
item.path = `/${encodeURIComponent(item.path)}`; item.path = `/i/${item.name}`;
} }
// //
if(item.children&&item.children.length > 0){ if(item.children&&item.children.length > 0){

View File

@ -132,8 +132,9 @@ function filterAsyncRouter(routerMap) {
routerMap.forEach(item => { routerMap.forEach(item => {
item.meta = item.meta?item.meta:{}; item.meta = item.meta?item.meta:{};
//处理外部链接特殊路由 //处理外部链接特殊路由
if(item.path.startsWith('http') && item.meta.target!='_blank'){ if(item.meta.type=='iframe'){
item.path = `/${encodeURIComponent(item.path)}`; item.meta.url = item.path;
item.path = `/i/${item.name}`;
item.component = 'other/iframe'; item.component = 'other/iframe';
} }
//MAP转路由对象 //MAP转路由对象

View File

@ -22,6 +22,18 @@
data() { data() {
return { return {
activities: [ activities: [
{
content: [
"[新增] 组件sortablejs",
"[新增] 7类系统基础页面",
"[新增] 表格新增paramshidePagination参数",
"[新增] 新的全局组件 sc-upload",
"[优化] 路由表新增type属性 判断菜单、链接等",
"[优化] iframe的表现形式,地址栏用别名代替",
'[修复] 图表和表格切换标签时宽高异常'
],
timestamp: '2021-05-16'
},
{ {
content: [ content: [
"[新增] 打印工具", "[新增] 打印工具",

View File

@ -6,8 +6,11 @@
export default { export default {
data() { data() {
return { return {
src: decodeURIComponent(this.$route.fullPath.substr(1)) src: this.$route.meta.url
} }
},
mounted() {
} }
} }
</script> </script>

View File

@ -12,47 +12,32 @@
</div> </div>
</el-header> </el-header>
<el-main class="nopadding"> <el-main class="nopadding">
<scTable ref="table" :apiObj="apiObj" stripe> <scTable ref="table" :apiObj="apiObj" stripe highlightCurrentRow @currentChange="currentChange">
<el-table-column label="#" type="index" width="50"></el-table-column> <el-table-column label="#" type="index" width="50"></el-table-column>
<el-table-column label="ID" prop="id" width="80"></el-table-column> <el-table-column label="ID" prop="id" width="180"></el-table-column>
<el-table-column label="日志名" prop="id" width="150"></el-table-column> <el-table-column label="日志名" prop="name" width="150"></el-table-column>
<el-table-column label="请求接口" prop="id" width="150"></el-table-column> <el-table-column label="请求接口" prop="url" width="150"></el-table-column>
<el-table-column label="请求方法" prop="id" width="150"></el-table-column> <el-table-column label="请求方法" prop="type" width="150"></el-table-column>
<el-table-column label="客户端IP" prop="id" width="150"></el-table-column> <el-table-column label="用户" prop="user" width="150"></el-table-column>
<el-table-column label="日志时间" prop="id" width="150"></el-table-column> <el-table-column label="客户端IP" prop="cip" width="150"></el-table-column>
<el-table-column label="日志时间" prop="time" width="150"></el-table-column>
</scTable> </scTable>
</el-main> </el-main>
</el-container> </el-container>
<el-aside width="340px" style="border-left: 1px solid #e6e6e6;border-right: 0;padding:20px;"> <el-aside width="340px" style="border-left: 1px solid #e6e6e6;border-right: 0;padding:20px;">
<info ref="info"></info>
<el-descriptions title="基础信息" :column="1" size="small">
<el-descriptions-item label="请求接口">/oauth/token</el-descriptions-item>
<el-descriptions-item label="请求方法">POST</el-descriptions-item>
<el-descriptions-item label="状态代码">200</el-descriptions-item>
<el-descriptions-item label="日志名">登录用户验证</el-descriptions-item>
<el-descriptions-item label="日志时间">2021-05-15 22:15:35</el-descriptions-item>
</el-descriptions>
<el-collapse>
<el-collapse-item title="Request" name="1">
<div>
Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198
Safari/537.36
</div>
</el-collapse-item>
<el-collapse-item title="Response" name="2">
<div>
id=100
</div>
</el-collapse-item>
</el-collapse>
</el-aside> </el-aside>
</el-container> </el-container>
</template> </template>
<script> <script>
import info from './info'
export default { export default {
name: 'log', name: 'log',
components: {
info
},
data() { data() {
return { return {
apiObj: this.$API.log.list, apiObj: this.$API.log.list,
@ -64,6 +49,9 @@
methods: { methods: {
upsearch(){ upsearch(){
},
currentChange(row){
this.$refs.info.setData(row)
} }
} }
} }

View File

@ -0,0 +1,48 @@
<template>
<template v-if="data&&data.id">
<el-descriptions title="基础信息" :column="1" size="small">
<el-descriptions-item label="请求接口">{{data.url}}</el-descriptions-item>
<el-descriptions-item label="请求方法">{{data.type}}</el-descriptions-item>
<el-descriptions-item label="状态代码">{{data.code}}</el-descriptions-item>
<el-descriptions-item label="日志名">{{data.name}}</el-descriptions-item>
<el-descriptions-item label="日志时间">{{data.time}}</el-descriptions-item>
</el-descriptions>
<el-collapse>
<el-collapse-item title="Request" name="1">
<div>
Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198
Safari/537.36
</div>
</el-collapse-item>
<el-collapse-item title="Response" name="2">
<div>
id=100
</div>
</el-collapse-item>
</el-collapse>
</template>
<template v-else>
<el-empty description="没有预览" :image-size="120"></el-empty>
</template>
</template>
<script>
export default {
data() {
return {
data: {}
}
},
methods: {
setData(data){
this.data = data
}
}
}
</script>
<style>
</style>

View File

@ -1,28 +1,91 @@
<template> <template>
<el-container> <el-container>
<el-header> <el-aside width="300px">
<div class="left-panel"> <el-container>
<el-button type="primary" icon="el-icon-plus" @click="add"></el-button> <el-header>
</div> <el-input placeholder="输入关键字进行过滤" v-model="menuFilterText" clearable></el-input>
</el-header> </el-header>
<el-main class="nopadding"> <el-main class="nopadding">
</el-main> <el-tree ref="menu" node-key="name" :data="menuList" :props="menuProps" highlight-current :expand-on-click-node="false" check-strictly show-checkbox :filter-node-method="menuFilterNode" @node-click="menuClick">
<template #default="{node, data}">
<span class="custom-tree-node">
<span class="label">{{ node.label }}</span>
<span class="do">
<i class="el-icon-plus" @click.stop="add(node, data)"></i>
</span>
</span>
</template>
</el-tree>
</el-main>
</el-container>
</el-aside>
<el-container>
<el-main class="nopadding" style="padding:20px;">
<save ref="save" :menu="menuList"></save>
</el-main>
</el-container>
</el-container> </el-container>
</template> </template>
<script> <script>
import save from './save'
export default { export default {
name: "settingMenu", name: "settingMenu",
components: {
save
},
data(){ data(){
return {} return {
menuList: [],
menuProps: {
label: (data)=>{
return data.meta.title
}
},
menuFilterText: ""
}
},
watch: {
menuFilterText(val){
this.$refs.menu.filter(val);
}
},
mounted() {
this.getMenu();
}, },
methods: { methods: {
add(){ //
async getMenu(){
var res = await this.$API.menu.list.get();
this.menuList = res.data;
},
//
menuClick(data, node){
var pid = node.level==1?undefined:node.parent.data.name;
this.$refs.save.setData(data, pid)
},
//
menuFilterNode(value, data){
if (!value) return true;
var targetText = data.name;
return targetText.indexOf(value) !== -1;
},
//
add(node, data){
console.log(node, data);
} }
} }
} }
</script> </script>
<style> <style scoped>
.custom-tree-node {display: flex;flex: 1;align-items: center;justify-content: space-between;font-size: 14px;padding-right: 24px;height:100%;}
.custom-tree-node .do {display: none;}
.custom-tree-node .do i {margin-left:5px;color: #999;padding:5px;}
.custom-tree-node .do i:hover {color: #333;}
.custom-tree-node:hover .do {display: inline-block;}
</style> </style>

View File

@ -0,0 +1,111 @@
<template>
<el-row>
<el-col :xl="12" :lg="16">
<h2>{{form.meta.title || "新增菜单"}}</h2>
<el-form :model="form" :rules="rules" ref="dialogForm" label-width="80px" label-position="left">
<el-form-item label="显示名称" prop="meta.title">
<el-input v-model="form.meta.title" clearable placeholder="菜单显示名字"></el-input>
</el-form-item>
<el-form-item label="上级菜单" prop="parent">
<el-cascader v-model="form.parent" :options="menu" :props="menuProps" :show-all-levels="false" clearable></el-cascader>
<div class="el-form-item-msg">这里还有点问题el-cascader的props的label 现暂只支持字符串还未支持自定义函数 </div>
</el-form-item>
<el-form-item label="类型" prop="meta.type">
<el-radio-group v-model="form.meta.type">
<el-radio-button label="menu">菜单</el-radio-button>
<el-radio-button label="iframe">Iframe</el-radio-button>
<el-radio-button label="link">外链</el-radio-button>
<el-radio-button label="button">按钮</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="别名" prop="name">
<el-input v-model="form.name" clearable placeholder="菜单别名"></el-input>
<div class="el-form-item-msg">系统唯一且与内置组件名一致否则导致缓存失效如类型为Iframe的菜单别名将代替源地址显示在地址栏</div>
</el-form-item>
<el-form-item label="菜单图标" prop="meta.icon">
<el-input v-model="form.meta.icon" clearable placeholder=""></el-input>
</el-form-item>
<el-form-item label="路由地址" prop="path">
<el-input v-model="form.path" clearable placeholder=""></el-input>
</el-form-item>
<el-form-item label="视图" prop="component">
<el-autocomplete v-model="form.component" :fetch-suggestions="querySearch" :debounce="10" clearable placeholder=""></el-autocomplete>
<div class="el-form-item-msg">如父节点链接或Iframe等没有视图的菜单不需要填写</div>
</el-form-item>
<el-form-item label="是否隐藏" prop="meta.hidden">
<el-checkbox v-model="form.meta.hidden">隐藏菜单</el-checkbox>
<div class="el-form-item-msg">菜单不显示在导航中但用户依然可以访问例如详情页</div>
</el-form-item>
<el-form-item>
<el-button type="primary"> </el-button>
</el-form-item>
</el-form>
</el-col>
</el-row>
</template>
<script>
export default {
props: {
menu: { type: Object, default: () => {} },
},
data(){
return {
form: {
parent: "",
name: "",
path: "",
component: "",
meta:{
title: "",
icon: "",
type: "menu"
}
},
menuProps: {
value: 'name',
label: 'name',
checkStrictly: true
},
rules: {},
views: []
}
},
mounted() {
this.views = this.getViews();
},
methods: {
//
setData(data, pid){
this.form = data
this.form.parent = pid
//
//Object.assign(this.form, data)
},
//
getViews(){
const filesUrl = []
let files = require.context('@/views', true, /\.vue$/)
files.keys().forEach(file => {
// index? .replace(/\/index$/, "")
filesUrl.push({
value: file.replace(/^\.\/(.*)\.\w+$/, '$1')
})
})
return filesUrl;
},
querySearch(queryString, cb){
var results = this.getViews();
results = results.filter(item => item.value.indexOf(queryString) !== -1)
cb(results)
}
}
}
</script>
<style scoped>
h2 {font-size: 17px;color: #3c4a54;padding:0 0 30px 0;}
</style>