构建框架

This commit is contained in:
龙运模
2024-06-24 18:52:21 +08:00
commit 33942cbe95
328 changed files with 50716 additions and 0 deletions
+48
View File
@@ -0,0 +1,48 @@
<template>
<div v-if="pageLoading">
<el-main>
<el-card shadow="never">
<el-skeleton :rows="1"></el-skeleton>
</el-card>
<el-card shadow="never" style="margin-top: 15px;">
<el-skeleton></el-skeleton>
</el-card>
</el-main>
</div>
<work v-if="dashboard=='1'" @on-mounted="onMounted"></work>
<widgets v-else @on-mounted="onMounted"></widgets>
</template>
<script>
import { defineAsyncComponent } from 'vue';
const work = defineAsyncComponent(() => import('./work'));
const widgets = defineAsyncComponent(() => import('./widgets'));
export default {
name: "dashboard",
components: {
work,
widgets
},
data(){
return {
pageLoading: true,
dashboard: '0'
}
},
created(){
this.dashboard = this.$TOOL.data.get("USER_INFO").dashboard || '0';
},
mounted(){
},
methods: {
onMounted(){
this.pageLoading = false
}
}
}
</script>
<style>
</style>
@@ -0,0 +1,27 @@
<template>
<el-card shadow="hover" header="关于项目" class="item-background">
<p>高性能 / 精致 / 优雅。基于Vue3 + Element-Plus 的中后台前端解决方案,如果喜欢就点个星星支持一下。</p>
<p>
<a href='https://gitee.com/lolicode/scui' target="_blank">
<img src='https://gitee.com/lolicode/scui/badge/star.svg?theme=dark' alt='star' style="vertical-align: middle">
</a>
</p>
</el-card>
</template>
<script>
export default {
title: "关于项目",
icon: "el-icon-setting",
description: "点个星星支持一下",
data() {
return {
}
}
}
</script>
<style scoped>
.item-background p {color: #999;margin-top:10px;line-height: 1.8;}
</style>
@@ -0,0 +1,98 @@
<template>
<el-card shadow="hover" header="实时收入" v-loading="loading">
<scEcharts ref="c1" height="300px" :option="option"></scEcharts>
</el-card>
</template>
<script>
import scEcharts from '@/components/scEcharts';
export default {
title: "实时收入",
icon: "el-icon-data-line",
description: "Echarts组件演示",
components: {
scEcharts
},
data() {
return {
loading: true,
option: {}
}
},
created() {
var _this = this;
setTimeout(function() {
_this.loading = false
}, 500);
var option = {
tooltip: {
trigger: 'axis'
},
xAxis: {
boundaryGap: false,
type: 'category',
data: (function (){
var now = new Date();
var res = [];
var len = 30;
while (len--) {
res.unshift(now.toLocaleTimeString().replace(/^\D*/,''));
now = new Date(now - 2000);
}
return res;
})()
},
yAxis: [{
type: 'value',
name: '价格',
"splitLine": {
"show": false
}
}],
series: [
{
name: '收入',
type: 'line',
symbol: 'none',
lineStyle: {
width: 1,
color: '#409EFF'
},
areaStyle: {
opacity: 0.1,
color: '#79bbff'
},
data: (function (){
var res = [];
var len = 30;
while (len--) {
res.push(Math.round(Math.random() * 0));
}
return res;
})()
},
],
};
this.option = option;
},
mounted(){
var _this = this;
setInterval(function (){
var o = _this.option;
o.series[0].data.shift()
o.series[0].data.push(Math.round(Math.random() * 100));
o.xAxis.data.shift();
o.xAxis.data.push((new Date()).toLocaleTimeString().replace(/^\D*/, ''));
//_this.$refs.c1.myChart.setOption(o)
},2100)
},
}
</script>
@@ -0,0 +1,12 @@
import {markRaw} from 'vue';
const resultComps = {}
let requireComponent = require.context(
'./', // 在当前目录下查找
false, // 不遍历子文件夹
/\.vue$/ // 正则匹配 以 .vue结尾的文件
)
requireComponent.keys().forEach(fileName => {
let comp = requireComponent(fileName)
resultComps[fileName.replace(/^\.\/(.*)\.\w+$/, '$1')] = comp.default
})
export default markRaw(resultComps)
@@ -0,0 +1,32 @@
<template>
<el-card shadow="hover" header="进度环">
<div class="progress">
<el-progress type="dashboard" :percentage="85.5" :width="160">
<template #default="{ percentage }">
<div class="percentage-value">{{ percentage }}%</div>
<div class="percentage-label">当前进度</div>
</template>
</el-progress>
</div>
</el-card>
</template>
<script>
export default {
title: "进度环",
icon: "el-icon-odometer",
description: "进度环原子组件演示",
data() {
return {
}
}
}
</script>
<style scoped>
.progress {text-align: center;}
.progress .percentage-value {font-size: 28px;}
.progress .percentage-label {font-size: 12px;margin-top: 10px;}
</style>
@@ -0,0 +1,40 @@
<template>
<el-card shadow="hover" header="时钟" class="item-background">
<div class="time">
<h2>{{ time }}</h2>
<p>{{ day }}</p>
</div>
</el-card>
</template>
<script>
export default {
title: "时钟",
icon: "el-icon-clock",
description: "演示部件效果",
data() {
return {
time: '',
day: ''
}
},
mounted() {
this.showTime()
setInterval(()=>{
this.showTime()
},1000)
},
methods: {
showTime(){
this.time = this.$TOOL.dateFormat(new Date(), 'hh:mm:ss')
this.day = this.$TOOL.dateFormat(new Date(), 'yyyy年MM月dd日')
}
}
}
</script>
<style scoped>
.item-background {background: linear-gradient(to right, #8E54E9, #4776E6);color: #fff;}
.time h2 {font-size: 40px;}
.time p {font-size: 14px;margin-top: 13px;opacity: 0.7;}
</style>
+41
View File
@@ -0,0 +1,41 @@
<template>
<el-card shadow="hover" header="版本信息">
<div style="height: 210px;text-align: center;">
<img src="img/ver.svg" style="height:140px"/>
<h2 style="margin-top: 15px;">SCUI {{$CONFIG.CORE_VER}}</h2>
<p style="margin-top: 5px;">最新版本 {{ver}}</p>
</div>
<div style="margin-top: 20px;">
<el-button type="primary" plain round @click="golog">更新日志</el-button>
<el-button type="primary" plain round @click="gogit">gitee</el-button>
</div>
</el-card>
</template>
<script>
export default {
title: "版本信息",
icon: "el-icon-monitor",
description: "当前项目版本信息",
data() {
return {
ver: 'loading...'
}
},
mounted() {
this.getVer()
},
methods: {
async getVer(){
const ver = await this.$API.demo.ver.get()
this.ver = ver.data
},
golog(){
window.open("https://gitee.com/lolicode/scui/releases")
},
gogit(){
window.open("https://gitee.com/lolicode/scui")
}
}
}
</script>
@@ -0,0 +1,69 @@
<template>
<el-card shadow="hover" header="欢迎">
<div class="welcome">
<div class="logo">
<img src="img/logo.png">
<h2>欢迎体验 SCUI</h2>
</div>
<div class="tips">
<div class="tips-item">
<div class="tips-item-icon"><el-icon><el-icon-menu/></el-icon></div>
<div class="tips-item-message">这里是项目控制台,你可以点击右上方的“自定义”按钮来添加移除或者移动部件。</div>
</div>
<div class="tips-item">
<div class="tips-item-icon"><el-icon><el-icon-promotion/></el-icon></div>
<div class="tips-item-message">在提高前端算力、减少带宽请求和代码执行力上多次优化,并且持续着。</div>
</div>
<div class="tips-item">
<div class="tips-item-icon"><el-icon><el-icon-milk-tea/></el-icon></div>
<div class="tips-item-message">项目目的:让前端工作更快乐</div>
</div>
</div>
<div class="actions">
<el-button type="primary" icon="el-icon-check" size="large" @click="godoc">文档</el-button>
</div>
</div>
</el-card>
<el-card style="max-width: 480px">
<template #header>
<div class="card-header">
<span>Card name</span>
</div>
</template>
<p v-for="o in 4" :key="o" class="text item">{{ 'List item ' + o }}</p>
<template #footer>Footer content</template>
</el-card>
</template>
<script>
export default {
title: "欢迎",
icon: "el-icon-present",
description: "项目特色以及文档链接",
data() {
return {
}
},
methods: {
godoc(){
window.open("https://lolicode.gitee.io/scui-doc/")
}
}
}
</script>
<style scoped>
.welcome {}
.welcome .logo {text-align: center;}
.welcome .logo img {vertical-align: bottom;width: 100px;height: 100px;margin-bottom: 20px;}
.welcome .logo h2 {font-size: 30px;font-weight: normal;display: flex;align-items: center;justify-content: center;}
.tips {margin-top: 20px;padding:0 40px;}
.tips-item {display: flex;align-items: center;justify-content: center;padding:7.5px 0;}
.tips-item-icon {width: 40px;height:40px;display: flex;align-items: center;justify-content: center;border-radius: 50%;font-size: 18px;margin-right: 20px;color: var(--el-color-primary);background: rgba(180,180,180,0.1);}
.tips-item-message {flex: 1;font-size: 14px;}
.actions {text-align: center;margin: 40px 0 20px 0;}
</style>
+256
View File
@@ -0,0 +1,256 @@
<template>
<div :class="['widgets-home', customizing?'customizing':'']" ref="main">
<div class="widgets-content">
<div class="widgets-top">
<div class="widgets-top-title">
控制台
</div>
<div class="widgets-top-actions">
<el-button v-if="customizing" type="primary" icon="el-icon-check" round @click="save">完成</el-button>
<el-button v-else type="primary" icon="el-icon-edit" round @click="custom">自定义</el-button>
</div>
</div>
<div class="widgets" ref="widgets">
<div class="widgets-wrapper">
<div v-if="nowCompsList.length<=0" class="no-widgets">
<el-empty image="img/no-widgets.svg" description="没有部件啦" :image-size="280"></el-empty>
</div>
<el-row :gutter="15">
<el-col v-for="(item, index) in grid.layout" v-bind:key="index" :md="item" :xs="24">
<draggable v-model="grid.copmsList[index]" animation="200" handle=".customize-overlay" group="people" item-key="com" dragClass="aaaaa" force-fallback fallbackOnBody class="draggable-box">
<template #item="{ element }">
<div class="widgets-item">
<component :is="allComps[element]"></component>
<div v-if="customizing" class="customize-overlay">
<el-button class="close" type="danger" plain icon="el-icon-close" size="small" @click="remove(element)"></el-button>
<label><el-icon><component :is="allComps[element].icon" /></el-icon>{{ allComps[element].title }}</label>
</div>
</div>
</template>
</draggable>
</el-col>
</el-row>
</div>
</div>
</div>
<div v-if="customizing" class="widgets-aside">
<el-container>
<el-header>
<div class="widgets-aside-title"><el-icon><el-icon-circle-plus-filled/></el-icon>添加部件</div>
<div class="widgets-aside-close" @click="close()"><el-icon><el-icon-close /></el-icon></div>
</el-header>
<el-header style="height:auto">
<div class="selectLayout">
<div class="selectLayout-item item01" :class="{active:grid.layout.join(',')=='12,6,6'}" @click="setLayout([12,6,6])">
<el-row :gutter="2">
<el-col :span="12"><span></span></el-col>
<el-col :span="6"><span></span></el-col>
<el-col :span="6"><span></span></el-col>
</el-row>
</div>
<div class="selectLayout-item item02" :class="{active:grid.layout.join(',')=='24,16,8'}" @click="setLayout([24,16,8])">
<el-row :gutter="2">
<el-col :span="24"><span></span></el-col>
<el-col :span="16"><span></span></el-col>
<el-col :span="8"><span></span></el-col>
</el-row>
</div>
<div class="selectLayout-item item03" :class="{active:grid.layout.join(',')=='24'}" @click="setLayout([24])">
<el-row :gutter="2">
<el-col :span="24"><span></span></el-col>
<el-col :span="24"><span></span></el-col>
<el-col :span="24"><span></span></el-col>
</el-row>
</div>
</div>
</el-header>
<el-main class="nopadding">
<div class="widgets-list">
<div v-if="myCompsList.length<=0" class="widgets-list-nodata">
<el-empty description="没有部件啦" :image-size="60"></el-empty>
</div>
<div v-for="item in myCompsList" :key="item.title" class="widgets-list-item">
<div class="item-logo"><el-icon><component :is="item.icon" /></el-icon></div>
<div class="item-info">
<h2>{{ item.title }}</h2>
<p>{{ item.description }}</p>
</div>
<div class="item-actions">
<el-button type="primary" icon="el-icon-plus" size="small" @click="push(item)"></el-button>
</div>
</div>
</div>
</el-main>
<el-footer style="height:51px;">
<el-button size="small" @click="backDefaul()">恢复默认</el-button>
</el-footer>
</el-container>
</div>
</div>
</template>
<script>
import draggable from 'vuedraggable'
import allComps from './components'
export default {
components: {
draggable
},
data() {
return {
customizing: false,
allComps: allComps,
selectLayout: [],
defaultGrid: this.$CONFIG.DEFAULT_GRID,
grid: []
}
},
created(){
this.grid = this.$TOOL.data.get("grid") || JSON.parse(JSON.stringify(this.defaultGrid))
},
mounted() {
this.$emit('on-mounted')
},
computed: {
allCompsList(){
var allCompsList = []
for(var key in this.allComps){
allCompsList.push({
key: key,
title: allComps[key].title,
icon: allComps[key].icon,
description: allComps[key].description
})
}
var myCopmsList = this.grid.copmsList.reduce(function(a, b){return a.concat(b)})
for(let comp of allCompsList){
const _item = myCopmsList.find((item)=>{return item === comp.key})
if(_item){
comp.disabled = true
}
}
return allCompsList
},
myCompsList(){
var myGrid = this.$TOOL.data.get("DASHBOARDGRID")
return this.allCompsList.filter(item => !item.disabled && myGrid.includes(item.key))
},
nowCompsList(){
return this.grid.copmsList.reduce(function(a, b){return a.concat(b)})
}
},
methods: {
//开启自定义
custom(){
this.customizing = true
const oldWidth = this.$refs.widgets.offsetWidth
this.$nextTick(() => {
const scale = this.$refs.widgets.offsetWidth / oldWidth
this.$refs.widgets.style.setProperty('transform', `scale(${scale})`)
})
},
//设置布局
setLayout(layout){
this.grid.layout = layout
if(layout.join(',')=='24'){
this.grid.copmsList[0] = [...this.grid.copmsList[0],...this.grid.copmsList[1],...this.grid.copmsList[2]]
this.grid.copmsList[1] = []
this.grid.copmsList[2] = []
}
},
//追加
push(item){
let target = this.grid.copmsList[0]
target.push(item.key)
},
//隐藏组件
remove(item){
var newCopmsList = this.grid.copmsList
newCopmsList.forEach((obj, index) => {
var newObj = obj.filter(o=>o!=item)
newCopmsList[index] = newObj;
})
},
//保存
save(){
this.customizing = false
this.$refs.widgets.style.removeProperty('transform')
this.$TOOL.data.set("grid", this.grid)
},
//恢复默认
backDefaul(){
this.customizing = false
this.$refs.widgets.style.removeProperty('transform')
this.grid = JSON.parse(JSON.stringify(this.defaultGrid))
this.$TOOL.data.remove("grid")
},
//关闭
close(){
this.customizing = false
this.$refs.widgets.style.removeProperty('transform')
}
}
}
</script>
<style scoped lang="scss">
.widgets-home {display: flex;flex-direction: row;flex: 1;height: 100%;}
.widgets-content {flex: 1;overflow: auto;overflow-x:hidden;padding:15px;}
.widgets-aside {width: 360px;background: #fff;box-shadow: 0 0 10px rgba(0,0,0,.1);position: relative;overflow: auto;}
.widgets-aside-title {font-size: 14px;display: flex;align-items: center;justify-content: center;}
.widgets-aside-title i {margin-right: 10px;font-size: 18px;}
.widgets-aside-close {font-size: 18px;width:30px;height: 30px;display: flex;align-items: center;justify-content: center;border-radius: 3px;cursor: pointer;}
.widgets-aside-close:hover {background: rgba(180,180,180,0.1);}
.widgets-top {margin-bottom: 15px;display: flex;justify-content: space-between;align-items: center;}
.widgets-top-title {font-size: 18px;font-weight: bold;}
.widgets {transform-origin: top left;transition: transform .15s;}
.draggable-box {height: 100%;}
.customizing .widgets-wrapper {margin-right:-360px}
.customizing .widgets-wrapper .el-col {padding-bottom:15px;}
.customizing .widgets-wrapper .draggable-box {border: 1px dashed var(--el-color-primary);padding:15px;}
.customizing .widgets-wrapper .no-widgets {display: none;}
.customizing .widgets-item {position: relative;margin-bottom: 15px;}
.customize-overlay {position: absolute;top:0;right:0;bottom:0;left:0;z-index: 1;display: flex;flex-direction: column;align-items: center;justify-content: center;background: rgba(255,255,255,0.9);cursor: move;}
.customize-overlay label {background: var(--el-color-primary);color: #fff;height:40px;padding:0 30px;border-radius: 40px;font-size: 18px;display: flex;align-items: center;justify-content: center;cursor: move;}
.customize-overlay label i {margin-right: 15px;font-size: 24px;}
.customize-overlay .close {position: absolute;top:15px;right:15px;}
.customize-overlay .close:focus, .customize-overlay .close:hover {background: var(--el-button-hover-color);}
.widgets-list {}
.widgets-list-item {display: flex;flex-direction: row;padding:15px;align-items: center;}
.widgets-list-item .item-logo {width: 40px;height: 40px;border-radius: 50%;background: rgba(180,180,180,0.1);display: flex;align-items: center;justify-content: center;font-size: 18px;margin-right: 15px;color: #6a8bad;}
.widgets-list-item .item-info {flex: 1;}
.widgets-list-item .item-info h2 {font-size: 16px;font-weight: normal;cursor: default;}
.widgets-list-item .item-info p {font-size: 12px;color: #999;cursor: default;}
.widgets-list-item:hover {background: rgba(180,180,180,0.1);}
.widgets-wrapper .sortable-ghost {opacity: 0.5;}
.selectLayout {width: 100%;display: flex;}
.selectLayout-item {width:60px;height:60px;border: 2px solid var(--el-border-color-light);padding:5px;cursor: pointer;margin-right: 15px;}
.selectLayout-item span {display: block;background: var(--el-border-color-light);height:46px;}
.selectLayout-item.item02 span {height:30px;}
.selectLayout-item.item02 .el-col:nth-child(1) span {height:14px;margin-bottom: 2px;}
.selectLayout-item.item03 span {height:14px;margin-bottom: 2px;}
.selectLayout-item:hover {border-color: var(--el-color-primary);}
.selectLayout-item.active {border-color: var(--el-color-primary);}
.selectLayout-item.active span {background: var(--el-color-primary);}
.dark {
.widgets-aside {background: #2b2b2b;}
.customize-overlay {background: rgba(43,43,43,0.9);}
}
@media (max-width: 992px){
.customizing .widgets {transform: scale(1) !important;}
.customizing .widgets-aside {width: 100%;position: absolute;top:50%;right:0;bottom:0;}
.customizing .widgets-wrapper {margin-right:0;}
}
</style>
+131
View File
@@ -0,0 +1,131 @@
<template>
<div>
<ul class="myMods">
<li v-for="mod in myMods" :key="mod.path" :style="{background:mod.meta.color||'#909399'}">
<a v-if="mod.meta.type=='link'" :href="mod.path" target="_blank">
<el-icon><component :is="mod.meta.icon||el-icon-menu" /></el-icon>
<p>{{ mod.meta.title }}</p>
</a>
<router-link v-else :to="{ path: mod.path }">
<el-icon><component :is="mod.meta.icon||el-icon-menu" /></el-icon>
<p>{{ mod.meta.title }}</p>
</router-link>
</li>
<li class="modItem-add" @click="addMods">
<a href="javascript:void(0)">
<el-icon><el-icon-plus /></el-icon>
</a>
</li>
</ul>
<el-drawer title="添加应用" v-model="modsDrawer" :size="570" destroy-on-close>
<div class="setMods">
<h4>我的常用 ( {{myMods.length}} )</h4>
<draggable tag="ul" v-model="myMods" animation="200" item-key="path" group="people">
<template #item="{ element }">
<li :style="{background:element.meta.color||'#909399'}">
<el-icon><component :is="element.meta.icon||el-icon-menu" /></el-icon>
<p>{{element.meta.title}}</p>
</li>
</template>
</draggable>
</div>
<div class="setMods">
<h4>全部应用 ( {{filterMods.length}} )</h4>
<draggable tag="ul" v-model="filterMods" animation="200" item-key="path" :sort="false" group="people">
<template #item="{ element }">
<li :style="{background:element.meta.color||'#909399'}">
<el-icon><component :is="element.meta.icon||el-icon-menu" /></el-icon>
<p>{{element.meta.title}}</p>
</li>
</template>
</draggable>
</div>
<template #footer>
<el-button @click="modsDrawer=false">取消</el-button>
<el-button type="primary" @click="saveMods">保存</el-button>
</template>
</el-drawer>
</div>
</template>
<script>
import draggable from 'vuedraggable'
export default {
components: {
draggable
},
data() {
return {
mods: [],
myMods: [],
myModsName: [],
filterMods: [],
modsDrawer: false
}
},
mounted(){
this.getMods()
},
methods: {
addMods(){
this.modsDrawer = true
},
getMods(){
//这里可用改为读取远程数据
this.myModsName = this.$TOOL.data.get("my-mods") || []
var menuTree = this.$TOOL.data.get("MENU")
this.filterMenu(menuTree)
this.myMods = this.mods.filter(item => {
return this.myModsName.includes(item.name)
})
this.filterMods = this.mods.filter(item => {
return !this.myModsName.includes(item.name)
})
},
filterMenu(map){
map.forEach(item => {
if(item.meta.hidden || item.meta.type=="button"){
return false
}
if(item.meta.type=='iframe'){
item.path = `/i/${item.name}`
}
if(item.children&&item.children.length > 0){
this.filterMenu(item.children)
}else{
this.mods.push(item)
}
})
},
saveMods(){
const myModsName = this.myMods.map(v => v.name)
this.$TOOL.data.set("my-mods", myModsName)
this.$message.success("设置常用成功")
this.modsDrawer = false
}
}
}
</script>
<style scoped>
.myMods {list-style:none;margin:-10px;}
.myMods li {display: inline-block;width: 100px;height:100px;vertical-align: top;transition:all 0.3s ease;margin:10px;border-radius:5px;}
.myMods li:hover {opacity: 0.8;}
.myMods li a {width: 100%;height: 100%;padding:10px;display: flex;flex-direction: column;align-items: center;justify-content: center;text-align: center;color: #fff;}
.myMods li i {font-size: 26px;color: #fff;}
.myMods li p {font-size: 12px;color: #fff;margin-top: 10px;width: 100%;white-space:nowrap;text-overflow:ellipsis;overflow: hidden;}
.modItem-add {border: 1px dashed #ddd;cursor: pointer;}
.modItem-add i {font-size: 30px;color: #999!important;}
.modItem-add:hover,.modItem-add:hover i {border-color: #409EFF;color: #409EFF!important;}
.setMods {padding:0 20px;}
.setMods h4 {font-size: 14px;font-weight: normal;}
.setMods ul {margin:20px -5px;min-height: 90px;}
.setMods li {display: inline-block;width: 80px;height:80px;text-align: center;margin:5px;color: #fff;vertical-align: top;padding:4px;padding-top:15px;cursor: move;border-radius: 3px;}
.setMods li i {font-size: 20px;}
.setMods li p {font-size: 12px;margin-top: 10px;}
.setMods li.sortable-ghost {opacity: 0.3;}
</style>
+37
View File
@@ -0,0 +1,37 @@
<template>
<el-main>
<el-alert title="根据角色配置,可让不同角色访问不同的控制台视图,参数值在登录成功后返回 dashboard:{type}" type="success" style="margin-bottom:20px;"></el-alert>
<el-row :gutter="15">
<el-col :lg="24">
<el-card shadow="never" header="我的常用">
<myapp></myapp>
</el-card>
</el-col>
</el-row>
</el-main>
</template>
<script>
import myapp from './components/myapp';
export default {
components: {
myapp
},
data() {
return {
}
},
mounted(){
this.$emit('on-mounted')
},
methods: {
}
}
</script>
<style scoped>
</style>