提交 49251825 编写于 作者: 杨若瑜's avatar 杨若瑜

发布至gitcode

上级 5125c54e
文件已添加
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
# frontend-blocks
# FrontendBlocks
一款强大的所见即所得前端页面设计器,不用写一行代码即可让设计师轻松设计出前端代码的利器,百分百还原设计稿不再是梦。
Powerful lowcode WYSIWYG frontend web page editor based on flex and normal layout. It can generate Html5 or Vue SFC.
\ No newline at end of file
#### 介绍 Introduce
本软件是一款强大的所见即所得前端页面设计器,是低代码开发领域的基础建设,生成的代码不依赖于任何框架,实测可以将前端布局工作的耗时减少80%以上,最关键的是,它实现了人人都可以写前端页面的梦想。
不用写一行代码即可让设计师、前端开发人员轻松设计出前端代码的利器,100%还原设计稿不再是梦。对于Uniapp开发非常友好,可以快速生成组件和页面。
本软件可以生成HTML5代码、按2x生成手机端VUE代码、按1x生成电脑端VUE代码,也可以直接生成JSON串,供其他开发工具(包括自主研发的)进行二次加工。
本软件采用Apache 2.0协议开源,可以免费商用。如有任何问题欢迎反馈,让我们共同建设好该开源项目。突出代码贡献者将会把名字写入README.md中,欢迎贡献。
This software is a powerful WYSIWYG front-end page designer, which is the basic construction in the field of low code development. The generated code does not depend on any framework. The actual measurement can reduce the time consumption of front-end layout work by more than 80%. The most important thing is that it realizes the dream that everyone can write front-end pages.
Designers and front-end developers can easily design front-end code without writing a line of code. It is no longer a dream to 100% restore the design draft. It is very friendly to uniapp development and can generate components and pages quickly.
This software can generate HTML5 code, generate mobile phone Vue code by 2x, generate computer Vue code by 1X, or directly generate JSON string for secondary processing by other development tools (including self-developed).
This software is open source with Apache 2.0 licence and can be used for free in business. If you have any questions, please feedback. Let's build this open source project together. Outstanding code contributors will write their names in readme.md, welcome to contribute.
![alt](./doc/preview1.png)
![alt](./doc/preview2.png)
![alt](./doc/preview3.png)
![alt](./doc/preview4.png)
在线演示地址:
Online presentation address:
http://39.104.17.92/frontendBlocks/
#### 软件架构 Architecture
使用VUE2.0开发,开箱即用。所生成的代码采用Flex布局和流式布局,具备良好的自适应性,特殊需求下也可以支持绝对定位布局、固钉布局。
对于Uniapp移动端开发者而言,可以直接生成rpx单位的样式。
对于图片,建议自建图床进行维护,如果有图片素材希望随着设计稿而维护,可以上网搜索“图片转BASE64编码”,把编码当做图片地址粘过去也是一样的(生成的文件会较大)。
Developed with vue2.0, ready to use. The generated code adopts flex layout and streaming layout with adaptability. Sometimes in your project, it can also support absolute positioning layout and fixed layout.
For uniapp mobile developers, rpx unit can be directly generated instead of px.
For pictures, it's recommended to build a picture cloud server. If you need picture embed in your design file, you can search the Internet for "picture to Base64 code", and paste the code as the picture address (the generated file will be large).
#### 安装教程 How to Install
1. npm install
2. npm run serve
3. 用浏览器打开(Open this url in Chrome) http://localhost:9000/frontendBlocks/
#### 独立部署教程 How to Deploy
1. npm run build
2. 把dist目录中的文件拷贝到服务器上,部署目录为frontendBlocks即可(copy files in 'dist' directory to your sever, deploy in apache/nginx and folder called 'frontendBlocks')
#### 使用说明 How to Use
1. 左侧图层面板可以点击“添加”按钮添加子元素
2. 设计时高度和设计时内距用来方便观察父子元素之间的关系,
3. 左侧面板的“在内部插入”功能是来源于public下的tools.json,其中填写的blocks节点源于“代码生成”菜单下的“生成可二次加工的JSON串”
4. 中间面板可以调整自适应宽度,如果是设计手机端,建议使用375px
5. 右侧面板当点选某个块时可以调整其属性,高级面板里可以设置元素类型的表达,支持表达成input
6. 文件可以新建、保存、读取,可以跨页签复制粘贴(复用设计稿的利器)
7. 当前的设计会被自动保存,待下次打开时可继续上次的设计稿继续设计,非常的人性化
8. 点击块上的小加号,可以很方便的追加新的块
1. On the left layer panel, click "add" to add sub element in target
2. The design-time height and the design-time internal distance are used to facilitate the observation of the relationship between parent and child elements,
3. The "InsertInto" tab of the left panel is derived from 'public/tools.json', where the filled blocks node is from the "generate reusable JSON string" under the "code generation" menu
4. The middle panel can adjust the adaptive width. If the mobile phone is designed, it is recommended to use 375px
5. On the right panel, when a block is selected, its attributes can be adjusted. In the advanced panel, the type of element can be set, and supported generate as 'input'
6. Your frontend design can be created, saved and read, and can be copied and pasted across tabs (very useful for reusing design drafts)
7. The current design will be automatically saved. When you open in browser at next time, you can continue the design of the previous design draft. It is user-friendly.
8. Click the small plus button on the block to easily add a new block
(English version needs someone to contribute i18n module or function)
#### 疑难解答 Troubleshoot
1. 为什么我不能撤销?
与传统设计软件不同,本软件产生的是实实在在的代码,因此增加撤销功能会增加前端运行时压力,考虑再三,决定不设计撤销功能。
2. 布局用不明白怎么办?
建议使用者先了解一下Flex布局。如果没有Flex布局基础可以先从这几步开始:设置父容器的“子元素排列”->设置子元素的宽度和高度->如果子元素的宽度和高度是百分比,则需要设置父容器的宽度和高度->给子元素设置一些可视化的效果,比如背景颜色、文本内容等等
3. 元素排列太紧密了,点不上怎么办?
可以把“设计时高度”和“设计时内距”拉大一点点,这样就能看到元素了,这两个设置不会影响输出效果。而且强烈建议在左侧“图层”面板中进行元素的点选,这样会更加精确
4. 为什么我设置了宽度或高度但实际上不是按我设置的值显示?而且为什么当我添加元素多起来的时候,所有元素都挤在一起了?
这是因为Flex布局下元素呈弹性效果,如果要保持一个元素固定宽度或高度,另一个元素自适应,请在固定宽高的元素上勾选“布局”->“弹性”->“钢化”。如果要实现一个可滚动的列表区,请将父元素勾选“布局”->“弹性”->“关闭弹性”
5. 有的元素里面子元素过多,导致页面出现滚动条或者影响了其他元素的布局怎么办?
可以尝试设置“高级”->“子元素溢出设置”->“滚动条”,只要不是“未设置”,都会触发BFC机制,成为独立渲染区域,这样就不会出现溢出或影响其他元素布局的情况了。
6. 我的设计稿崩溃导致FrontendBlocks打不开,怎么办?
尽管我极力避免软件出现崩溃,但事实上软件操作环境复杂,可能出现无法预料的BUG。可以按下F12,在应用(Application)->存储(Storage)->本地存储空间(Local Storage)中,把tempSave删掉,重新刷新界面即可。如果不涉密的话,可以将tempSave中的值复制出来提一个Issue,社区会尽快解决这个BUG。
1. Why can't I undo it?
Unlike traditional design software, this software produces real code in memory, so adding undo will increase the pressure on front-end runtime.
2. What can I do with the layout?
It is recommended that users first understand the Flex layout. If you don't have a Flex layout knowledge, you can start with these steps: Set the "child element arrangement" of the parent container - > Set the width and height of the child element - > If the width and height of the child element are a percentage, you need to set the width and height of the parent container - > Set some visual effects for the child element, such as background color, text content, and so on.
3. The elements are so tightly arranged that cannot click accurately?
You can increase the Design-Time Height and Design-Time Interval a little so that you can see the elements, which do not affect the output. It is also strongly recommended that elements be selected in the Layers panel on the left for more precision
4. Why did I set the width or height but not actually display it at the value I set? And why are all the elements crowded together when I add more?
This is because elements under the Flex layout have an elastic effect. To keep one element fixed width or height and the other element adaptive, check Layout -> Elasticity -> Tempering on elements with fixed width and height. To achieve a scrollable list area, check the parent element Layout -> Elasticity -> Turn Elasticity off
5. What if there are too many sub-elements in some elements that cause scrollbars on the page or affect the layout of other elements?
You can try setting Advanced-> Subelement Overflow Settings-> Scrollbar, which triggers the BFC mechanism to become a stand-alone rendering area as long as it is not Set-Up, so that no overflow or impact on the layout of other elements will occur.
6. What can I do when my design crash prevents FrontendBlocks from opening?
Despite my best efforts to avoid software crashes, the reality is that the software is operating in a complex environment with unexpected BUGs. You can press F12 to delete tempSave and refresh the interface in Application->Storage->Local Storage. If not, you can copy the values in tempSave to raise a question and the community will resolve the BUG as soon as possible.
#### 二次开发说明 Secondary Development
1. “在内部插入”功能的扩展是在public/tools.json中完成,手动编写可以参照第4条。
2. 想要增加新的属性,可以修改src/components/layout/PropertiesPanel.vue文件,data里的defaultForm是默认值,每一次更新都会用它来初始化,当前编辑的元素样式都会存在在propForm对象里。fromBlock方法会将选中元素的样式进行解析,回显到界面上。而toBlock方法则会将填写好的属性赋予给选中元素,使之生效。(如果你需要和业务系统相结合,可以在这里加点东西,比如接口地址、数据来源之类的)
3. 生成代码的功能都放在了src/components/layout/ToolsBar.vue里,想生成其他语言的(比如Android的XML、React、甚至Winform,可以根据逻辑自行实现)
4. 每一个元素所对应的Object里,properties代表了附加属性,id代表全局唯一编码,style代表该元素的样式,blocks代表该元素的子元素集合,domType是元素的类型(默认是div,即使表达成view也是写成div),text是指元素内部的文字
5. 上下文在src/components/layout/WorkSpace.vue里,它是充血的,可以扩展方法,而且几乎在整个项目的任何地方都能找到它的引用,其中的createNewBlock定义了创建一个新元素的初始属性。
6. 总的来说,实现逻辑就是:模板创建->用户自定义属性->上下文连锁更新->Block.vue预览表达->ToolsBar.vue生成代码->CodePreview.vue预览并下载代码
1. The extension of the "insert inside" function is completed in public/tools.json. Please refer to Article 4 for manual writing.
2. To add new properties, you can modify src/components/layout/PropertiesPanel.vue file, the defaultform in the data is the default value. It will be used to initialize each update. The currently edited element style will exist in the propform object. The fromblock method will parse the style of the selected element and echo it to the interface. The toblock method will assign the filled attributes to the selected element to make it effective. (if you need to integrate with the business system, you can add something here, such as interface address, data source, etc.)
3. The function of generating code is placed in src/components/layout/ToolsBar.vue, you want to generate other languages (such as Android XML, react, and even WinForm, which can be implemented according to logic)
4. In the object corresponding to each element, properties represents the additional attribute, ID represents the globally unique code, style represents the style of the element, blocks represents the collection of sub elements of the element, domtype is the type of the element (default is div, even if expressed as view, it is also written as DIV), and text refers to the text inside the element
5. The context is in src/components/layout/WorkSpace.vue, it is congested and can be extended. Its reference can be found almost anywhere in the whole project. The createnewblock defines the initial attribute of creating a new element.
6. In general, the implementation logic is: template creation - > user-defined attributes - > context chain update - > block.vue preview expression - > toolbar.vue generate code - > codepreview.vue preview and download code
#### 快捷键 Shortcut Keys
1. Ctrl+C 复制选中的块 Copy selected block
2. Ctrl+V 在所选中块的内部进行追加粘贴 Paste copied block in selected block
3. Delete 删除选中的块 Delete selected block
#### 参与贡献 Contributors
1. 黑龙江省瑜美科技发展有限公司 杨若瑜 2020年 创建 1.0
Heilongjiang Yumeisoft Co., Ltd Ruoyu Yang in 2020 ver 1.0
#### 其他 Other
1. 我的博客:yangruoyu.blog.csdn.net
My Blog: yangruoyu.blog.csdn.net
module.exports = {
presets: ["@vue/cli-plugin-babel/preset"]
};
此差异已折叠。
{
"name": "frontend-blocks",
"version": "3.6.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build"
},
"dependencies": {
"axios": "^0.27.2",
"core-js": "^3.8.3",
"element-ui": "^2.15.9",
"vue": "^2.6.14",
"vue-awesome": "^4.5.0",
"vue-draggable-resizable": "^2.3.0",
"vue-router": "^2.8.1",
"vuex": "^2.5.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"less": "^4.0.0",
"less-loader": "^8.0.0",
"vue-template-compiler": "^2.6.14"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}
文件已添加
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>FrontendBlocks前端低代码可视化布局器 - Author:杨若瑜</title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
此差异已折叠。
文件已添加
<template>
<div id="app">
<router-view />
</div>
</template>
<script>
export default {
data(){
return {}
}
}
</script>
<style lang="less">
#app{
width: 100%;
height: 100%;
padding: 0px;
margin: 0px;
display: flex;
flex-direction: column;
box-sizing: border-box;
}
</style>
\ No newline at end of file
<template>
<component :is="blockConfig.domType?blockConfig.domType:'div'" :blockid="blockConfig.id" :class="['block',context.viewMode=='preview'?'preview':null]"
@click="setCurrentBlock($event)" :style="autoStyle"
v-bind="properties" :value="blockConfig.text"
>
<!-- 遮罩层 -->
<div v-if="context.viewMode=='edit'" v-show="blockConfig.id==context.currentBlock" class="block-mask"></div>
<!-- 添加按钮开始 -->
<div v-if="context.viewMode=='edit'" v-show="blockConfig.id==context.currentBlock&&blockConfig.id!='root'"
class="add-btn top" @click="addBlocks('top')">
<img src="../../asset/images/icon-add.png">
</div>
<div v-if="context.viewMode=='edit'" v-show="blockConfig.id==context.currentBlock&&blockConfig.id!='root'"
class="add-btn bottom" @click="addBlocks('bottom')">
<img src="../../asset/images/icon-add.png">
</div>
<div v-if="context.viewMode=='edit'" v-show="blockConfig.id==context.currentBlock&&blockConfig.id!='root'"
class="add-btn left" @click="addBlocks('left')">
<img src="../../asset/images/icon-add.png">
</div>
<div v-if="context.viewMode=='edit'" v-show="blockConfig.id==context.currentBlock&&blockConfig.id!='root'"
class="add-btn right" @click="addBlocks('right')">
<img src="../../asset/images/icon-add.png">
</div>
<div v-if="context.viewMode=='edit'" v-show="blockConfig.id==context.currentBlock" class="add-btn middle"
@click="addBlocks('middle')">
<img src="../../asset/images/icon-add.png">
</div>
<!-- 添加按钮结束 -->
<!-- 文本内容 -->
{{blockConfig.text}}
<!-- 子元素 -->
<Block v-if="blockConfig" v-for="(item,index) in blockConfig.blocks" v-bind:key="item.id" :blockConfig="item"
:context="context">
</Block>
</component>
</template>
<script>
export default {
data: () => ({
dragContext: {
x: 0,
y: 0,
l: 0,
t: 0,
isDown: true
}
}),
props: [
// 元素
"blockConfig",
// 全局上下文
"context"
],
methods: {
forceLostInputFocuse() {
if (document.activeElement.tagName == 'INPUT') {
var input = document.activeElement
input.blur()
}
},
/**
* 把自己设置为当前元素
* @param {Object} e
*/
setCurrentBlock(e) {
this.forceLostInputFocuse();
this.context.currentBlock = this.blockConfig.id
e.stopPropagation()
},
/**
* 添加元素
* @param {Object} position
*/
addBlocks(position) {
if (position == 'top') {
this.context.addBefore(this.blockConfig)
}
if (position == 'bottom') {
this.context.addAfter(this.blockConfig)
}
if (position == 'middle') {
this.context.addMiddle(this.blockConfig)
}
if (position == 'left') {
this.context.addLeft(this.blockConfig)
}
if (position == 'right') {
this.context.addRight(this.blockConfig)
}
},
dragMouseDown(e) {
e.preventDefault()
e.stopPropagation()
if (this.blockConfig.id == "root") {
return;
}
if (!(this.blockConfig.style.position == 'absolute' || this.blockConfig.style.position == 'fixed')) {
return;
}
let page = this;
//获取x坐标和y坐标
this.dragContext.x = e.clientX;
this.dragContext.y = e.clientY;
//获取左部和顶部的偏移量
function getRealOffset(el, pid = 'root') {
let left = el.offsetLeft;
let top = el.offsetTop;
if (el.offsetParent && el.offsetParent.attributes && el.offsetParent.attributes.blockid && el
.offsetParent.attributes.blockid.value !== pid) {
let p = getRealOffset(el.offsetParent, pid);
left += p.left;
top += p.top;
}
return {
left,
top
};
}
let realOffset = getRealOffset(e.target);
this.dragContext.l = realOffset.left;
this.dragContext.t = realOffset.top;
if (this.blockConfig.style.left && this.blockConfig.style.top) {
let left = parseInt(this.blockConfig.style.left.replace('px', ''))
let top = parseInt(this.blockConfig.style.top.replace('px', ''))
}
//开关打开
this.dragContext.isDown = true;
//鼠标移动
window.onmousemove = function(e) {
e.preventDefault()
e.stopPropagation()
if (page.dragContext.isDown == false) {
return;
}
//获取x和y
var nx = e.clientX;
var ny = e.clientY;
//计算移动后的左偏移量和顶部的偏移量
var nl = nx - (page.dragContext.x - page.dragContext.l);
var nt = ny - (page.dragContext.y - page.dragContext.t);
// var nl = nx - (page.dragContext.x);
// var nt = ny - (page.dragContext.y);
page.blockConfig.style.left = nl + 'px';
page.blockConfig.style.top = nt + 'px';
page.$forceUpdate();
}
},
dragMouseUp() {
//开关关闭
this.dragContext.isDown = false;
window.onmousemove = null
}
},
computed: {
properties(){
if(this.blockConfig.properties){
return this.blockConfig.properties
}else{
return {}
}
},
autoStyle() {
let {
context,
blockConfig,
} = this
let newPadding = {
top: 0,
right: 0,
bottom: 0,
left: 0
}
let newMargin = {
top: 0,
right: 0,
bottom: 0,
left: 0
}
if (blockConfig.style.padding) {
let words = blockConfig.style.padding.split(' ')
if (words.length == 1) {
newPadding.top = parseInt(words[0].replace('px', ''))
newPadding.right = parseInt(words[0].replace('px', ''))
newPadding.bottom = parseInt(words[0].replace('px', ''))
newPadding.left = parseInt(words[0].replace('px', ''))
} else {
newPadding.top = parseInt(words[0].replace('px', ''))
newPadding.right = parseInt(words[1].replace('px', ''))
newPadding.bottom = parseInt(words[2].replace('px', ''))
newPadding.left = parseInt(words[3].replace('px', ''))
}
}
if (blockConfig.style.margin) {
let words = blockConfig.style.margin.split(' ')
if (words.length == 1) {
newMargin.top = parseInt(words[0].replace('px', ''))
newMargin.right = parseInt(words[0].replace('px', ''))
newMargin.bottom = parseInt(words[0].replace('px', ''))
newMargin.left = parseInt(words[0].replace('px', ''))
} else {
newMargin.top = parseInt(words[0].replace('px', ''))
newMargin.right = parseInt(words[1].replace('px', ''))
newMargin.bottom = parseInt(words[2].replace('px', ''))
newMargin.left = parseInt(words[3].replace('px', ''))
}
}
if (!(context.viewMode == 'preview' || blockConfig.id == 'root')) {
newPadding.top += context.editPadding
newPadding.bottom += context.editPadding
newPadding.left += context.editPadding
newPadding.right += context.editPadding
newMargin.top += context.editPadding
newMargin.bottom += context.editPadding
}
let newStyle = {
...blockConfig.style,
...(context.viewMode == 'preview' || blockConfig.id == 'root' ? {} : {
margin: `${newMargin.top}px ${newMargin.right}px ${newMargin.bottom}px ${newMargin.left}px`,
padding: `${newPadding.top}px ${newPadding.right}px ${newPadding.bottom}px ${newPadding.left}px`,
minHeight: `${context.editMinHeight+(blockConfig.style.minHeight?parseInt(blockConfig.style.minHeight.replace('px','')):0)}px`
})
}
return newStyle;
}
},
components: {}
};
</script>
<style lang="less">
.block {
box-sizing: border-box;
border: #00A0E9 1px dashed;
min-height: 50px;
position: relative;
user-select: none;
.block-mask {
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
background-color: #baf8ff;
border: #0066CC 2px solid;
// background-image: linear-gradient(#baf8ff, #e9f7f1);
opacity: 0.5;
z-index: 2;
pointer-events: none;
}
&.preview {
min-height: unset;
border: unset;
padding: unset;
margin-top: unset;
margin-bottom: unset;
// background-color: red;
}
}
.add-btn {
position: absolute;
transition: opacity 2s;
z-index: 2;
font-size: 0px;
line-height: 0px;
opacity: 1;
width: 0px;
&.top {
left: calc(50% - 8px);
top: 0px;
}
&.bottom {
left: calc(50% - 8px);
bottom: 0px;
}
&.left {
left: 0px;
top: calc(50% - 8px);
}
&.right {
right: 16px;
top: calc(50% - 8px);
}
&.middle {
top: calc(50% - 8px);
left: calc(50% - 8px);
}
&:active {
opacity: 0.8;
}
img {
width: 16px;
height: 16px;
opacity: 0.7;
}
}
</style>
<template>
<el-dialog title="代码预览" :visible.sync="visible" width="70%" @close="onClose">
<div class="root">
<div class="codePanel">
<textarea class="codeArea">{{code}}</textarea>
</div>
<div class="buttonBar">
<div class="closeBtn" @click="onClose">关闭</div>
<div class="saveBtn" @click="onSave">保存</div>
</div>
</div>
</el-dialog>
</template>
<script>
export default {
data: () => ({
}),
props: ['visible','onClose','onSave','code'],
methods: {
},
};
</script>
<style scoped>
.root {
box-sizing: border-box;
font-weight: normal;
font-style: normal;
text-align: left;
display: flex;
position: relative;
width: 100%;
height: 600px;
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
}
.codePanel {
box-sizing: border-box;
font-weight: normal;
font-style: normal;
text-align: left;
display: flex;
position: relative;
width: 100%;
height: 100%;
padding: 20px;
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
}
.codeArea {
box-sizing: border-box;
font-weight: normal;
font-style: normal;
text-align: left;
display: flex;
position: relative;
width: 100%;
height: 100%;
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
overflow: hidden scroll;
}
.buttonBar {
box-sizing: border-box;
border: 1px solid rgba(207, 207, 207, 1);
border-top: 1px solid rgba(207, 207, 207, 1);
border-bottom: none;
border-left: none;
border-right: none;
font-weight: normal;
font-style: normal;
text-align: left;
display: flex;
position: relative;
width: 100%;
padding: 10px;
flex-direction: row;
justify-content: space-between;
align-items: flex-end;
}
.closeBtn {
box-sizing: border-box;
background-color: rgba(159, 159, 159, 1);
border-radius: 100px;
font-weight: normal;
font-style: normal;
color: rgba(255, 255, 255, 1);
text-align: left;
display: flex;
position: relative;
width: 100%;
padding: 10px;
margin: 0px 20px 0px 0px;
flex-direction: column;
justify-content: center;
align-items: center;
}
.saveBtn {
box-sizing: border-box;
background-color: rgba(68, 125, 56, 1);
border-radius: 100px;
font-weight: normal;
font-style: normal;
color: rgba(255, 255, 255, 1);
text-align: left;
display: flex;
position: relative;
width: 100%;
padding: 10px;
flex-direction: column;
justify-content: center;
align-items: center;
}
</style>
\ No newline at end of file
<template>
<div class="root">
<div class="block-4">
<div class="block-2"></div>
<div class="block-3" @click="hideMe">关闭</div>
</div>
<div class="block-5">
使用说明:
<div class="block-6">1、快捷键</div>
<div class="block-7">Ctrl+C 复制</div>
<div class="block-8">Ctrl+V 粘贴</div>
<div class="block-9">Ctrl+S 保存</div>
<div class="block-10">2、如何使用</div>
<div class="block-11">1)如果是APP项目,先设置自适应为750px,其余的项目按需要来。如果点不上背景的盒子,则可以使用缩放功能把盒子撑开。</div>
<div class="block-13">2)点击加号可以在上下左右或者内部增加新的盒子,如果父容器排列方向与要操作的方向不同,则会自动增加一个新的盒子用来改变排列方向。</div>
<div class="block-14">3)设计好之后可以保存,也可以直接生成HTML、VUE手机端或电脑端</div>
</div>
</div>
</template>
<script>
export default {
data: () => ({}),
props:['onHide'],
methods:{
hideMe(){
console.log("关闭自己");
if(this.onHide){
this.onHide();
}
}
}
}
</script>
<style>
html,body,#root,uni-page-body,page #app{
padding:0;
margin:0;
width:100%;
height:100%;
}
.root{
display:flex;
box-sizing:border-box;
font-weight:normal;
font-style:normal;
text-align:left;
position:relative;
width:100%;
flex-direction:column;
justify-content:flex-start;
align-items:flex-start;
}
.block-4{
display:flex;
flex-direction:row;
justify-content:flex-start;
align-items:flex-start;
width:100%;
}
.block-2{
display:flex;
box-sizing:border-box;
background-color:rgba(54, 120, 145, 1);
font-weight:normal;
font-style:normal;
text-align:left;
position:relative;
height:50px;
width:100%;
flex-direction:column;
justify-content:flex-start;
align-items:flex-start;
}
.block-3{
display:flex;
box-sizing:border-box;
background-color:rgba(225, 56, 56, 1);
font-weight:normal;
font-style:normal;
text-align:left;
position:relative;
width:50px;
height:50px;
flex-direction:column;
justify-content:center;
align-items:center;
color: white;
}
.block-5{
display:flex;
box-sizing:border-box;
font-weight:normal;
font-style:normal;
line-height:25px;
text-align:left;
position:relative;
width:100%;
padding:20px;
flex-direction:column;
justify-content:flex-start;
align-items:flex-start;
}
.block-6{
display:flex;
box-sizing:border-box;
font-weight:normal;
font-style:normal;
text-align:left;
position:relative;
width:100%;
flex-direction:column;
justify-content:flex-start;
align-items:flex-start;
}
.block-7{
display:flex;
box-sizing:border-box;
font-weight:normal;
font-style:normal;
text-align:left;
position:relative;
width:100%;
flex-direction:column;
justify-content:flex-start;
align-items:flex-start;
}
.block-8{
display:flex;
box-sizing:border-box;
font-weight:normal;
font-style:normal;
text-align:left;
position:relative;
width:100%;
flex-direction:column;
justify-content:flex-start;
align-items:flex-start;
}
.block-9{
display:flex;
box-sizing:border-box;
font-weight:normal;
font-style:normal;
text-align:left;
position:relative;
width:100%;
flex-direction:column;
justify-content:flex-start;
align-items:flex-start;
}
.block-10{
display:flex;
box-sizing:border-box;
font-weight:normal;
font-style:normal;
text-align:left;
position:relative;
width:100%;
flex-direction:column;
justify-content:flex-start;
align-items:flex-start;
}
.block-11{
display:flex;
box-sizing:border-box;
font-weight:normal;
font-style:normal;
text-align:left;
position:relative;
width:100%;
flex-direction:column;
justify-content:flex-start;
align-items:flex-start;
}
.block-13{
display:flex;
box-sizing:border-box;
font-weight:normal;
font-style:normal;
text-align:left;
position:relative;
width:100%;
flex-direction:column;
justify-content:flex-start;
align-items:flex-start;
}
.block-14{
display:flex;
box-sizing:border-box;
font-weight:normal;
font-style:normal;
text-align:left;
position:relative;
width:100%;
flex-direction:column;
justify-content:flex-start;
align-items:flex-start;
}
</style>
\ No newline at end of file
<template>
<div class="LayoutPanel">
<el-tabs v-model="activeName">
<el-tab-pane label="图层" name="first">
<el-tree
ref="eltree"
:data="[context.tabBlocks[context.currentTab]]"
:props="{
label: 'id',
children: 'blocks',
}"
node-key="id"
:default-expand-all="false"
:expand-on-click-node="false"
@node-click="nodeClick"
:allow-drag="allowDrag"
:render-content="renderContent"
:default-expanded-keys="expandedList"
:highlight-current="true"
@node-drop="dropSuccess"
@node-expand="nodeExpand"
@node-collapse="nodeCollapse"
draggable
></el-tree>
</el-tab-pane>
<el-tab-pane
v-if="context.currentBlock != null"
label="在内部插入"
name="second"
>
<div v-for="(item, i) in tools" v-bind:key="i">
<el-button
type="primary"
style="width: 100%"
@click="insertTool(item)"
>
{{ item.name }}
</el-button>
</div>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import axios from "axios";
export default {
data: () => ({
activeName: "first",
currentBlock: null,
tools: [],
expandedList: ["root"],
}),
props: ["context", "currentId"],
methods: {
nodeClick(node) {
this.context.currentBlock = node.id;
},
allowDrag(draggingNode) {
return draggingNode.data.id != "root";
},
addChild(data) {
this.context.addMiddle(this.context.findBlockById(data.id));
},
renderContent(h, { node, data, store }) {
return (
<div style="display:flex;flex-direction:row;line-height:30px;justify-content: space-between; width:100%">
<div>{node.label}</div>
<a
href="#"
on-click={() => this.addChild(data)}
style="color:#86cef1;font-size:12px;"
>
添加
</a>
</div>
);
},
insertTool(item) {
let context = this.context;
let toPaste = context.findBlockById(context.currentBlock);
let toCopy = JSON.parse(JSON.stringify(item.blocks));
context.autoRenameNewBlocks(toCopy);
toPaste.blocks.push(toCopy);
},
nodeExpand(data) {
let parent = data;
while (parent.id != "root") {
parent = this.context.findParent(parent);
if (this.expandedList.indexOf(parent.id) == -1) {
this.expandedList.push(parent.id);
}
console.log(parent.id);
}
this.$nextTick(() => {
this.$refs.eltree.setCurrentKey(data.id);
});
console.log(this.expandedList);
},
nodeCollapse(data) {
if (this.expandedList.indexOf(data.id) != -1) {
this.expandedList.splice(this.expandedList.indexOf(data.id), 1);
let parent = this.context.findBlockById(data.id);
if (parent.blocks && parent.blocks.length > 0) {
for (let childIdx in parent.blocks) {
this.nodeCollapse(parent.blocks[childIdx]);
}
}
}
},
dropSuccess() {},
},
watch: {
currentId: {
handler(id) {
let block = this.context.findBlockById(id);
this.currentBlock = block;
this.nodeExpand(block);
},
},
},
created() {
axios.get("./tools.json").then((res) => {
this.tools = res.data.tools;
});
},
};
</script>
<style lang="less">
.LayoutPanel {
width: 320px;
flex-shrink: 0;
height: 100%;
background-color: #ffffff;
border-right: #f3f3f3 1px solid;
border-left: #999999 1px solid;
padding: 10px;
box-sizing: border-box;
user-select: none;
overflow-y: scroll;
&::-webkit-scrollbar {
width: 0 !important;
}
h4 {
line-height: unset;
margin: 0;
margin-left: 3px;
margin-bottom: 10px;
padding-top: 10px;
margin-top: 10px;
padding-bottom: 10px;
text-align: left;
padding-left: 10px;
border-bottom: #cccccc 1px solid;
background-color: white;
}
}
.propTable {
font-size: 12px;
line-height: 12px;
input,
select {
line-height: 12px;
width: 100%;
box-sizing: border-box;
user-select: all;
}
td {
border: none;
}
tr {
border-bottom: #e6e6e6 1px solid;
}
&.firstColumnFixed > tr > td:first-child {
vertical-align: middle;
word-break: keep-all;
white-space: nowrap;
width: 60px;
}
}
.el-tabs__header {
margin: 0;
}
.el-checkbox__label {
color: #9aa5c0;
font-size: 12px;
}
</style>
此差异已折叠。
<template>
<div class="dark-2 tb">
<el-menu default-active="1" class="el-menu-demo" mode="horizontal"
background-color="#181128" text-color="#fff" active-text-color="#ffd04b">
<el-menu-item index="1">FrontendBlocks</el-menu-item>
<el-submenu index="2">
<template slot="title">文件</template>
<el-menu-item index="2-1" @click="createTab()">新建</el-menu-item>
<el-menu-item index="2-2" @click="genJson()">保存</el-menu-item>
<el-menu-item index="2-3" @click="fromFile()">读取</el-menu-item>
<el-menu-item index="2-4" @click="clearCache()">重置工作区</el-menu-item>
</el-submenu>
<el-submenu index="3">
<template slot="title">代码生成</template>
<el-menu-item index="3-1" @click="showHtml()">生成HTML(100%宽 1x输出 px单位)</el-menu-item>
<el-menu-item index="3-2" @click="showVUE()">生成VUE手机端(375宽 2x输出 rpx单位)</el-menu-item>
<el-menu-item index="3-3" @click="showVUEForPC()">生成VUE电脑端(100%宽 1x输出 px单位)</el-menu-item>
<el-menu-item index="3-4" @click="showJSON()">生成可二次加工的JSON串</el-menu-item>
</el-submenu>
<el-submenu index="4">
<template slot="title">关于</template>
<a href="https://yangruoyu.blog.csdn.net/" target="_blank"><el-menu-item index="4-1">访问 杨若瑜的CSDN博客</el-menu-item></a>
<el-menu-item>本产品采用Apache Licence授权许可协议</el-menu-item>
<el-menu-item>版本号:3.6 (2022)</el-menu-item>
</el-submenu>
</el-menu>
<CodePreview :visible="dialogVisible" :onClose="onDilaogClose" :onSave="onSave" :code="code" />
</div>
</template>
<script>
export default {
data: () => ({
dialogVisible:false,
activeIndex2: '1',
onSave:null,
code:null
}),
props: ['context'],
methods: {
handleSelect(key, keyPath) {
console.log(key, keyPath);
},
createTab() {
this.context.createTab();
},
clearCache(){
this.$confirm('你所有暂存的状态都将被永久删除,是否重置工作区?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
localStorage.clear();
location.reload();
});
},
showHtml(){
if (this.context) {
let rootBlock = this.context.tabBlocks[this.context.currentTab]
let result = this.parse2HTML(rootBlock)
console.log(result)
this.onSave=this.genHtml
this.code=result.html
this.dialogVisible=true
} else {
this.$message("还未完成加载")
}
},
showVUE(){
if (this.context) {
let rootBlock = this.context.tabBlocks[this.context.currentTab]
let result = this.parse2VUE(rootBlock)
this.code=result.html
this.onSave=this.genVUE
this.dialogVisible=true
} else {
this.$message("还未完成加载")
}
},
showVUEForPC(){
if (this.context) {
let rootBlock = this.context.tabBlocks[this.context.currentTab]
let result = this.parse2VUE4PC(rootBlock, 1, 'div')
this.code=result.html
this.onSave=this.genVUEForPC
this.dialogVisible=true
} else {
this.$message("还未完成加载")
}
},
showJSON(){
if (this.context) {
let rootBlock = this.context.tabBlocks[this.context.currentTab]
let result=JSON.parse(JSON.stringify(rootBlock))
this.code=JSON.stringify(result,null,4)
this.onSave=this.genJson
this.dialogVisible=true
} else {
this.$message("还未完成加载")
}
},
onDilaogClose(){
this.dialogVisible=false
},
genHtml() {
function download(filename, text) {
var element = document.createElement('a');
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
element.setAttribute('download', filename);
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
if (this.context) {
let rootBlock = this.context.tabBlocks[this.context.currentTab]
let result = this.parse2HTML(rootBlock)
download("page.html", result.html)
console.log(result.html);
} else {
this.$message("还未完成加载")
}
},
genVUE() {
function download(filename, text) {
var element = document.createElement('a');
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
element.setAttribute('download', filename);
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
if (this.context) {
let rootBlock = this.context.tabBlocks[this.context.currentTab]
let result = this.parse2VUE(rootBlock)
download("page.vue", result.html)
// console.log(result.html);
} else {
this.$message("还未完成加载")
}
},
genVUEForPC() {
function download(filename, text) {
var element = document.createElement('a');
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
element.setAttribute('download', filename);
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
if (this.context) {
let rootBlock = this.context.tabBlocks[this.context.currentTab]
let result = this.parse2VUE4PC(rootBlock, 1, 'div')
download("page.vue", result.html)
// console.log(result.html);
} else {
this.$message("还未完成加载")
}
},
genJson() {
this.context.save();
},
fromFile() {
let page = this;
let fileName = '';
let openFile = new Promise((resolve, reject) => {
let input = document.createElement('input');
input.value = '选择文件';
input.type = 'file';
input.onchange = event => {
let file = event.target.files[0];
let file_reader = new FileReader();
file_reader.onload = () => {
let fc = file_reader.result;
resolve(fc); // 返回文件文本内容到Promise
};
file_reader.readAsText(file, 'UTF-8');
fileName = file.name
};
input.click();
});
openFile.then((content) => {
try {
let saved = JSON.parse(content)
page.context.createTab(fileName)
page.context.tabBlocks[page.context.currentTab] = saved
page.$message('加载成功')
} catch (e) {
page.$message('文件格式错误,无法解析')
}
})
},
/**
* 生成HTML代码
*/
parse2HTML(block, level = 0) {
let result = {
html: '',
css: ''
}
let html = ''
let css = ''
// 样式
if (block.id == 'root') {
css += `\t\thtml,body{\n`
css += `\t\t\tpadding:0;\n`
css += `\t\t\tmargin:0;\n`
css += `\t\t\twidth:100%;\n`
css += `\t\t\theight:100%;\n`
css += `\t\t}\n`
css += `\t\tbody{\n`
} else {
css += `\t\t.${block.id}{\n`
}
for (let key in block.style) {
let value = block.style[key]
let realCss = key.replace(/([A-Z])/g, "-$1").toLowerCase()
css += `\t\t\t${realCss}:${value};\n`
}
css += `\t\t}`
result.css += css;
// 头部
if (block.id == 'root') {
html +=
`<!DOCTYPE html>\n<html lang="zh">\n<head>\n\t<meta charset="UTF-8">\n\t<meta http-equiv="X-UA-Compatible" content="IE=edge">\n\t<meta name="viewport" content="width=device-width, initial-scale=1.0">\n\t<title></title>\n</head>\n<body>`
} else {
for (let i = 0; i < level; i++) {
html += '\t'
}
html += `<${block.domType?block.domType:'div'} class="${block.id}"${block.domType&&block.domType=='input'&&block.text?' value="'+block.text+'"':''}>`
}
// 内容
if (block.text&&(!block.domType||block.domType!='input')) {
if (block.blocks && block.blocks.length >= 1) {
html += '\n'
for (let i = 0; i < level + 1; i++) {
html += '\t'
}
}
html += block.text
}
if (block.blocks && block.blocks.length >= 1) {
html += '\n'
for (let i = 0; i < block.blocks.length; i++) {
let child = block.blocks[i]
let childHtml = this.parse2HTML(child, level + 1)
html += childHtml.html
// 顺带处理CSS
result.css += '\n'
result.css += childHtml.css
if (i != block.blocks.length - 1) {
html += '\n'
}
}
html += '\n'
for (let i = 0; i < level; i++) {
html += '\t'
}
}
// 尾部
if (block.id == 'root') {
html += `\t<style>\n`
html += result.css + '\n'
html += `\t</style>\n`
html += `</body>\n</html>`
} else {
html += `</${block.domType?block.domType:'div'}>`
}
result.html = html
return result
},
/**
* 生成VUE代码
*/
parse2VUE(block, level = 1, defaultTag = 'view') {
let result = {
html: '',
css: ''
}
let html = ''
let css = ''
// 样式
if (block.id == 'root') {
css += `\thtml,body,#root,uni-page-body,page #app{\n`
css += `\t\tpadding:0;\n`
css += `\t\tmargin:0;\n`
css += `\t\twidth:100%;\n`
css += `\t\theight:100%;\n`
css += `\t}\n`
css += `\t.${block.id}{\n`
} else {
css += `\t.${block.id}{\n`
}
for (let key in block.style) {
let value = new String(block.style[key])
// 处理RPX换算问题
if (value) {
let allCells = value.split(' ')
for (let i = 0; i < allCells.length; i++) {
if (allCells[i].match('[0-9]+px')) {
allCells[i] = (parseInt(allCells[i].replace('px', '')) * 2) + 'rpx'
}
}
value = allCells.join(' ')
}
let realCss = key.replace(/([A-Z])/g, "-$1").toLowerCase()
css += `\t\t${realCss}:${value};\n`
}
css += `\t}`
result.css += css;
// 头部
if (block.id == 'root') {
html += `<template>\n\t<${defaultTag} class="${block.id}">`
} else {
for (let i = 0; i < level; i++) {
html += '\t'
}
html += `<${block.domType?(block.domType=='div'?defaultTag:block.domType):defaultTag} class="${block.id}"${block.domType&&block.domType=='input'&&block.text?' value="'+block.text+'"':''}>`
}
// 内容
if (block.text&&(!block.domType||block.domType!='input')) {
if (block.blocks && block.blocks.length >= 1) {
html += '\n'
for (let i = 0; i < level + 1; i++) {
html += '\t'
}
}
html += block.text
}
if (block.blocks && block.blocks.length >= 1) {
html += '\n'
for (let i = 0; i < block.blocks.length; i++) {
let child = block.blocks[i]
let childHtml = this.parse2VUE(child, level + 1, defaultTag)
html += childHtml.html
// 顺带处理CSS
result.css += '\n'
result.css += childHtml.css
if (i != block.blocks.length - 1) {
html += '\n'
}
}
html += '\n'
for (let i = 0; i < level; i++) {
html += '\t'
}
}
// 尾部
if (block.id == 'root') {
html += `</${defaultTag}>\n`
html += "</template>\n\n"
html += "<script>\n"
html += "\texport default {\n"
html += "\t\tdata: () => ({}),\n"
html += "\t\tprops:[],\n"
html += "\t\tmethods:{}\n"
html += "\t}\n"
html += "</" + "script>\n\n"
html += "<style scoped>\n"
html += result.css + "\n"
html += "</style>"
} else {
html += `</${block.domType?(block.domType=='div'?defaultTag:block.domType):defaultTag}>`
}
result.html = html
return result
},
parse2VUE4PC(block, level = 1, defaultTag = 'view') {
let result = {
html: '',
css: ''
}
let html = ''
let css = ''
// 样式
if (block.id == 'root') {
css += `\thtml,body,#root,uni-page-body,page #app{\n`
css += `\t\tpadding:0;\n`
css += `\t\tmargin:0;\n`
css += `\t\twidth:100%;\n`
css += `\t\theight:100%;\n`
css += `\t}\n`
css += `\t.${block.id}{\n`
} else {
css += `\t.${block.id}{\n`
}
for (let key in block.style) {
let value = block.style[key]
let realCss = key.replace(/([A-Z])/g, "-$1").toLowerCase()
css += `\t\t\t${realCss}:${value};\n`
}
css += `\t}`
result.css += css;
// 头部
if (block.id == 'root') {
html += `<template>\n\t<${defaultTag} class="${block.id}">`
} else {
for (let i = 0; i < level; i++) {
html += '\t'
}
html += `<${block.domType?(block.domType=='div'?defaultTag:block.domType):defaultTag} class="${block.id}"${block.domType&&block.domType=='input'&&block.text?' value="'+block.text+'"':''}>`
}
// 内容
if (block.text&&(!block.domType||block.domType!='input')) {
if (block.blocks && block.blocks.length >= 1) {
html += '\n'
for (let i = 0; i < level + 1; i++) {
html += '\t'
}
}
html += block.text
}
if (block.blocks && block.blocks.length >= 1) {
html += '\n'
for (let i = 0; i < block.blocks.length; i++) {
let child = block.blocks[i]
let childHtml = this.parse2VUE4PC(child, level + 1, defaultTag)
html += childHtml.html
// 顺带处理CSS
result.css += '\n'
result.css += childHtml.css
if (i != block.blocks.length - 1) {
html += '\n'
}
}
html += '\n'
for (let i = 0; i < level; i++) {
html += '\t'
}
}
// 尾部
if (block.id == 'root') {
html += `</${defaultTag}>\n`
html += "</template>\n\n"
html += "<script>\n"
html += "\texport default {\n"
html += "\t\tdata: () => ({}),\n"
html += "\t\tprops:[],\n"
html += "\t\tmethods:{}\n"
html += "\t}\n"
html += "</" + "script>\n\n"
html += "<style scoped>\n"
html += result.css + "\n"
html += "</style>"
} else {
html += `</${block.domType?(block.domType=='div'?defaultTag:block.domType):defaultTag}>`
}
result.html = html
return result
}
}
}
</script>
<style lang="less">
.tb {
width: 100%;
flex-shrink: 0;
box-sizing: border-box;
line-height: 30px;
}
.logo {
font-size: 20px;
display: inline-block;
margin-right: 20px;
}
</style>
<template>
<div class="row hfull scroll-y" v-if="context.tabBlocks[context.currentTab]&&context.tabs.length>0">
<!-- 图层面板 -->
<LayoutPanel :context="context" :currentId="context.currentBlock"></LayoutPanel>
<div class="ws">
<!-- 页签 -->
<el-tabs v-model="context.currentTab" type="card" @tab-click="changeTab" style="user-select: none;" closable
@tab-remove="removeTab" :before-leave="beforeChangeTab">
<el-tab-pane :label="item.name" :name="item.code" v-for="(item,index) in context.tabs"
v-bind:key="'tab'+index"></el-tab-pane>
</el-tabs>
<!-- 设计区 -->
<div style="z-index: 5;">
<table>
<tr>
<td>
<button type="button" @click="preview()">预览</button>
<button type="button" @click="edit()">设计</button>
{{this.context.viewMode=='edit'?'设计模式':null}}
{{this.context.viewMode!='edit'?'预览模式':null}}
</td>
<td width="40px" style="vertical-align:middle;">自适应</td>
<td width="50px" style="vertical-align:middle;">
{{viewportType==0?'375px':null}}
{{viewportType==1?'750px':null}}
{{viewportType==2?'960px':null}}
{{viewportType==3?'100%':null}}
</td>
<td width="20%">
<el-slider v-model="viewportType" :min="0" :max="3" :step="1" :show-tooltip="false"
show-stops input-size="mini"></el-slider>
</td>
<td width="80px" style="vertical-align:middle;">设计时高度</td>
<td width="20%">
<el-slider v-model="context.editMinHeight" :show-tooltip="false" :min="0" :max="100"
input-size="mini"></el-slider>
</td>
<td width="80px" style="vertical-align:middle;">设计时内距</td>
<td width="20%">
<el-slider v-model="context.editPadding" :show-tooltip="false" :min="0" :max="20"
input-size="mini"></el-slider>
</td>
</tr>
</table>
</div>
<div class="blocks-panel">
<Block v-if="showRootBlock" class="root-block" :blockConfig="context.tabBlocks[context.currentTab]"
:context="context" :style="{width:viewportWidth}"></Block>
</div>
</div>
<!-- 属性面板 -->
<PropertiesPanel :context="context" :currentId="context.currentBlock"></PropertiesPanel>
</div>
</template>
<script>
// 声明上下文
let context = {
currentBlock: null,
// 展现方式
// edit - 编辑模式
// preview - 预览模式
viewMode: "edit",
editPadding: 0,
editMinHeight: 0,
tabIndex: 0,
currentTab: 'blank',
tabs: [],
tabBlocks: {},
/**
* 找当前元素
* @param {Object} id
*/
findBlockById(id, parent) {
if (!parent) {
parent = context.tabBlocks[context.currentTab]
}
if (parent.id == id) {
return parent;
}
for (let i in parent.blocks) {
let child = parent.blocks[i]
let result = context.findBlockById(id, child)
if (result) {
return result;
}
}
return null;
},
autoRenameNewBlocks(parent) {
if (!parent) {
throw '必须要传入对象'
}
parent.id = "block-" + (++context.tabBlocks[context.currentTab].indexCounter)
for (let i in parent.blocks) {
let child = parent.blocks[i]
context.autoRenameNewBlocks(child)
}
},
/**
* 获取当前的元素
*/
getCurrentBlock() {
return context.findBlockById(context.currentBlock)
},
/**
* 删除制定ID的元素及其子元素
* @param {Object} id ID
*/
deleteBlockById(id) {
if (!id) {
return;
}
let block = context.findBlockById(id)
if (!block) {
return;
}
let parent = context.findParent(block)
let index = context.indexOf(block, parent)
if (block.id == "root") {
context.workspace.$message('画板中至少留有一个区块');
return
}
parent.blocks.splice(index, 1)
},
/**
* 删除当前元素
*/
deleteCurrentBlock() {
context.deleteBlockById(context.currentBlock)
},
/**
* 找父级元素
* @param {Object} block 元素
* @param {Object} parent 父级元素
*/
findParent(block, parent) {
if (!parent) {
parent = context.tabBlocks[context.currentTab]
}
for (let i in parent.blocks) {
let child = parent.blocks[i]
if (child.id == block.id) {
return parent
}
}
for (let i in parent.blocks) {
let child = parent.blocks[i]
let result = context.findParent(block, child)
if (result) {
return result;
}
}
return null;
},
/**
* 查找元素在父容器中的位置
* @param {Object} block 元素
* @param {Object} parent 父容器
*/
indexOf(block, parent) {
if (!parent) {
parent = context.tabBlocks[context.currentTab]
}
for (let i in parent.blocks) {
let child = parent.blocks[i]
if (child.id == block.id) {
return i;
}
}
return -1;
},
/**
* 创建新的元素
*/
createNewBlock() {
let result = {
style: {
display: "flex",
flexDirection: "column",
justifyContent: "flex-start",
alignItems: "flex-start",
width: "100%"
},
blocks: [],
domType:"div"
}
if (context.tabBlocks[context.currentTab]) {
result.id = "block-" + (++context.tabBlocks[context.currentTab].indexCounter)
} else {
result.indexCounter = 1
result.id = "root"
delete result.display
delete result.flexDirection
delete result.justifyContent
delete result.alignItems
}
return result;
},
/**
* 在上方加入
* @param {Object} block 元素
*/
addBefore(block) {
let parent = context.findParent(block)
let index = context.indexOf(block, parent)
let newBlock = context.createNewBlock()
if (parent.style.flexDirection == "row") {
let newParent = context.createNewBlock()
newParent.style.flexDirection = "column"
parent.blocks.splice(index, 1)
parent.blocks.splice(index, 0, newParent)
newParent.blocks.push(newBlock);
newParent.blocks.push(block);
} else {
parent.blocks.splice(index, 0, newBlock)
}
},
/**
* 在下方加入
* @param {Object} block 元素
*/
addAfter(block) {
let parent = context.findParent(block)
let index = context.indexOf(block, parent)
let newBlock = context.createNewBlock()
if (parent.style.flexDirection == "row") {
let newParent = context.createNewBlock()
newParent.style.flexDirection = "column"
parent.blocks.splice(index, 1)
parent.blocks.splice(index, 0, newParent)
newParent.blocks.push(block);
newParent.blocks.push(newBlock);
} else {
parent.blocks.splice(index + 1, 0, newBlock)
}
},
/**
* 在内部加入
* @param {Object} block 元素
*/
addMiddle(block) {
let newBlock = context.createNewBlock()
block.blocks.push(newBlock)
},
/**
* 在左侧插入
* @param {Object} block 元素
*/
addLeft(block) {
let parent = context.findParent(block)
let index = context.indexOf(block, parent)
let newBlock = context.createNewBlock()
if (parent.style.flexDirection == "column") {
let newParent = context.createNewBlock()
newParent.style.flexDirection = "row"
parent.blocks.splice(index, 1)
parent.blocks.splice(index, 0, newParent)
newParent.blocks.push(newBlock);
newParent.blocks.push(block);
} else {
parent.blocks.splice(index, 0, newBlock)
}
},
/**
* 在右侧插入
* @param {Object} block 元素
*/
addRight(block) {
let parent = context.findParent(block)
let index = context.indexOf(block, parent)
let newBlock = context.createNewBlock()
if (parent.style.flexDirection == "column") {
let newParent = context.createNewBlock()
newParent.style.flexDirection = "row"
parent.blocks.splice(index, 1)
parent.blocks.splice(index, 0, newParent)
newParent.blocks.push(block);
newParent.blocks.push(newBlock);
} else {
parent.blocks.splice(index + 1, 0, newBlock)
}
context.currentBlock = newBlock.id
},
/**
* 获取上下文
*/
getContext() {
return context;
},
/**
* 保存
*/
save() {
function download(filename, text) {
var element = document.createElement('a');
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
element.setAttribute('download', filename);
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
let rootBlock = context.tabBlocks[context.currentTab]
context.workspace.$message('已生成文件')
download("page.blocks", JSON.stringify(rootBlock))
},
/** 临时保存 */
tempSave(){
localStorage.setItem("tempSave",JSON.stringify({
tabBlocks:context.tabBlocks,
currentTab:context.currentTab,
tabs:context.tabs,
tabIndex:context.tabIndex
}))
},
/** 读取临时保存 */
tempLoad(){
try{
let temp = JSON.parse(localStorage.getItem("tempSave"))
if(temp){
for(let i in temp){
context[i]=temp[i]
}
}
}catch(e){
}
},
/**
* 创建页签
*/
createTab(title) {
context.tabIndex++
context.tabs = [
...context.tabs,
{
code: `tab${context.tabIndex}`,
name: title ? title : `未命名${context.tabIndex}`
}
]
context.currentTab = context.tabs[context.tabs.length - 1].code
context.tabBlocks[context.currentTab] = {
...context.createNewBlock()
};
context.currentBlock = null;
},
/**
* 删除页签
* @param {Object} code
*/
removeTab(code) {
for (let i = 0; i < context.tabs.length; i++) {
if (context.tabs[i].code == code) {
let tab = context.tabs[i]
// 当前页处理
if (tab.code == context.currentTab) {
if (i == context.tabs.length - 1 && context.tabs.length > 1) {
// 关闭最后一页,自动打开前一页
context.currentTab = context.tabs[i - 1].code
} else if (i == 0 && context.tabs.length > 1) {
// 关闭第一页,自动打开第二页
context.currentTab = context.tabs[1].code
} else if (context.tabs.length > 1) {
// 关闭当前页,打开后一页
context.currentTab = context.tabs[i + 1].code
}
}
context.tabs.splice(i, 1)
break;
}
}
context.tempSave()
}
}
export default {
data: () => ({
name: null,
context,
showRootBlock: true,
viewportType: 3,
lastSelectBlock: {}
}),
props: ['initFinished'],
methods: {
/**
* 进入编辑模式
*/
edit() {
this.context.viewMode = 'edit'
},
/**
* 进入预览模式
*/
preview() {
this.context.viewMode = 'preview'
},
beforeChangeTab(newName, oldName) {
console.log(newName, oldName);
this.lastSelectBlock[oldName] = context.currentBlock
context.currentBlock = null
if (this.lastSelectBlock[newName]) {
this.$nextTick(() => {
context.currentBlock = this.lastSelectBlock[newName]
})
} else {
context.currentBlock = null
}
},
/**
* 切换页签
*/
changeTab(e) {
},
removeTab(name) {
this.$confirm('在保存前此操作将永久清理该稿件, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.context.removeTab(name);
});
}
},
mounted() {
let page = this;
context.workspace = this;
document.onkeydown = function(e) {
// 如果当前标签是输入框,则不进行操作
if (document.activeElement.tagName == 'INPUT') {
return
}
// 获取键位
var keyCode = e.keyCode || e.which || e.charCode;
var ctrlKey = e.ctrlKey || e.metaKey;
// 删除操作,即按下delete键
if (keyCode == 46) {
page.context.deleteCurrentBlock()
e.preventDefault();
return false;
}
// 保存操作,即ctrl+s
if (ctrlKey && keyCode == 83) {
context.save();
e.preventDefault();
return false;
}
// 复制操作
if (ctrlKey && keyCode == 67) {
if (context.currentBlock) {
let toCopy = context.findBlockById(context.currentBlock)
if (toCopy) {
context.toCopy = JSON.stringify(toCopy);
console.log("已复制", context.toCopy);
e.preventDefault();
return false;
}
}
}
// 粘贴操作
if (ctrlKey && keyCode == 86) {
if (context.toCopy && context.currentBlock) {
let toPaste = context.findBlockById(context.currentBlock)
if (toPaste) {
let toCopy = JSON.parse(context.toCopy)
console.log("粘贴", toCopy);
context.autoRenameNewBlocks(toCopy)
console.log("粘贴到", toPaste);
toPaste.blocks.push(toCopy)
e.preventDefault();
return false;
}
}
}
// 剪切操作
if (ctrlKey && keyCode == 88) {
if (context.currentBlock) {
let toCopy = context.findBlockById(context.currentBlock)
if (toCopy) {
context.toCopy = JSON.stringify(toCopy);
console.log("已复制", context.toCopy);
}
}
page.context.deleteCurrentBlock()
e.preventDefault();
return false;
}
// 还原操作
if (ctrlKey && keyCode == 89) {
}
// 撤销操作
if (ctrlKey && keyCode == 90) {
}
}
if (this.initFinished) {
this.initFinished(context)
}
},
computed: {
viewportWidth() {
if (this.viewportType == 0) {
return '375px'
}
if (this.viewportType == 1) {
return '750px'
}
if (this.viewportType == 2) {
return '960px'
}
if (this.viewportType == 3) {
return '100%'
}
}
},
components: {}
};
</script>
<style lang="less">
.scroll-y {
overflow-y: scroll;
}
.scroll-y::-webkit-scrollbar {
width: 0px;
}
.ws {
width: 100%;
height: 100%;
box-sizing: border-box;
font-size: initial;
font-family: initial;
color: initial;
display: flex;
flex-direction: column;
overflow: hidden;
}
.ws::-webkit-scrollbar {
width: 0px;
}
.root-block {
overflow-y: scroll;
transform: translate(0, 0);
height: 100%;
// width: 100%;
background-color: white;
}
.root-block::-webkit-scrollbar {
width: 0px;
}
.row {
display: flex;
flex-direction: row;
}
.column {
display: flex;
flex-direction: column;
}
.blocks-panel {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
box-sizing: border-box;
padding: 5px;
overflow: hidden;
background-color: #333333;
}
</style>
@import './less/theme.less';
@import './less/layout.less';
@import './less/animation.less';
html,
body {
height: 100%;
width: 100%;
padding: 0px;
margin: 0px;
display: flex;
font-size: 14px;
font-family: 'Arial','Microsoft YaHei', '微软雅黑', 'Microsoft JhengHei', '华文细黑', 'STHeiti', 'MingLiu';
}
a {
text-decoration: none;
}
/**
* 布局方式
*/
.rel{
position: relative;
}
.abs{
position: absolute;
}
/**
* 预设宽度
*/
.gen-w(960);
.gen-w(@n, @i: 0) when (@i =< @n) {
.w@{i} {
width: @i + 0px;
flex-shrink: 0;
}
.gen-w(@n, (@i + 5));
}
.wfull {
width: 100%;
}
/**
* 预设高度
*/
.gen-h(960);
.gen-h(@n, @i: 0) when (@i =< @n) {
.h@{i} {
height: @i + 0px;
flex-shrink: 0;
}
.gen-h(@n, (@i + 5));
}
.hfull {
height: 100%;
}
/**
* 点钟方位表示法
*/
.align-0 {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.align-1,
.align-2 {
display: flex;
flex-direction: column;
align-items: flex-end;
justify-content: flex-start;
}
.align-3 {
display: flex;
flex-direction: column;
align-items: flex-end;
justify-content: center;
}
.align-4,
.align-5 {
display: flex;
flex-direction: column;
align-items: flex-end;
justify-content: flex-end;
}
.align-6 {
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-end;
}
.align-7,
.align-8 {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-end;
}
.align-9 {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
}
.align-10,
.align-11 {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
}
.align-12 {
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
}
/** 内边距 */
.gen-p(30);
.gen-p(@n, @i: 0) when (@i =< @n) {
.p@{i} {
padding: @i + 0px;
box-sizing: border-box;
}
.gen-p(@n, (@i + 5));
}
.p0-top{
padding-top: 0px;
box-sizing: border-box;
}
.p0-bottom{
padding-bottom: 0px;
box-sizing: border-box;
}
.p0-left{
padding-left: 0px;
box-sizing: border-box;
}
.p0-right{
padding-right: 0px;
box-sizing: border-box;
}
/** 外边距 */
.gen-m(30);
.gen-m(@n, @i: 0) when (@i =< @n) {
.m@{i} {
margin: @i + 5px;
}
.gen-m(@n, (@i + 5));
}
.gen-m-outer(30);
.gen-m-outer(@n, @i: 0) when (@i =< @n) {
.m@{i}-bottom{
margin-bottom: @i + 5px;
}
.m@{i}-left{
margin-left: @i + 5px;
}
.m@{i}-top{
margin-top: @i + 5px;
}
.m@{i}-right{
margin-right: @i + 5px;
}
.gen-m-outer(@n, (@i + 5));
}
/** 字体大小 */
.gen-f(36);
.gen-f(@n, @i: 0) when (@i =< @n) {
.f@{i} {
font-size: @i + 0px;
line-height: @i + 5px;
}
.gen-f(@n, (@i + 2));
}
\ No newline at end of file
@keyframes fadeInFrames {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fadeLeftFrames {
from {
opacity: 0;
padding-right: 100px;
}
to {
opacity: 1;
}
}
.fadeIn {
animation-name: fadeInFrames;
animation-duration: 0.5s;
}
.fadeLeft{
animation-name: fadeLeftFrames;
animation-duration: 0.5s;
}
/**
* 栅格布局-行
*/
.gen-col(24);
.gen-col(@n, @i: 1) when (@i =< @n) {
.col-@{i} {
width: (@i * 100% / @n);
box-sizing: border-box;
display: inline-block;
}
.col-fixed-@{i} {
width: (@i * 100% / @n) !important;
box-sizing: border-box;
display: inline-block;
}
@media(max-width:1200px){
.col-@{i} {
width: 50%;
display: inline-block;
}
}
@media(max-width:600px){
.col-@{i} {
width: 100%;
display: inline-block;
}
}
.gen-col(@n, (@i + 1));
}
/**
* 栅格布局-列
*/
.gen-row(24);
.gen-row(@n, @i: 1) when (@i =< @n) {
.row-@{i} {
height: (@i * 100% / @n);
display: inline-block;
}
.gen-row(@n, (@i + 1));
}
.is-mobile{
display: none !important;
}
@media(max-width:600px){
.not-mobile{
display: none !important;
}
.is-mobile{
display: unset !important;
}
}
此差异已折叠。
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import './global.less';
import 'vue-awesome/icons'
import Icon from 'vue-awesome/components/Icon'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import VueDraggableResizable from 'vue-draggable-resizable'
import 'vue-draggable-resizable/dist/VueDraggableResizable.css'
Vue.component('v-icon', Icon);
Vue.use(ElementUI);
Vue.component('vue-draggable-resizable', VueDraggableResizable)
// 输入正整数的指令
Vue.directive('enterInteger', {
inserted: function (el) {
el.addEventListener("keypress",function(e){
e = e || window.event;
let charcode = typeof e.charCode == 'number' ? e.charCode : e.keyCode;
let re = /\d/;
if(!re.test(String.fromCharCode(charcode)) && charcode > 9 && !e.ctrlKey){
if(e.preventDefault){
e.preventDefault();
}else{
e.returnValue = false;
}
}
});
}
});
// 引入组件
const requireComponent = require.context(
// 其组件目录的相对路径
"./components",
// 是否查询其子目录
true,
// 匹配基础组件文件名的正则表达式
/[A-Za-z0-9_]+\.(vue|js)$/
);
requireComponent.keys().forEach((fileName) => {
// 获取组件配置
const componentConfig = requireComponent(fileName);
// 获取组件的 PascalCase 命名
const componentName = fileName
.split("/")
.pop()
.replace(/\.\w+$/, "");
// 全局注册组件
Vue.component(
componentName,
// 如果这个组件选项是通过 `export default` 导出的,
// 那么就会优先使用 `.default`,
// 否则回退到使用模块的根。
componentConfig.default || componentConfig
);
});
Vue.config.productionTip = false;
new Vue({
router,
store,
render: (h) => h(App),
}).$mount("#app");
import Vue from "vue";
import VueRouter from "vue-router";
Vue.use(VueRouter);
const routes = [{
name:"index",
path: "/",
component: () => import("../views/Home.vue"),
}
];
const router = new VueRouter({
routes,
});
export default router;
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: {},
mutations: {},
actions: {},
modules: {},
});
<!-- 首页 -->
<template>
<div class="home hfull">
<ToolsBar :context="context"></ToolsBar>
<WorkSpace ref="workspace" :initFinished="workSpaceInitFinished"></WorkSpace>
</div>
</template>
<script>
export default {
data: () => ({
context:null
}),
methods: {
workSpaceInitFinished(context){
this.context=context
this.context.tempLoad()
if(this.context.tabs.length==0){
// 创建一个页签
this.context.createTab();
}
}
},
created(){
window.isCloseHint = true;
//初始化关闭
window.addEventListener("beforeunload", function(e) {
if (window.isCloseHint) {
var confirmationMessage = "记得保存!确定要离开吗?";
(e || window.event).returnValue = confirmationMessage; // 兼容 Gecko + IE
return confirmationMessage; // 兼容 Gecko + Webkit, Safari, Chrome
}
});
},
components: {}
};
</script>
<style lang="less">
.home{
display: flex;
flex-direction: column;
}
</style>
module.exports = {
publicPath: '/frontendBlocks',
devServer:{
port:9000
}
}
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册