【珍惜时间】iReport
项目很点意思,感觉很高超的样子
先放下项目的github地址:https://github.com/tctangyanan/iReport
感谢各位伟大的程序员无私的分享自己的技术
老规矩,我们会运行项目
页面效果为
先逐行分析代码
先看main.js,引入了我们需要的一些全局插件
//main.js
import Vue from 'vue'
import 'normalize.css/normalize.css'// A modern alternative to CSS resets
import App from './App'
import router from './router'
// import routes from './router/routes'
import store from './vuex/store'
import { sync } from 'vuex-router-sync'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import './styles/index.scss' // global css
// 进度条
import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css'// progress bar style
import UtilsPlugin from './assets/utils'
import HttpPlugin from './http/index'
import VueParticles from 'vue-particles' // 粒子酷炫效果
import formatStyle from './filters/formatStyle.js'
// import { Tag } from '../src/model/index'
import './mock' // mock
import VCharts from 'v-charts'
Vue.config.productionTip = false
Vue.use(ElementUI)
// plugins
Vue.use(UtilsPlugin)
Vue.use(HttpPlugin)
Vue.use(VCharts)
Vue.use(VueParticles)
Vue.filter('formatStyle', formatStyle)
// const dispatch = store.dispatch
router.beforeEach((to, from, next) => {
NProgress.start() // start progress bar
if (to.path === '/' || to.path === '/login' || to.path === '/404' || to.path === '/401') {
setTimeout(next, 0)
} else {
setTimeout(next, 20)
}
})
router.afterEach((to) => {
NProgress.done() // finish progress bar
})
sync(store, router)
/* eslint-disable no-new */
// global
window.$globalHub = new Vue({
store,
router,
render: h => h(App)
}).$mount('#app')
//App.vue
<template>
<div id="app" style="height:100%;">
<!--default slot-->
<router-view :key="$route.path"></router-view>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
components: {},
data () {
return {}
},
mounted () {
// 禁用整个页面的右击事件
// document.oncontextmenu = () => {
// return false
// }
},
computed: {
...mapState({})
},
methods: {}
}
</script>
<style lang="less" rel="stylesheet/less">
#nprogress .spinner {
display: none;
}
@import "styles/icon.css";
</style>
接下来看路由页面
//router.js
/* eslint-disable no-undef */
import NotFind from '../pages/errors/404.vue'
import VChars from '../pages/test/VChars.vue'
// 生产/测试环境,使用路由懒加载
const _import = process.env.NODE_ENV === 'development'
? file => require(`@/pages/${file}.vue`).default
: file => () => System.import(`@/pages/${file}.vue`).then(m => m.default)
export default [
{ path: '/', component: _import('login/Index') },
{ path: '/login', component: _import('login/Index') },
{ path: '/VChars', component: VChars },
{
path: '/main',
component: resolve => require(['../layout/Layout'], resolve),
children: [
{
path: '/dashboard',
name: '首页',
component: _import('dashboard/Index')
}, {
path: '/table/list',
name: '表格',
component: _import('table/Index')
}, {
path: '/icons',
name: '图标',
component: _import('icons/Index')
}, {
path: '/role/list',
name: '角色管理',
component: _import('roles/Index')
}, {
path: '/user/list',
name: '用户管理',
component: _import('users/Index')
}, {
path: '/menus/list',
name: '菜单设置',
component: _import('menus/Index')
}, {
path: '/smsCode/list',
name: '短信码',
component: _import('smsCode/Index')
}, {
path: '/errorCode/list',
name: '错误码',
component: _import('errorCode/Index')
}, {
path: '/404',
name: '404',
component: NotFind
}, {
path: '/401',
name: '401',
component: _import('errors/401')
}
]
},
{ path: '/editor', component: _import('editor/Index') }
]
//index.js
/* eslint-disable no-undef */
import Vue from 'vue'
import Router from 'vue-router'
import config from '../../config/index'
import routes from './routes'
Vue.use(Router)
export default new Router({
mode: 'history', // 后端支持可开
base: config.build.assetsPublicPath,
routes
})
接下来我们根据页面效果看代码
//src\pages\login\Index.vue
<template>
<!--region 粒子效果的登录背景-->
<div class="login-page">
<vue-particles color="#fff" :particleOpacity="0.7" :particlesNumber="60" shapeType="circle" :particleSize="4"
linesColor="#fff" :linesWidth="1" :lineLinked="true" :lineOpacity="0.4" :linesDistance="150"
:moveSpeed="2" :hoverEffect="true" hoverMode="grab" :clickEffect="true" clickMode="push"
class="bg-lizi">
</vue-particles>
<!--region 登录表单-->
<el-form :model="loginForm" class="login-form" label-position="left" size="large">
<el-form-item>
<el-input type="text" v-model="loginForm.phone" auto-complete="off" placeholder="注册时的手机号">
<span v-html="''" class="axon-icon" slot="prefix"></span>
</el-input>
</el-form-item>
<el-form-item>
<el-input :type="passwordType" v-model="loginForm.password" size="large" auto-complete="off" placeholder="登录密码">
<span v-html="''" class="axon-icon" slot="prefix"></span>
<span v-html="passwordType === 'password' ? '' : ''" class="axon-icon" slot="suffix"
@click="showPwd()"></span>
</el-input>
</el-form-item>
<el-button type="primary" @click="submitLogin" @enter="submitLogin()" :loading="loading">登 录</el-button>
</el-form>
<!--endregion-->
</div>
<!--endregion-->
</template>
<script>
import BLL from './Index.js'
import menusLisit from '../../../static/json/limit.json'
export default {
components: {},
data () {
return {
loginForm: {
phone: '18951871658',
password: '123456'
},
loading: false,
menus: menusLisit,
passwordType: 'password' // 密码控件的类型
}
},
created () {
this.BLL = new BLL(this)
},
mounted () {
},
methods: {
// 登录
submitLogin () {
this.BLL.login()
},
showPwd () {
this.passwordType === 'password' ? this.passwordType = 'text' : this.passwordType = 'password'
}
}
}
</script>
<style lang="scss">
@import "./Index.scss";
</style>
//src\pages\login\Index.js
import Base from '../../base/index'
export default class extends Base {
/**
* 用户登录
*/
login () {
if (!this.vm.loginForm.phone) {
this.vm.$message.warning('请填写登录账号!')
return false
}
if (!this.vm.$utils.Validate.chkFormat(this.vm.loginForm.phone, 'phone')) {
this.vm.$message.warning('登录手机号的格式不正确!')
return false
}
if (!this.vm.loginForm.password) {
this.vm.$message.warning('请填写登录密码!')
return false
}
if (this.vm.loginForm.phone !== '18951871658' || this.vm.loginForm.password !== '123456') {
this.vm.$message.warning('账号或密码不匹配!')
return false
}
this.vm.loading = true
// 取用户的平台权限信息,并且持久化
this.vm.$store.dispatch('init_sidebar_data', this.vm.menus)
// 清空tab标签
this.vm.$store.dispatch('del_all_tags')
// 登录成功
this.vm.$notify.success({
title: '登录成功',
message: '欢迎小主回来!'
})
this.vm.$router.push({path: '/dashboard'})
}
}
//src\pages\dashboard\Index.vue
<template>
<div class="dashboard-page">
<!--region 栏目-->
<el-row class="panel-group" :gutter="40">
<template v-for="(panel, index) in panelList">
<el-col :key="index" :xs="12" :sm="12" :lg="6" class="card-panel-col">
<div class='card-panel' @click="handleSetLineChartData(panel.id)">
<div class="card-panel-icon-wrapper icon-people">
<div :class="`card-panel-icon panel-${panel.color}`">
<span class="axon-icon" v-html="panel.icon"></span>
</div>
</div>
<div class="card-panel-description">
<div class="card-panel-text">{{ panel.label }}</div>
<count-to class="card-panel-num" :startVal="0" :endVal="panel.value" :decimals=2 :duration="2600"></count-to>
</div>
</div>
</el-col>
</template>
</el-row>
<!--endregion-->
<!--region 创建新场景-->
<div class="add-new-scene">
<el-button type="primary" icon="el-icon-plus" plain round @click.native="creatNewScene">添加新场景</el-button>
</div>
<!--endregion-->
</div>
</template>
<script>
const CountTo = () => import('vue-count-to')
const lineChartData = {
newVisitis: {
expectedData: [100, 120, 161, 134, 105, 160, 165],
actualData: [120, 82, 91, 154, 162, 140, 145]
},
messages: {
expectedData: [200, 192, 120, 144, 160, 130, 140],
actualData: [180, 160, 151, 106, 145, 150, 130]
},
purchases: {
expectedData: [80, 100, 121, 104, 105, 90, 100],
actualData: [120, 90, 100, 138, 142, 130, 130]
},
shoppings: {
expectedData: [130, 140, 141, 142, 145, 150, 160],
actualData: [120, 82, 91, 154, 162, 140, 130]
}
}
export default {
components: { CountTo },
data () {
return {
panelList: [
{
id: 'newVisitis',
label: 'Demo1',
value: 102400,
icon: '',
color: 1// '#40c9c6'
},
{
id: 'messages',
label: 'Demo2',
value: 81212,
icon: '',
color: 2 // '#36a3f7'
},
{
id: 'purchases',
label: 'Demo3',
value: 9280.3,
icon: '',
color: 3 // '#f4516c'
},
{
id: 'shoppings',
label: 'Demo4',
value: 13600.6,
icon: '',
color: 4 // '#34bfa3'
}
],
lineChartData: lineChartData.newVisitis
}
},
mounted () {
},
methods: {
handleSetLineChartData (panel) {
this.lineChartData = lineChartData[panel]
},
/**
* 创建新场景
*/
creatNewScene () {
this.$router.push({ path: '/editor' })
}
}
}
</script>
<style lang="scss" scoped>
@import "../../styles/font.scss";
.dashboard-page {
background-color: #f0f2f5;
padding: 32px;
overflow-y: auto;
.panel-group {
padding: 40px 0;
.card-panel-col {
margin-top: 10px;
.card-panel {
height: 108px;
cursor: pointer;
font-size: $font-size-s;
position: relative;
overflow: hidden;
color: #666;
background: #fff;
-webkit-box-shadow: 4px 4px 40px rgba(0, 0, 0, 0.05);
box-shadow: 4px 4px 40px rgba(0, 0, 0, 0.05);
border-color: rgba(0, 0, 0, 0.05);
display: flex;
padding: 0 20px;
&:hover {
.card-panel-icon-wrapper {
.card-panel-icon {
border-radius: 4px;
transition: all 0.38s ease-out;
&.panel-1 {
background-color: #40c9c6;
.axon-icon {
color: #ffffff;
}
}
&.panel-2 {
background-color: #36a3f7;
.axon-icon {
color: #ffffff;
}
}
&.panel-3 {
background-color: #f4516c;
.axon-icon {
color: #ffffff;
}
}
&.panel-4 {
background-color: #34bfa3;
.axon-icon {
color: #ffffff;
}
}
}
}
}
.card-panel-icon-wrapper {
flex: 0 0 50%;
.card-panel-icon {
width: 60px;
height: 60px;
margin-top: 29px;
text-align: center;
line-height: 60px;
&.panel-1 {
.axon-icon {
color: #40c9c6;
}
}
&.panel-2 {
.axon-icon {
color: #36a3f7;
}
}
&.panel-3 {
.axon-icon {
color: #f4516c;
}
}
&.panel-4 {
.axon-icon {
color: #34bfa3;
}
}
.axon-icon {
font-size: $font-size-large;
}
}
}
.card-panel-description {
flex: 0 0 50%;
text-align: right;
margin: 26px 0;
.card-panel-text {
font-size: $font-size-xxl;
font-weight: 600;
line-height: 32px;
color: rgba(0, 0, 0, 0.45);
}
.card-panel-num {
font-weight: 600;
font-size: $font-size-xxxl;
}
}
}
}
}
.add-new-scene {
text-align: center;
}
}
</style>
这个里面写的css的方式处理的非常优雅,包括在css里面的定义,很值得学习
接下来的页面是,点击添加新场景
我们先看Index.vue中引入的组件
//src\pages\editor\Index.vue
<template>
<div class="editor-index">
<!--顶部各种组件列表-->
<the-tools></the-tools>
<!--region 中间区域分为三个区间-->
<div class="index-center" ref="centerBox">
<!--左边在线模板列表-->
<the-template-list></the-template-list>
<!--内容面板 编辑-->
<div class="editor-box">
<the-editor-container ref="editorContainer"></the-editor-container>
</div>
<!--右侧页面列表-->
<the-page-list></the-page-list>
<!--组件属性设置面板-->
<the-comp-props-config></the-comp-props-config>
<!--组件右击图层设置面板-->
<the-comp-layer-manager></the-comp-layer-manager>
</div>
<!--endregion-->
</div>
</template>
<script>
// 右侧页面列表
import ThePageList from './components/ThePageList'
// 左边在线模板列表
import TheTemplateList from './components/TheTemplateList'
// 顶部各种组件列表
import TheTools from './components/TheTools'
// 面板
import TheEditorContainer from './components/TheEditorContainer'
// 组件属性设置面板
import TheCompPropsConfig from './components/TheCompPropsConfig'
// 组件右击图层设置面板
import TheCompLayerManager from './components/TheCompLayerManager'
export default {
// 引入组件
components: {
ThePageList,
TheTemplateList,
TheTools,
TheEditorContainer,
TheCompPropsConfig,
TheCompLayerManager
},
data () {
return {
toolJson: {},
panelWidthAndHeight: {
width: 200,
height: 400
}
}
},
created () {
// 初始化
this.$store.dispatch('initPageEditor')
},
mounted () {
},
computed: {},
methods: {}
}
</script>
<style lang="scss">
@import "../../styles/variables";
@import "../../styles/mixin";
.editor-index {
background-color: #fff;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
.index-center {
box-sizing: border-box;
flex: 0 0 calc(#{"100% - "+ $tool-header-height +""});
display: flex;
flex-direction: row;
position: relative;
@include scrollBar;
.editor-box {
box-sizing: border-box;
width: 100%;
overflow-y: auto;
padding: 40px 0;
background-color: #eee;
}
}
}
</style>
接下来我们一个一个分析
//src\components\EChars\reportComponent.js
export default {
// 园区客流检测 数据
reportComponentList: [
{
componentId: 87001,
componentName: '柱状图',
sourceRemark: '项王故里为节日期间游客量最多的,三台山森林公园和洪泽湖湿地公园分列二三名。',
tempHtml: '<i-LBarFour :chartsId="dataObj.chartsId" :title="dataObj.title" :xAxis="dataObj.xAxis" :yAxis="dataObj.yAxis" :unit="dataObj.unit"></i-LBarFour >',
dataObj: {
chartsId: `circularCharsID`,
title: '接待省外访客量TOP10景区排名',
xAxis: ['项王故里', '三台山森林公园', '洪泽湖湿地公园', '湖滨公园', '洋河酒厂', '雪枫公园', '龙王庙行宫', '中国杨树博物馆'],
yAxis: [38056, 33824, 22672, 18616, 13112, 7016, 6864, 5792],
unit: '人次'
}
},
{
componentId: 87002,
componentName: '折线图',
sourceRemark: '中秋期间,9月23日接待访客量最多,9月24日接待访客量最少',
tempHtml: '<i-Line :chartsId="dataObj.chartsId" :title="dataObj.title" :xAxis="dataObj.xAxis" :yAxis="dataObj.yAxis" :unit="dataObj.unit"></i-Line >',
dataObj: {
chartsId: `lineCharsID`,
title: '分日客流量变化',
xAxis: ['9月22日', '9月23日', '9月23日'],
yAxis: [59.28, 63.86, 33.86],
unit: '万人'
}
},
{
componentId: 87003,
componentName: '饼状图',
sourceRemark: '节假日期间,年龄在22岁及以下、23-35岁和36-45岁的访客占访客总量的60%',
tempHtml: '<i-HollowPie :chartsId="dataObj.chartsId" :title="dataObj.title" :xAxis="dataObj.xAxis" :yAxis="dataObj.yAxis" :unit="dataObj.unit"></i-HollowPie>',
dataObj: {
chartsId: `hollowPieCharsID`,
title: '访客年龄段分布',
xAxis: ['22岁及以下', '23-35岁', '36-45岁', '46-55岁', '56岁及以上'],
yAxis: [25145, 108532, 83761, 91837, 53111],
unit: '%'
}
}
]
}
//src\components\iDialogImg\Index.vue
<!--region 封装的图片列表 卡片-->
<template>
<div class="custom-dialog-wrapper" v-if="dialogVisible">
<div class="custom-dialog-container" :style="{width: '60%',marginTop: '15vh'}">
<el-container>
<el-aside :style="{height: '75vh',width:'200px'}">
<div class="custom-aside__wrapper">
<span class="custom-aside__title">图片库</span>
<el-upload
class="upload-demo"
drag
action="https://jsonplaceholder.typicode.com/posts/"
multiple>
<i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
<div class="el-upload__tip" slot="tip">只能上传jpg/png文件,且不超过500kb</div>
</el-upload>
</div>
</el-aside>
<el-container>
<el-header>
<div class="custom-dialog__header">
<span class="custom-dialog__title">{{title}}</span>
<a class="custom-dialog__headerbtn" @click="closeDialog">
<i class="el-dialog__close el-icon el-icon-close"></i>
</a>
</div>
</el-header>
<el-main>
<ul v-if="operationFlag==='BackgroundImage'">
背景图片
<template v-for="item in backgroundImageList">
<li :key="item.id" @click="selectImage(item)" style="height: 30vh;">
<div class="item-bg-images" :style="{backgroundImage: 'url(' + item.url + ')'}">
<div class="item-active"
v-if="imageSelectItem && imageSelectItem.id === item.id">
<span class="axon-icon" v-html="''"></span>
</div>
</div>
</li>
</template>
</ul>
<ul v-if="operationFlag==='Image' || operationFlag==='UpdateImages'">
<template v-for="item in iconList">
<li :key="item.id" @click="selectImage(item)" style="height: 24vh;border: 1px solid #c8c9ca">
<div class="item-bg-images" :style="{backgroundImage: 'url(' + item.url + ')'}">
<div class="item-active"
v-if="imageSelectItem && imageSelectItem.id === item.id">
<span class="axon-icon" v-html="''"></span>
</div>
</div>
</li>
</template>
</ul>
</el-main>
<el-footer>
<el-button @click="cancelSelectImage" round> 取消 </el-button>
<el-button type="success" @click="confirmSelectImage" round> 确定 </el-button>
</el-footer>
</el-container>
</el-container>
</div>
</div>
</template>
<!--endregion-->
<script>
export default {
name: 'iDialogImg',
data () {
return {
dialogVisible: '',
title: '',
operationFlag: '',
backgroundImageList: [
{ id: 1, url: require('../../assets/images/bgi/1521186133451071.jpg') },
{ id: 2, url: require('../../assets/images/bgi/1522660019106280.png') },
{ id: 3, url: require('../../assets/images/bgi/1521186133451071.jpg') },
{ id: 4, url: require('../../assets/images/bgi/1522660019106280.png') },
{ id: 5, url: require('../../assets/images/bgi/1521186133451071.jpg') },
{ id: 6, url: require('../../assets/images/bgi/1522660019106280.png') },
{ id: 7, url: require('../../assets/images/bgi/1521186133451071.jpg') },
{ id: 8, url: require('../../assets/images/bgi/1522660019106280.png') },
{ id: 9, url: require('../../assets/images/bgi/1521186133451071.jpg') },
{ id: 10, url: require('../../assets/images/bgi/1522660019106280.png') },
{ id: 11, url: require('../../assets/images/bgi/1521186133451071.jpg') },
{ id: 12, url: require('../../assets/images/bgi/1522660019106280.png') },
{ id: 13, url: require('../../assets/images/bgi/1521186133451071.jpg') },
{ id: 14, url: require('../../assets/images/bgi/1522660019106280.png') }
],
iconList: [
{ id: 1, url: require('../../assets/icon/bridge1.png') },
{ id: 2, url: require('../../assets/icon/person.png') },
{ id: 3, url: require('../../assets/icon/ren1.png') },
{ id: 4, url: require('../../assets/icon/bridge1.png') },
{ id: 5, url: require('../../assets/icon/person.png') },
{ id: 6, url: require('../../assets/icon/ren1.png') },
{ id: 7, url: require('../../assets/icon/bridge1.png') },
{ id: 8, url: require('../../assets/icon/person.png') },
{ id: 9, url: require('../../assets/icon/bridge1.png') },
{ id: 10, url: require('../../assets/icon/person.png') },
{ id: 11, url: require('../../assets/icon/person.png') },
{ id: 12, url: require('../../assets/icon/ren1.png') },
{ id: 13, url: require('../../assets/icon/bridge1.png') },
{ id: 14, url: require('../../assets/icon/person.png') }
],
imageSelectItem:
{
id: null,
url:
''
}
}
},
created () { },
mounted () {
},
computed: {
// 取当前页面
curPage () {
return this.$store.getters.curPage
},
// 取当前组件
currComp () {
return this.$store.getters.curComp
}
},
methods: {
// 关闭弹出框
closeDialog () {
this.dialogVisible = false
},
// 新增图片/背景图增弹出框
showAddDialog (flag) {
this.operationFlag = flag
switch (flag) {
case 'Image':
this.title = '新增图标'
break
case 'BackgroundImage':
this.title = '设置背景图'
break
default:
break
}
this.dialogVisible = true
},
// 编辑图片/背景图增弹出框
showEditDialog () {
this.title = '修改图标'
this.operationFlag = 'UpdateImages'
this.dialogVisible = true
},
// 取消图片选中
cancelSelectImage () {
this.imageSelectItem.id = null
this.imageSelectItem.url = ''
},
// 确认操作
confirmSelectImage () {
this.closeDialog()
switch (this.operationFlag) {
case 'Image':
this.handleAddImageComponent(this.imageSelectItem)
break
case 'BackgroundImage':
this.updateCurPageBackgroundImage(this.imageSelectItem)
break
case 'UpdateImages':
this.updateProps()
break
default:
break
}
},
// 选中事件
selectImage (item) {
console.log(item)
this.imageSelectItem.id = item.id
this.imageSelectItem.url = item.url
},
// 添加图片
handleAddImageComponent (item) {
this.$store.dispatch('addNewCompByParams', { name: 'Image', url: item.url })
},
// 编辑图片组件
updateProps () {
this.$store.dispatch('editComp', {
type: 'props',
value: { 'url': this.imageSelectItem.url }
})
},
// 设置当前页面背景
updateCurPageBackgroundImage (item) {
this.curPage.css.bgi = item.url
this.syncCss('css', { 'bgi': this.curPage.css['bgi'] }, this.curPage.id)
},
// 同步到持久化
syncCss (type, val, pageId) {
this.$store.dispatch('editCurPage', {
type: type,
value: val,
pageId: pageId
})
}
}
}
</script>
<style lang="scss">
.custom-dialog-wrapper {
z-index: 2025;
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
overflow: auto;
margin: 0;
.custom-dialog-container {
position: relative;
margin: 0 auto 50px;
background: #fff;
border-radius: 2px;
box-shadow: 0 1px 3px rgba(0, 0, 0, .3);
box-sizing: border-box;
width: 50%;
}
}
.el-aside {
color: #333;
text-align: center;
.custom-aside__wrapper {
width: 100%;
position: relative;
background-color: #1f2d3d;
.custom-aside__title {
position: absolute;
top: 0;
left: 0;
width: 200px;
height: 60px;
line-height: 60px;
font-size: 18px;
color: #fff;
background: #212121;
font-weight: bolder;
}
.upload-demo {
position: absolute;
top: 80px;
left: 0;
.el-upload-dragger {
width: 180px;
}
}
}
}
.el-header {
color: #333;
box-shadow: 0 6px 6px 0 rgba(0, 0, 0, .16);
.custom-dialog__header {
padding: 20px 20px 10px 20px;
.custom-dialog__title {
line-height: 24px;
font-size: 18px;
color: #303133;
}
.custom-dialog__headerbtn {
position: absolute;
top: 20px;
right: 20px;
padding: 0;
background: transparent;
border: none;
outline: none;
cursor: pointer;
font-size: 16px;
}
}
}
.el-main {
background-color: #E9EEF3;
color: #333;
text-align: center;
height: 60vh;
overflow: scroll;
ul {
li {
float: left;
width: 24%;
margin-top: 1.5vh;
.item-bg-images {
width: 100%;
height: 100%;
background-repeat: no-repeat;
background-size: 100% 100%;
.item-active {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
background-color: rgb(0, 0, 0);
opacity: 0.7;
span {
color: #31b4a7;
font-size: 52px;
}
}
}
}
li:nth-child(4n + 1) {
margin-left: 0px;
}
li:nth-child(4n + 2) {
margin-left: 1%;
}
li:nth-child(4n + 3) {
margin-left: 1%;
}
li:nth-child(4n + 4) {
margin-left: 1%;
}
}
}
.el-footer {
text-align: center;
line-height: 60px;
}
</style>
//src\pages\editor\components\TheTemplateList.vue
<template>
<!--region 左边在线模板列表-->
<div class="the-template-list">
</div>
<!--endregion-->
</template>
<script>
export default {
components: {},
data () {
return {}
},
created () {
},
mounted () {
},
computed: {},
methods: {}
}
</script>
<style lang="scss">
@import "../../../styles/variables";
.the-template-list {
background-color: #fff;
box-sizing: border-box;
border-right: 1px solid $border-color-1;
flex: 0 0 $template-width;
}
</style>
//src\vuex\constTypes.js
export default {
// 页面组件
ADD_COMPONENT: 'add_component', // 添加组件
TOGGLE_COMP: 'toggle_component', // 切换组件
COPY_COMP: 'copy_comp', // 拷贝组件
EDIT_COMP: 'edit_component', // 编辑组件
REMOVE_COMP: 'remove_component', // 删除组件
// 页面
ADD_PAGE: 'add_page', // 添加新页面
INSERT_PAGE: 'insert_page', // 插入页面
COPY_PAGE: 'copy_page', // 拷贝页面
REMOVE_PAGE: 'remove_page', // 删除页面
EDIT_PAGE: 'edit_page', // 编辑页面
TOGGLE_PAGE: 'toggle_page', // 切换页面
ADD_COMP_TO_PAGES: 'add_component_to_page', // 往页面中添加组件
REMOVE_COMP_TO_PAGES: 'remove_component_to_page', // 往页面中删除组件
// 属性编辑调整
OPEN_PROPS_PANEL: 'open_props_panel', // 打开组件属性面板
CLOSE_PROPS_PANEL: 'close_props_panel', // 关闭组件属性面板
// 图层编辑调整
OPEN_LAYER_PANEL: 'open_layer_panel', // 打开图层属性面板
CLOSE_LAYER_PANEL: 'close_layer_panel' // 关闭图层属性面板
}
//src\vuex\modules\pages.js
import types from '../constTypes.js'
import { getNewPageId, getNewPage } from '../function.js'
import { cloneObj } from '../../assets/utils/util.js'
const state = {
list: [],
curPageId: null
}
const getters = {
// 取页面列表
pages: (state) => state.list,
// 取当前页面ID
curPageId: (state) => {
if (state.curPageId) {
return state.curPageId
}
if (state.list[0]) {
return state.list[0]['id']
}
},
// 取当前页面
curPage: (state) => {
return state.list
.find(
(_x) => _x.id === state.curPageId
) || state.list[0]
},
getPageConfigByPageId: (state) => (pageId) => {
return state.list.find(_x => _x.id === pageId)
}
}
const mutations = {
// 切换页面
[types.TOGGLE_PAGE] (state, pageId) {
state.curPageId = pageId
},
// 添加页面
[types.ADD_PAGE] (state, pageData) {
state.list.push(pageData)
},
// 插入页面
[types.INSERT_PAGE] (state, { page, pageId }) {
let index = state.list
.findIndex(
_x => _x.id === pageId
)
if (index > -1) {
state.list.splice(index + 1, 0, page)
}
},
// 拷贝页面
[types.COPY_PAGE] (state, { prePageId, pageId }) {
let list = state.list
let index = list.findIndex(_x => _x.id === prePageId)
if (index > -1) {
// 复制一个新的页面JSON对象
let pageData = cloneObj(list[index])
pageData.id = pageId
// 克隆的页面中组件处理
// if (pageData.comps && pageData.comps.length > 0) {
// for (let i = 0; i < pageData.comps.length; i++) {
// pageData.comps[i].id = pageData.comps[i].id + (i + 1) * pageData.comps[i].id
// pageData.comps[i].parentId = pageId
// }
// }
list.splice(index + 1, 0, pageData)
state.curPageId = pageId
}
},
// 删除页面
[types.REMOVE_PAGE] (state, pageId) {
let list = state.list
let index = list.findIndex(_x => _x.id === pageId)
if (index > -1) {
list.splice(index, 1)
let nextActivePage = list[index] || list[index - 1]
state.curPageId = nextActivePage['id']
}
},
// 编辑当前页面
[types.EDIT_PAGE] (state, { type, value, pageId }) {
let page = state.list
.find(
(_x) => _x.id === pageId || _x.id === state.curPageId
)
if (page) {
let pageProp = page[type]
for (let key in value) {
if (!pageProp[key]) {
continue
}
if (typeof value[key] === 'object') {
Object.assign(pageProp[key], value[key])
} else {
pageProp[key] = value[key]
}
}
}
},
// 往页面中添加组件
[types.ADD_COMP_TO_PAGES] (state, compData) {
let curPage = state.list
.find((_x) => _x.id === state.curPageId)
if (curPage) {
curPage.comps.push(compData)
}
},
// 往页面中删除组件
[types.REMOVE_COMP_TO_PAGES] (state, compId) {
let curPage = state.list.find((_x) => _x.id === state.curPageId)
if (curPage) {
let comps = curPage.comps
let index = curPage.comps.findIndex(_x => _x.id === compId)
if (index > -1) {
comps.splice(index, 1)
if (comps && comps.length > 0) {
curPage.comps = comps
} else {
curPage.comps = []
}
}
}
}
}
const actions = {
// 添加新的页面
addNewPages ({ commit }) {
const page = getNewPage()
if (page) {
commit(types.ADD_PAGE, page)
}
return page.id
},
// 拷贝指定页面
copyPage ({ commit }, pageId) {
let id = getNewPageId()
if (id) {
commit(types.COPY_PAGE, {
prePageId: pageId,
pageId: id
})
}
return id
},
// 删除指定页面
removePage ({ commit }, pageId) {
commit(types.REMOVE_PAGE, pageId)
return pageId
},
// 从页面中删除组件
removeComponentToPage ({ commit }, compId) {
commit(types.REMOVE_COMP_TO_PAGES, compId)
return compId
},
/**
* 初始化Editor 编辑器
* 同时新建一个页面
**/
initPageEditor ({ dispatch, commit }) {
dispatch('addNewPages')
.then((id) => {
commit(types.TOGGLE_PAGE, id)
})
},
/**
* 切换页面
* @param commit
* @param pageId
*/
togglePage ({ commit }, pageId) {
commit(types.TOGGLE_PAGE, pageId)
},
/**
* 修改当前页面
* @param commit
* @param type
* @param value
* @param pageId
*/
editCurPage ({ commit }, { type, value, pageId }) {
commit([types.EDIT_PAGE], { type, value, pageId })
},
/**
* 更新页面中的某个组件中的css属性
* @param commit
* @param getters
* @param state
* @param type
* @param key
*/
editCompOfCurPage ({ commit, getters, state }, { type, key }) {
// 首先找到当前页面
let _page = state.list.find(_x => _x.id === state.curPageId)
if (_page) {
// 找当前页面中的组件
let _comp = _page.comps.find(_x => _x.id === window.$globalHub.$store.state.components.curCompId)
let _obj = _comp[type]
if (typeof _obj === 'object') {
if (typeof _obj[key] === 'object') {
Object.assign(_obj[key], window.$globalHub.$store.getters.curComp[type][key])
console.log(' state.list:', state.list)
} else {
_obj[key] = window.$globalHub.$store.getters.curComp[type][key]
console.log(' state.list:', state.list)
}
}
}
}
}
export default {
state,
getters,
actions,
mutations
}
这个里面主要是数据的流向,真的简化了很多dom的思想不需要dom思想
//src\pages\editor\components\TheTemplateList.vue
<template>
<!--region 页面列表-->
<div class="the-page-list">
<div class="title">页面管理</div>
<div class="action-group">
<div class="item" @click="handleAddPage">
<span class="axon-icon" v-html="''"></span>添加
</div>
<div class="item" @click="handleCopyPage">
<span class="axon-icon" v-html="''"></span>复制
</div>
<div class="item" @click="handleRemovePage">
<span class="axon-icon" v-html="''"></span>删除
</div>
</div>
<ul class="list">
<template v-for="(page, key) in pages">
<li :key="key"
:id="page.id"
:class="curPageId === page.id ? 'page active' : 'page'" @click="handleTogglePage(page.id)">
<span class="key">
<em>{{ key + 1 }}</em>
</span>
<p class="name">第 {{ key + 1 }} 页</p>
<span class="more-action axon-icon" v-html="''">
</span>
</li>
</template>
</ul>
</div>
<!--endregion-->
</template>
<script>
export default {
components: {},
data () {
return {}
},
created () {
},
mounted () {
},
computed: {
// 取页面列表
pages () {
try {
return this.$store.getters.pages
} catch (e) {
this.$notify.error({
title: '错误',
message: '初始化页面失败,请刷新后重试!'
})
}
},
// 当前页面ID
curPageId () {
try {
return this.$store.getters.curPageId
} catch (e) {
this.$notify.error({
title: '错误',
message: '初始化页面失败,请刷新后重试!'
})
}
}
},
methods: {
/**
* 创建新页面
*/
handleAddPage () {
this.$store.dispatch('addNewPages', this.curPageId).then((id) => {
this.handleTogglePage(id)
})
},
/**
* 拷贝当前页面
*/
handleCopyPage () {
this.$store.dispatch('copyPage', this.curPageId).then((id) => {
this.handleTogglePage(id)
})
},
/**
* 删除当前页面
*/
handleRemovePage () {
this.$store.dispatch('removePage', this.curPageId)
},
/**
* 切换页面
* @param pageId
*/
handleTogglePage (pageId) {
this.$store.dispatch('togglePage', pageId)
}
}
}
</script>
<style lang="scss">
@import "../../../styles/variables";
.the-page-list {
background-color: #fafafa;
border-left: 1px solid $border-color-1;
box-sizing: border-box;
flex: 0 0 $setting-width;
// 页头
.title {
height: 40px;
line-height: 40px;
text-align: center;
background-color: #e0e0e0;
font-weight: 600;
}
// 列表
.list {
.page {
border-top: 1px solid #e6ebed;
height: 70px;
line-height: 70px;
color: #666;
cursor: pointer;
font-size: 12px;
position: relative;
border-bottom: 1px solid #e6ebed;
display: flex;
.key, .more-action {
flex: 0 0 44px;
text-align: center;
display: block;
em {
display: inline-block;
width: 24px;
height: 24px;
line-height: 24px;
text-align: center;
border-radius: 12px;
background-color: #ccc;
color: #fff;
font-weight: 400;
font-style: normal;
}
}
.more-action {
font-size: 13px;
}
.name {
font-size: 14px;
flex: 100%;
}
&.active {
background-color: #eee;
.key {
em {
background-color: #1593ff;
}
}
.name {
color: #000;
}
}
}
}
.action-group {
display: flex;
height: 36px;
line-height: 36px;
.item {
flex: 0 0 33.33%;
text-align: center;
font-size: 12px;
&:not(:last-child) {
border-right: 1px solid #eee;
}
span {
font-size: 14px;
padding-right: 4px;
}
}
}
}
</style>
//src\pages\editor\components\TheEditorContainer.vue
<template>
<div class="the-editor-container">
<div class="container-wrapper">
<!--region 页面列表-->
<div class="page-list">
<div class="page-wrapper">
<!--region 背景-->
<div class="wrapper-background"
:style="{
backgroundColor: curPage.css.bgc,
backgroundImage: 'url(' + curPage.css.bgi + ')'
}"></div>
<!--endregion-->
<!--region 组件-->
<div class="component-wrapper">
<template v-for="comp in curPage.comps">
<vue-drr
:ref="'comp_' + comp.id"
:id="'comp_' + comp.id"
:key="comp.id"
:w="parseInt(comp.css.w)"
:h="parseInt(comp.css.h)"
:x="parseInt(comp.css.l)"
:y="parseInt(comp.css.t)"
:minw="20"
:minh="30"
:grid="grid"
:parent="true"
:angle="parseInt(comp.css.rotate)"
@activated="toggleComp(comp.id, comp.name)"
@resizestop="handleResizing"
@dragstop="handleDragging"
@rotatestop="handleRotating">
<comp-list
@click="handleClick(comp)"
@dblclick="handleDbClick"
@contextmenu="handleContextmenu(comp)"
:id="comp.id"
class="comp"
:style="comp.css | formatStyle('ft', 'lh')"
:type="comp.name"></comp-list>
</vue-drr>
</template>
</div>
<!--endregion-->
</div>
</div>
<!--endregion-->
</div>
</div>
</template>
<script>
import vueDrr from 'vue-drr'
import { throttle } from '../../../assets/utils/util.js'
import BaseComps from '../../../components/iTemplate/index.js'
const BASE_COMP_NAME = 'Base'
const BASE_COMP_CONFIG = 'Config'
export default {
components: {
vueDrr,
compList: {
props: {
id: {
type: [String, Number],
required: true
}, // 组件ID
type: {
type: String,
required: true
} // 组件类型名称
},
render (h) {
let _compFlagName = `${BASE_COMP_NAME}${this.type}`
let _module = BaseComps[_compFlagName]
return h(_module, {
props: {
id: this.id
},
nativeOn: {
click: throttle(this.handleClick, 1000),
dblclick: this.handleDbClick,
// contextmenu: throttle(this.handleContextmenu, 1000)
contextmenu: evt => {
// 阻止浏览器默认点击行为
evt.preventDefault()
throttle(this.handleContextmenu(this.comp), 1000)
}
}
})
},
methods: {
// 单击:左击
handleClick (comp) {
this.$emit('click', comp)
},
// 双击
handleDbClick () {
this.$emit('dblclick', this.type)
},
// 单击:右击
handleContextmenu (comp) {
this.$emit('contextmenu', comp)
}
}
}
},
data () {
return {
grid: [1, 1] // 组件拖动的网格
}
},
created () {
},
mounted () {
},
computed: {
// 取当前页面
curPage () {
try {
return this.$store.getters.curPage
} catch (e) {
this.$notify.error({
title: '错误',
message: '初始化页面失败,请刷新后重试!'
})
}
},
// 取当前正在编辑的组件
curCompId () {
return this.$store.state.components.curCompId
},
// 取当前组件
curComp () {
return this.$store.getters.currComp
}
},
methods: {
// 切换组件
toggleComp (id, name) {
// 打开组件属性设置面板
this.$store.dispatch('openPropsPanel', {
id: id,
name: name + BASE_COMP_CONFIG
})
this.$store.dispatch('toggleComp', id)
},
// 左击:点击组件
handleClick (comp) {
// 关闭图层面板
this.$store.dispatch('closeLayerPanel')
// 打开组件属性设置面板
this.$store.dispatch('openPropsPanel', {
id: comp.id,
name: comp.name + BASE_COMP_CONFIG
})
},
/**
* 双击组件
* @param type 组件类型
*/
handleDbClick (type) {
console.log('双击666')
},
handleContextmenu (comp) {
console.log('右击666')
this.$store.dispatch('closePropsPanel')
// 打开图层设置面板
this.$store.dispatch('openLayerPanel', {
id: comp.id,
name: comp.name + 'layer' + BASE_COMP_CONFIG
})
},
// 组件拉伸时触发
handleResizing (x, y, w, h) {
this.updateCompStyle({ l: x, t: y, w, h })
},
// 组件拖动时触发
handleDragging (x, y) {
this.updateCompStyle({ l: x, t: y })
},
// 组件旋转时触发
handleRotating (angle) {
this.updateCompStyle({ rotate: angle })
},
// 同步用户修改
updateCompStyle (value) {
this.$store.dispatch('editComp', {
type: 'css',
value: value
})
}
}
}
</script>
<style lang="scss">
.the-editor-container {
margin: 0 auto;
border: 2px solid #ddd;
border-radius: 2px;
width: 52.7rem;
min-height: 82.6rem;
flex: 0 0 1;
height: 100%;
background-color: #fff;
position: relative;
.container-wrapper {
width: 100%;
height: 100%;
.page-list {
width: 100%;
height: 100%;
.page-wrapper {
width: 100%;
height: 100%;
.wrapper-background {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-repeat: no-repeat;
background-size: cover;
background-origin: content-box;
background-position: 50% 50%;
}
.component-wrapper {
width: 100%;
height: 100%;
position: relative;
}
}
}
}
}
</style>
//src\pages\editor\components\TheCompPropsConfig.vue
<!--region 组件属性设置面板-->
<template>
<div ref="propsPanelConfig" class="the-comp-props-config-page" v-if="propsPanel && propsPanel.show"
:style="{ height: panelHeight }">
<div :class="isMouseDown ? 'header pointer-events': 'header'">
<div class="title" ref="header" @mousedown="handleDragMouseDown">组件设置</div>
<div class="close">
<span class="axon-icon" v-html="''" @click="closePropsPanel()"></span>
</div>
</div>
<div class="props-panel-config">
<props-panel :name="propsPanel.name"></props-panel>
</div>
</div>
</template>
<!--endregion-->
<script>
import CompPropsConfig from '../../../components/iTemplate/config.js'
import { throttle } from '../../../assets/utils/util.js'
export default {
components: {
propsPanel: {
props: {
name: {
type: String,
required: true
}
},
render (h) {
let _module = CompPropsConfig[this.name]
return h(_module, {
props: {
name: {
type: String
}
}
})
}
}
},
data () {
return {
isMouseDown: false, // 鼠标是否按住可拖放区域
dragObj: {
initX: 0,
indexY: 0,
width: 260,
height: 48
} // 拖动对象位置
}
},
created () {
},
mounted () {
document.addEventListener('mousemove', (e) => { this.throttleMove(e) })
document.addEventListener('mouseup', (e) => { this.handleDragMouseUp(e) })
},
computed: {
// 根据父级组件高度设定属性面板高度
panelHeight () {
// 取父级组件中的父级标签属性
let _parentHeight = 0
try {
_parentHeight = this.$parent.$refs.centerBox.offsetHeight
return `${_parentHeight - 100}px`
} catch (e) {
return 0
}
},
// 取面板信息
propsPanel () {
return this.$store.getters.propPanel
}
},
watch: {},
methods: {
// 关闭属性面板
closePropsPanel () {
console.log(' close:')
this.$store.dispatch('closePropsPanel')
},
// 拖动:当鼠标点下的时候,给要拖动的元素附上初始值
handleDragMouseDown (e) {
this.isMouseDown = true
this.dragObj.initX = e.offsetX
this.dragObj.initY = e.offsetY
this.dragObj.width = this.$refs.propsPanelConfig.offsetWidth
this.dragObj.height = this.$refs.propsPanelConfig.offsetHeight - this.$refs.header.offsetHeight
},
// 鼠标移动 --- 节流
throttleMove (e) {
return throttle(this.handleDragMouseMove(e), 500)
},
// 拖动过程中,需要实时监听位置变化
handleDragMouseMove (e) {
if (this.isMouseDown) {
// 移动外层需要移动的div
let _cx = e.clientX - this.dragObj.initX
let _cy = e.clientY - this.dragObj.indexY
// 限制panel不能超出浏览器边界
_cx = _cx >= 0 ? _cx : 0
_cy = _cy >= 0 ? _cy : 0
if (window.innerWidth - e.clientX + this.dragObj.initX < this.dragObj.width + 16 || window.innerWidth - this.dragObj.width < _cx) {
_cx = window.innerWidth - this.dragObj.width
}
if (e.clientY > window.innerHeight - this.dragObj.height - this.$refs.header.offsetHeight + this.dragObj.initY) {
_cy = window.innerHeight - this.$refs.header.offsetHeight - this.dragObj.height
}
this.$refs.propsPanelConfig.style.left = _cx + 'px'
this.$refs.propsPanelConfig.style.top = _cy + 'px'
}
},
// 鼠标移除,取消监听
handleDragMouseUp (e) {
if (e.clientY > window.innerWidth || e.clientY < 0 || e.clientX < 0 || e.clientX > window.innerHeight) {
this.isMouseDown = false
}
this.isMouseDown = false
}
}
}
</script>
<style lang="scss">
.the-comp-props-config-page {
background-color: #fafafa;
position: fixed;
left: 0;
top: 0;
width: 260px;
overflow: hidden;
box-shadow: 0 0 16px rgba(0, 0, 0, 0.16);
z-index: 1000;
height: 100%;
user-select: none;
.header {
background-color: #fff;
height: 48px;
line-height: 48px;
width: 100%;
padding: 0 16px;
user-select: none;
display: flex;
&.pointer-events {
pointer-events: none;
}
.title {
flex: 0 0 50%;
font-size: 13px;
cursor: move;
}
.close {
flex: 0 0 50%;
text-align: right;
z-index: 9999;
span {
cursor: pointer;
font-size: 14px;
color: #a3afb7;
}
}
}
.props-panel-config {
height: calc(#{"100% - 54px"});
.text-comp-config-page {
height: 100%;
.el-tabs {
height: 100%;
overflow: hidden;
.el-tabs__content {
height: calc(#{"100% - 50px"});
overflow-y: auto;
.el-tab-pane {
height: 100%;
}
}
}
}
}
}
</style>
//src\pages\editor\components\TheCompLayerManager.vue
<!--region 图层设置面板-->
<template>
<div ref="layerManagerPanel" class="the-comp-layer-manager-page" v-if="layerPanel && layerPanel.show">
<div :class="isMouseDown ? 'header pointer-events': 'header'">
<div class="title" ref="header" @mousedown="handleDragMouseDown">图层设置</div>
<div class="close">
<span class="axon-icon" v-html="''" @click="closeLareyPanel()"></span>
</div>
</div>
<div class="layer-panel-list">
<ul>
<li @click="handRemove">删除组件</li>
<li @click="handleCopyComp">拷贝组件</li>
<!---图层显示依据数组的降序排练--关于图层数据的操作要反过来-->
<li @click="handleSwapItem('down')">上移一层</li>
<li @click="handleSwapItem('up')">下移一层</li>
<li @click="handleSwapItem('bottom')">置顶</li>
<li @click="handleSwapItem('top')">置底</li>
</ul>
</div>
</div>
</template>
<!--endregion-->
<script>
import { throttle } from '../../../assets/utils/util.js'
import SwapArrayItem from '../../../assets/utils/swapArrayItem.js'
export default {
components: {},
data () {
return {
isMouseDown: false, // 鼠标是否按住可拖放区域
dragObj: {
initX: 0,
indexY: 0,
width: 160,
height: 48
} // 拖动对象位置
}
},
created () {
},
mounted () {
document.addEventListener('mousemove', (e) => { this.throttleMove(e) })
document.addEventListener('mouseup', (e) => { this.handleDragMouseUp(e) })
},
computed: {
// 取图层信息
layerPanel () {
return this.$store.getters.layerPanel
},
// 取当前页面ID
curPageId () {
return this.$store.getters.curPage.id
},
// 取当前页面中所有组件
curPageComps () {
return this.$store.getters.curPage.comps
},
// 取当前正在编辑的组件
curCompId () {
return this.$store.state.components.curCompId
}
},
watch: {},
methods: {
// 关闭图层面板
closeLareyPanel () {
this.$store.dispatch('closeLayerPanel')
},
// 删除当前组件
handRemove () {
let compId = this.curCompId
// 删除当前组件
this.$store.dispatch('removeComp', compId)
// 将组件从当前页面中移除
this.$store.dispatch('removeComponentToPage', compId)
// 关闭图层面板
this.$store.dispatch('closeLayerPanel')
},
// 拷贝当前组件
handleCopyComp () {
this.$store.dispatch('copyComp', this.curCompId)
},
// 交换数组元素
handleSwapItem (operateName) {
// 获取当前数组下标
let index = this.accpetIndex(this.curPageComps, this.curCompId)
SwapArrayItem.swapByOperate(this.curPageComps, index, operateName)
},
/**
* 获取下标
* @param ary
* @param id
* @returns {number}
*/
accpetIndex (ary, id) {
var index = -1
for (let i = 0; i < ary.length; i++) {
if (ary[i] && ary[i].id === id) {
index = i
}
}
return index
},
// 拖动:当鼠标点下的时候,给要拖动的元素附上初始值
handleDragMouseDown (e) {
this.isMouseDown = true
this.dragObj.initX = e.offsetX
this.dragObj.initY = e.offsetY
this.dragObj.width = this.$refs.layerManagerPanel.offsetWidth
this.dragObj.height = this.$refs.layerManagerPanel.offsetHeight - this.$refs.header.offsetHeight
},
// 鼠标移动 --- 节流
throttleMove (e) {
return throttle(this.handleDragMouseMove(e), 500)
},
// 拖动过程中,需要实时监听位置变化
handleDragMouseMove (e) {
if (this.isMouseDown) {
// 移动外层需要移动的div
let _cx = e.clientX - this.dragObj.initX
let _cy = e.clientY - this.dragObj.indexY
// 限制panel不能超出浏览器边界
_cx = _cx >= 0 ? _cx : 0
_cy = _cy >= 0 ? _cy : 0
if (window.innerWidth - e.clientX + this.dragObj.initX < this.dragObj.width + 16 || window.innerWidth - this.dragObj.width < _cx) {
_cx = window.innerWidth - this.dragObj.width
}
if (e.clientY > window.innerHeight - this.dragObj.height - this.$refs.header.offsetHeight + this.dragObj.initY) {
_cy = window.innerHeight - this.$refs.header.offsetHeight - this.dragObj.height
}
this.$refs.layerManagerPanel.style.left = _cx + 'px'
this.$refs.layerManagerPanel.style.top = _cy + 'px'
}
},
// 鼠标移除,取消监听
handleDragMouseUp (e) {
if (e.clientY > window.innerWidth || e.clientY < 0 || e.clientX < 0 || e.clientX > window.innerHeight) {
this.isMouseDown = false
}
this.isMouseDown = false
}
}
}
</script>
<style lang="scss">
.the-comp-layer-manager-page {
background-color: #fafafa;
position: fixed;
left: 0;
top: 0;
width: 160px;
overflow: hidden;
box-shadow: 0 0 16px rgba(0, 0, 0, 0.16);
z-index: 1000;
height: 286px;
user-select: none;
box-sizing: content-box;
.header {
background-color: #fff;
height: 48px;
line-height: 48px;
width: 100%;
padding: 0 16px;
user-select: none;
display: flex;
&.pointer-events {
pointer-events: none;
}
.title {
flex: 0 0 50%;
font-size: 13px;
cursor: move;
}
.close {
flex: 0 0 30%;
text-align: right;
z-index: 9999;
span {
cursor: pointer;
font-size: 14px;
color: #a3afb7;
}
}
}
.layer-panel-list {
height: calc(#{"100% - 54px"});
font-size: 13px;
li {
height: 40px;
line-height: 40px;
padding: 0 16px;
box-sizing: content-box;
border-top: 1px solid #e6ebed;
&:hover {
color: #ffffff;
background-color: #1593ff;
}
}
li:nth-child(1) {
&:hover {
color: #ffffff;
background-color: #ff2a6a;
}
}
}
}
</style>
//src\pages\icons\Index.vue
<template>
<div class="icon-page">
<el-row :gutter="20">
<template v-for="(item, index) in icons">
<el-col :key="index" :span="6" class="item">
<span class="axon-icon" v-html="item.icon"></span>
<p><span style="color: #606266;height: 1em;font-size: 12px;">{{item.icon}}</span></p>
</el-col>
</template>
</el-row>
</div>
</template>
<script>
export default {
components: {},
data () {
return {
icons: [{ name: '叉号', icon: '' },
{ name: '原型叉号', icon: '' },
{ name: '栏目', icon: '' },
{ name: '箭头', icon: '' },
{ name: '隐藏密码', icon: '' },
{ name: '显示密码', icon: '' },
{ name: '用户', icon: '' },
{ name: '用户', icon: '' },
{ name: '发货', icon: '' },
{ name: '订单', icon: '' },
{ name: '用户', icon: '' },
{ name: '金额', icon: '' },
{ name: '主页', icon: '' },
{ name: '错误', icon: '' },
{ name: '错误', icon: '' },
{ name: '栏目', icon: '' },
{ name: '栏目', icon: '' },
{ name: '验证码', icon: '' },
{ name: '手机', icon: '' }
]
}
},
mounted () {},
methods: {}
}
</script>
<style lang="less" rel="stylesheet/less" scoped>
.icon-page {
background-color: #fff;
padding: 20px 40px;
.item {
text-align: center;
height: 120px;
.axon-icon {
font-size: 24px;
color: #606266;
margin-bottom: 15px;
display: block;
}
}
}
</style>
//src\pages\table\Index.vue
<template>
<div class="table-page">
<!--region table 表格-->
<i-table :list="list" :total="total" :loading="loading" :otherHeight="otherHeight"
@handleSizeChange="handleSizeChange"
@handleIndexChange="handleIndexChange" @handleSelectionChange="handleSelectionChange" :options="options"
:pagination="pagination" :columns="columns" :operates="operates">
</i-table>
<!--endregion-->
</div>
</template>
<script type="text/jsx">
import iTable from '../../components/iTable/Index'
import BLL from './Index'
import { mapGetters } from 'vuex'
export default {
components: { iTable },
data () {
return {
total: 0,
list: [],
otherHeight: 208,
filter: {
date: '',
phone: ''
},
collapseTitle: '筛选条件:',
columns: [
{
prop: 'id',
label: '编号',
align: 'center',
width: 60
},
{
prop: 'title',
label: '标题',
align: 'center',
width: 400,
formatter: (row, column, cellValue) => {
return `<span style="white-space: nowrap;color: dodgerblue;">${row.title}</span>`
}
},
{
prop: 'state',
label: '状态',
align: 'center',
width: '160',
render: (row, column) => {
return row.state === 0 ? <el-tag type='success'> 上架 </el-tag> : row.state === 1
? <el-tag type="info">下架</el-tag> : <el-tag type='danger'>审核中</el-tag>
}
}, {
prop: 'author',
label: '作者',
align: 'center',
width: 120
},
{
prop: 'phone',
label: '联系方式',
align: 'center',
width: 160
},
{
prop: 'email',
label: '邮箱',
align: 'center',
width: 240
},
{
prop: 'createDate',
label: '发布时间',
align: 'center'
}
], // 需要展示的列
operates: {
width: 200,
fixed: 'right',
list: [
{
label: '编辑',
type: 'warning',
show: true,
icon: 'el-icon-edit',
plain: false,
disabled: false,
method: (index, row) => {
this.handleEdit(index, row)
}
},
{
label: '删除',
type: 'danger',
icon: 'el-icon-delete',
show: true,
plain: false,
disabled: false,
method: (index, row) => {
this.handleDel(index, row)
}
}
]
}, // 操作按钮组
pagination: {
pageIndex: 1,
pageSize: 20
}, // 分页参数
options: {
stripe: true, // 是否为斑马纹 table
highlightCurrentRow: true, // 是否支持当前行高亮显示
mutiSelect: true // 是否支持列表项选中功能
} // table 的参数
}
},
created () {
this.BLL = new BLL(this)
},
mounted () {
this.BLL.getList()
},
computed: {
...mapGetters(['button']),
loading () {
return this.$store.getters.btnLoading.str && this.$store.getters.btnLoading.id
}
},
methods: {
// 切换每页显示的数量
handleSizeChange (pagination) {
this.pagination = pagination
this.BLL.getList()
},
// 切换页码
handleIndexChange (pagination) {
this.pagination = pagination
this.BLL.getList()
},
// 选中行
handleSelectionChange (val) {
console.log('val:', val)
},
// 编辑
handleEdit (index, row) {
console.log(' index:', index)
console.log(' row:', row)
this.BLL.getList()
},
// 删除
handleDel (index, row) {
console.log(' index:', index)
console.log(' row:', row)
},
// 刷新
reload (form) {
console.log('into reload')
this.$refs['filter'].resetFields()
this.filter = { date: '', phone: '' }
this.BLL.getList()
},
// 筛选数据
filterData () {
this.BLL.getList()
}
}
}
</script>
<style lang="scss">
@import "./Index";
</style>
//src\pages\menus\Index.vue
<!--菜单页-->
<template>
<div class="menus-page">
<div slot="header" class="clearfix header">
</div>
<i-table :list="menusList" :otherHeight="otherHeight" :columns="columns" :operates="operates"></i-table>
</div>
</template>
<script type="text/jsx">
import iTable from '../../components/iTable/Index'
import { mapState } from 'vuex'
export default {
components: { iTable },
data () {
return {
menusList: [],
columns: [
{
prop: 'name',
label: '名称',
align: 'left',
width: 200,
className: 'first-level',
formatter: function (row, column) {
let marginLeft = parseInt(row.level) * 20
if (row.icon) {
let icon = `<span class="axon-icon" style="margin-right:4px;margin-left:${marginLeft}px;font-size:16px;">${row.icon}</span>`
return `${icon}<span>${row.name}</span>`
} else {
return `<span style="margin-left:${marginLeft}px;">${row.name}</span>`
}
}
}, {
prop: 'link',
label: '地址',
align: 'center'
}, {
prop: 'level',
label: '排序',
align: 'center',
width: 100
}, {
prop: 'state',
label: '状态',
align: 'center',
width: 100,
render: function (row, column) {
return row.state === 1 ? <el-tag>启用</el-tag> : <el-tag type="info">禁用</el-tag>
}
}
],
operates: {
width: 200,
fixed: 'right',
list: [
{
label: '编辑',
type: 'warning',
show: true,
icon: 'el-icon-edit',
plain: true,
disabled: false,
method: (index, row) => {
this.handleEdit(index, row)
}
},
{
label: '删除',
type: 'danger',
show: true,
icon: 'el-icon-delete',
plain: true,
disabled: false,
method: (index, row) => {
this.handelDel(index, row)
}
}
]
},
otherHeight: 216
}
},
mounted () {
this.initMenus()
},
computed: {
...mapState([
'menus'
]),
loading () {
return this.$store.getters.btnLoading.str && this.$store.getters.btnLoading.id
}
},
methods: {
initMenus () {
if (this.menus && this.menus.sidebar.length > 0) {
let level = 1
this.formatMenusList(this.menus.sidebar, level)
}
},
// 递归menus的各级菜单
formatMenusList (list, level) {
for (let i = 0; i < list.length; i++) {
this.menusList.push({ ...list[i], level })
if (list[i].children) {
level++
this.formatMenusList(list[i].children, level)
}
}
},
// 编辑菜单
handleEdit (index, row) {
console.log(row)
},
// 删除菜单
handelDel (index, row) {
console.log(row)
},
add () {
console.log('into add()')
},
// 转换 string 方法名为 Function 对象
callback (fn) {
fn = `this.${fn}()`
return eval(fn) // eslint-disable-line
// this.__callback(fn, this)
}
}
}
</script>
<style lang="scss">
.menus-page {
background-color: #fff;
.header {
padding: 10px 10px;
}
}
</style>
<!--角色管理页面-->
<template>
<div class="role-page">
<i-table></i-table>
</div>
</template>
<script>
import iTable from '../../components/iTable/Index'
export default {
components: { iTable },
data () {
return {}
},
mounted () { },
computed: {},
methods: {}
}
</script>
<style lang="scss" scoped>
.role-page {
background-color: #fff;
}
</style>
<template>
<div class="MyContain">
</div>
</template>
<script>
export default {
components: {},
data () {
return {}
},
mounted () { },
computed: {},
methods: {}
}
</script>
<style lang="scss" scoped>
.MyContain {
background-color: #fff;
}
</style>
<template>
<div class="MyContain">
</div>
</template>
<script>
export default {
components: {},
data () {
return {}
},
mounted () { },
computed: {},
methods: {}
}
</script>
<style lang="scss" scoped>
.MyContain {
background-color: #fff;
}
</style>
【珍惜时间】iReport的更多相关文章
- N年之后,只记得三井寿!而我们程序猿们也要加油珍惜时间!
[感觉程序员看一篇励志文章效果大于6篇技术文章,3份源码下载.....所以上此文] [说明:本文不少段落是摘自别人文章,因为本人写程序的文笔有限,怕感动不了大家,所以摘取了不错的部分] 前段时间重新看 ...
- 【珍惜时间】vue-websocket
这个项目可能是个有始无终的项目?跟我一起分析吧,比较简单的一个项目 另外,我也想跟自己说,我好像失去了那个努力的自己了.要珍惜时间,好好加油啊~ 项目地址为:https://github.com/xi ...
- 【珍惜时间】vuepro
老规矩放上大大的github开源地址:https://github.com/goodheart222/vuepro 我们再来看看项目的效果,初步根据效果做到心中有数 看到效果的话,我们会发现,肯定是有 ...
- F01:金融学第一定律:时间的价值
很小的时候,我们就听大人们讲过:时间就是金钱. 长大了,也觉得这句话应该是对的.我们很珍惜时间,但是似乎没有看到金钱的急剧增加啊.总之,这是模糊,体验感又不强的一句话. 既然谈到时间的价值,价值最容易 ...
- 向大学说拜拜——大学 > 兴趣 + 时间 + 思考 + 实践
[人物素描] 大学期间,担任过班委,加入过学生会,参加过社团,拿过奖学金......而印象最深刻的莫过于参加并组织过ACM集训,以及参加过导师的国家自然科学基金项目了.毕业时顺利拿到一波offer,并 ...
- JS or C#?不存在的脚本之争
前言: 又来到了周末,小匹夫也终于有了喘口气写写博客的时间和精力.话说周五的下午,小匹夫偶然间晃了一眼蛮牛的QQ群,又看到了一个Unity3D开发中老生长谈的问题,“我的开发语言究竟是选择JavaSc ...
- 终于等到你---订餐系统之负载均衡(nginx+memcached+ftp上传图片+iis)
又见毕业 对面工商大学的毕业生叕在拍毕业照了,一个个脸上都挂满了笑容,也许是满意自己四年的修行,也许是期待步入繁华的社会... 恰逢其时的连绵细雨与满天柳絮,似乎也是在映衬他们心中那些离别的忧伤,与对 ...
- ACM训练计划建议(写给本校acmer,欢迎围观和指正)
ACM训练计划建议 From:freecode# Date:2015/5/20 前言: 老师要我们整理一份训练计划给下一届的学弟学妹们,整理出来了,费了不少笔墨,就也将它放到博客园上供大家参考. 菜 ...
- 关于我的OI生涯(AFO){NOIP2016 后}
这篇我就随意写啦~不用统一的“题解”形式.♪(^∀^●)ノ 也分好几次慢慢更吧~ 对于NOIP2016的总结,我本想善始善终back回,但是心情不足以支撑我,那就只能有始有终了......下面进入我的 ...
随机推荐
- NX二次开发-遍历当前part所有component,把装配子部件设置成工作部件
NX11+VS2013 #include <uf.h> #include <uf_disp.h> #include <uf_modl.h> #include < ...
- [JZOJ 5811] 简单的填数
题意:自己搜吧... 思路: 记二元组\((x,l)\)表示当前为\(x\)且之前有\(l\)个连续数与\(x\)相同. 并且维护up和low数组表示取到最大/最小值时,连续序列的长度. 正一遍,反一 ...
- easyUI tabs 显示与隐藏 tab 页
隐藏: tab_option = $('#tabs').tabs('getTab'," 单位信息 ").panel('options').tab; tab_option.hide( ...
- D语言简介
D语言,一种通用计算机程序语言,威力强大.功能丰富,支持多种编程范式,例如面向对象.[1] D语言最初由Digital Mars公司就职的Walter Bright于2001年发布,意图改进C++语 ...
- luogu3384 /// 树链剖分+线段树模板
题目大意: https://www.luogu.org/problemnew/show/P3384 树链剖分的讲解 两个dfs() 修改 查询 很详细很好理解 https://www.cnblogs. ...
- 2018湘潭大学程序设计竞赛【B】
题目链接: https://www.nowcoder.com/acm/contest/105/B 题意: 给你一个字母矩阵,和测试组数,让你统计字符串的字符累计出现的次数,然后让你找出需要找的字符,这 ...
- 面试系列38 分库分表之后,id主键如何处理?
(1)数据库自增id 这个就是说你的系统里每次得到一个id,都是往一个库的一个表里插入一条没什么业务含义的数据,然后获取一个数据库自增的一个id.拿到这个id之后再往对应的分库分表里去写入. 这个方案 ...
- HTML_CSS使用
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" ...
- django 项目分析
项目要点 一.功能制定 1.用户功能 #.登陆 #.权限组功能 2.数据展示功能 #.列表展示 #.详细信息展示 #.图标展示 3.资源管理功能 #远程管理 #对远程服务器上的进程具有 #开启 #关闭 ...
- Android开发 Button的开发记录
Button置顶层效果取消 android:stateListAnimator="@null" 在代码里执行点击 mButton.performClick(); //点击 mBut ...