no message

main
LX 2023-12-05 13:23:01 +08:00
commit f98960fb94
562 changed files with 70738 additions and 0 deletions

6
.editorconfig 100644
View File

@ -0,0 +1,6 @@
root = true
[*]
end_of_line = lf
tab_width = 2
indent_style = space

28
.env 100644
View File

@ -0,0 +1,28 @@
# 网站标题
VITE_APP_TITLE='OpenDataV'
# 端口
VITE_APP_PORT=8890
#VITE_APP_PROXY 和 VITE_APP_BASE_URL 的关系
#如果 VITE_APP_BASE_URL 配置为空,所有请求就通过 VITE_APP_PROXY 代理转发
#如果 VITE_APP_BASE_URL 配置不为空VITE_APP_PROXY 的配置就不生效
# 代理配置,必须在一行中
VITE_APP_PROXY=[["/api","http://localhost:9900"]]
# 后端地址
VITE_APP_BASE_URL=''
# 路由模式hash 或者 history
VITE_ROUTER_MODE='hash'
# Token 键
VITE_AUTH_TOKEN='Token'
# Token 的存储TokenStorage
VITE_TOKEN_STORAGE='localStorage'
# 报警通知延时,单位:毫秒
VITE_ALERT_DELAY=300
VITE_MOCK=true

27
.env.development 100644
View File

@ -0,0 +1,27 @@
# 网站标题
VITE_APP_TITLE='OpenDataV'
# 端口
VITE_APP_PORT=8890
# 代理配置(开发使用),必须在一行中
VITE_APP_PROXY=[["/api","http://localhost:8000"]]
# 后端地址
VITE_APP_BASE_URL='/'
# 路由模式hash 或者 history
VITE_ROUTER_MODE='hash'
# Token 键
VITE_AUTH_TOKEN='Token'
# Token 的存储TokenStorage
VITE_TOKEN_STORAGE='localStorage'
# 网站是否不需要权限认证: NoAuth Auth
VITE_APP_NO_AUTH='NoAuth'
VITE_BACKIMAGE=''
VITE_MOCK=true

27
.env.production 100644
View File

@ -0,0 +1,27 @@
# 网站标题
VITE_APP_TITLE='OpenDataV'
# 端口
VITE_APP_PORT=8890
# 代理配置,必须在一行中
VITE_APP_PROXY=[["/api","http://localhost:9900"]]
# 后端地址
VITE_APP_BASE_URL='/'
# 路由模式hash 或者 history
VITE_ROUTER_MODE='hash'
# Token 键
VITE_AUTH_TOKEN='Token'
# Token 的存储TokenStorage
VITE_TOKEN_STORAGE='localStorage'
# 网站是否不需要权限认证: NoAuth Auth
VITE_APP_NO_AUTH='NoAuth'
VITE_BACKGROUND=''
VITE_MOCK=true

5
.eslintignore 100644
View File

@ -0,0 +1,5 @@
/src/assets/*
**/node_modules/**
.vscode
.idea
public

117
.eslintrc.js 100644
View File

@ -0,0 +1,117 @@
const { defineConfig } = require('eslint-define-config')
module.exports = defineConfig?.({
root: true,
env: {
browser: true,
node: true,
es6: true,
'vue/setup-compiler-macros': true
},
parser: 'vue-eslint-parser',
parserOptions: {
parser: '@typescript-eslint/parser',
ecmaVersion: 2020,
sourceType: 'module',
jsxPragma: 'React',
ecmaFeatures: {
jsx: true
}
},
extends: [
'plugin:vue/vue3-recommended',
'plugin:@typescript-eslint/recommended',
'prettier',
'plugin:prettier/recommended'
// 'plugin:jest/recommended'
],
plugins: ['prettier', 'import', '@typescript-eslint', 'spellcheck', 'simple-import-sort'],
rules: {
'vue/script-setup-uses-vars': 'error',
'@typescript-eslint/ban-ts-ignore': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/no-empty-function': 'off',
'vue/custom-event-name-casing': 'off',
'no-use-before-define': 'off',
'vue/no-v-html': 'off',
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/ban-types': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_'
}
],
'no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_'
}
],
'space-before-function-paren': 'off',
'vue/one-component-per-file': 'off',
'vue/html-closing-bracket-newline': 'off',
'vue/max-attributes-per-line': 'off',
'vue/multiline-html-element-content-newline': 'off',
'vue/singleline-html-element-content-newline': 'off',
'vue/attribute-hyphenation': 'off',
'vue/require-default-prop': 'off',
'vue/require-explicit-emits': 'off',
'vue/html-self-closing': [
'error',
{
html: {
void: 'always',
normal: 'never',
component: 'always'
},
svg: 'always',
math: 'always'
}
],
'vue/multi-word-component-names': 'off',
'sonarjs/no-duplicate-string': 'off',
'sonarjs/cognitive-complexity': 'off',
'sonarjs/no-identical-functions': 'off',
'@typescript-eslint/no-this-alias': [
'error',
{
allowDestructuring: false, // Disallow `const { props, state } = this`; true by default
allowedNames: ['self'] // Allow `const self = this`; `[]` by default
}
],
/**
* 属性排序
*
* @see https://eslint.vuejs.org/rules/attributes-order.html#rule-details
*/
'vue/attributes-order': 1,
/**
* 仅仅导入/导出声明
*
* @see https://jkchao.github.io/typescript-book-chinese/new/typescript-3.8.html#%E4%BB%85%E4%BB%85%E5%AF%BC%E5%85%A5-%E5%AF%BC%E5%87%BA%E5%A3%B0%E6%98%8E
*/
'@typescript-eslint/consistent-type-imports': 2,
/**
* 强制使用一致的换行符风格
*
* @see https://cn.eslint.org/docs/rules/linebreak-style
*/
'linebreak-style': [2, 'unix'],
'simple-import-sort/imports': 'error',
'simple-import-sort/exports': 'error',
'import/first': 'error',
'import/newline-after-import': 'error',
'import/no-duplicates': 'error',
'vue/no-setup-props-destructure': 'off'
}
})

10
.gitignore vendored 100644
View File

@ -0,0 +1,10 @@
node_modules/
dist/
.idea/
node_modules
package-lock.json
components.d.ts
.DS_Store
dist
.idea

View File

@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
# Format and submit code according to lintstagedrc.js configuration
pnpm lint

2
.npmrc 100644
View File

@ -0,0 +1,2 @@
auto-install-peers=true
strict-peer-dependencies=false

8
.vscode/extensions.json vendored 100644
View File

@ -0,0 +1,8 @@
{
"recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"stylelint.vscode-stylelint",
"johnsoncodehk.volar"
]
}

16
.vscode/launch.json vendored 100644
View File

@ -0,0 +1,16 @@
{
// 使 IntelliSense
//
// 访: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "pwa-chrome",
"request": "launch",
"name": "Launch Chrome against localhost",
"url": "http://localhost:8080",
"webRoot": "${workspaceFolder}"
}
]
}

43
.vscode/settings.json vendored 100644
View File

@ -0,0 +1,43 @@
{
"css.validate": false,
"less.validate": false,
"sass.validate": false,
"editor.tabSize": 2,
"javascript.validate.enable": false,
"tailwindCSS.includeLanguages": {
"wxml": "html"
},
"prettier.trailingComma": "none",
"[vue]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"[css]": {
"editor.defaultFormatter": "stylelint.vscode-stylelint"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"search.exclude": {
"**/node_modules": true,
"**/*.log": true,
"**/*.log*": true,
"**/dist": true,
"**/.git": true,
"**/.gitignore": true,
"**/.svn": true,
"**/.DS_Store": true,
"**/.idea": true,
"**/.vscode": false,
"**/yarn.lock": true,
"**/tmp": true,
"out": true,
"dist": true,
"node_modules": true,
"yarn-error.log": true,
"**/.yarn": true
},
}

201
LICENSE 100644
View File

@ -0,0 +1,201 @@
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 2022 AnsGoo
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.

131
README.md 100644
View File

@ -0,0 +1,131 @@
![](./public/logo.png)
![](https://img.shields.io/github/license/AnsGoo/openDataV)
![](https://img.shields.io/github/stars/AnsGoo/openDataV)
![](https://img.shields.io/github/issues/AnsGoo/openDataV)
![](https://img.shields.io/github/forks/AnsGoo/openDataV)
## 简介
🎃OpenDataV 是一个纯前端的`拖拽式`、`可视化`、`低代码`数据可视化🌈开发平台,你可以用它自由的拼接成各种✨炫酷的大屏,同时支持用户方便的开发自己的组件并接入平台。
## 体验
🧙国外:[http://datav.byteportrait.com/](http://datav.byteportrait.com/)
🧙国内:[http://small_bud_star.gitee.io/opendatav](http://small_bud_star.gitee.io/opendatav)
🧙国内:[http://opendatav.xingxingzaixian.fun/](http://opendatav.xingxingzaixian.fun/) (已对接后端)
🧙文档: [https://datav.byteportrait.com/#/docs/quick-satrt/quick-start](https://datav.byteportrait.com/#/docs/quick-satrt/quick-start)
## 源码地址:
🍨github: [https://github.com/AnsGoo/openDataV](https://github.com/AnsGoo/openDataV)
🍨gitee: [https://gitee.com/small_bud_star/OpenDataV](https://gitee.com/small_bud_star/OpenDataV)
🍨后端: [https://gitee.com/small_bud_star/open-data-backend](https://gitee.com/small_bud_star/open-data-backend)
目前该项目在不断的完善中,🎉欢迎 issuer,🌹欢迎 start, 🎨欢迎 commit, 🚀欢迎 use...,💪欢迎一切技术交流活动
## 👁️预览
### 🤿 页面编辑
1. 组件添加
![screenshots2.gif](https://s2.loli.net/2022/10/31/nApiFm7PogI1dHS.gif)
2. 组件操作
![screenshots1.gif](https://s2.loli.net/2022/10/31/9lkiR15sVMLapIe.gif)
3. 组件数据配置
![screenshots.gif](https://s2.loli.net/2022/10/31/28lf6NK35EaY9wJ.gif)
### 🖇️ 接口管理
![API.png](https://s2.loli.net/2022/10/31/f1GuMTIp2rzEPQF.png)
## 💒功能
- 🎊 编辑器页面基本功能完成,包括编辑、预览、导入、导出、保存
- 🪄 图层的置顶、置底、上下移动、显示、隐藏、复制、剪切、粘贴
- 🖼️ 组件的缩放、旋转、拖动、复制、粘贴、组合、拆分、移除、自动对齐
- 🔮 支持用户操作记录的恢复、撤销功能
- 🧶 支持用户自定义组件
- 📔 支持组件的用户自定组件配置项
- 🏪 支持明暗主题切换
- 🧬 使用Monorepo模式进行组件和依赖管理
- 🧶 组件数据自定义接入
- 🔌 数据动态处理JS已完成
- 🖇️ 接口管理(示例数据、静态数据、HTTP接口数据已完成适配)
## 🎢技术点
本项目采用`Vue3` + `vite` + `TypeScript`开发,界面库使用`NaiveUI`使用面向对象方式封装了路由、请求、存储组件采用自动扫描注册、异步加载提升渲染速度使用IndexDB存储快照数据减少快照数据内存占用加快访问速度组件独立依赖解耦了组件和基础框架的依赖库方便后续独立开发组件。
目前仅开发了部分组件,后续还会继续完善。
## ⌛计划功能
- [ ] 组件管理
- [ ] 脚本管理Python待支持
- [ ] GraphQL、GRPC、WebScoket、MQTT、SocketIO多种数据接口适配
- [ ] 代码生成
- [ ] 动态文档
- [ ] 全局数据接入
## 💂开发
### 🧊开发环境
| 名称 | 版本 |
| ---- | ------- |
| node | 16.14.x |
| pnpm | 7.9.3 |
| vue | 3.2.37 |
🚥目前仅在 `Chrome``Microsoft Edge` 最新版浏览器测试过,其他浏览器未测试
### 🎮启动项目
```Bash
# 安装依赖
pnpm install
pnpm bootstrap
# 运行项目
pnpm dev
# 打包项目
pnpm build
```
## 🧑‍💻代码提交
git commit 信息请按照如下规范进行书写
- feat: 新功能
- fix: 修复 Bug
- docs: 文档修改
- perf: 性能优化
- revert: 版本回退
- ci: CICD 集成相关
- test: 添加测试代码
- refactor: 代码重构
- build: 影响项目构建或依赖修改
- style: 不影响程序逻辑的代码修改
- other: 不属于以上类型的其他类型(日常事务)
## ☎️联系方式
**技术交流,请加微信群**
<img src="./public/wechat.png" style="width:430px">
**更新动态请关注公众号**
![wechat](./screenshot/OfficialAccounts.jpg)
**如果群链接失效请加微信回复OpenDataV 拉群**
![](./screenshot/wechat.png)

128
build/toc.js 100644
View File

@ -0,0 +1,128 @@
// Process @[toc](|Title)
'use strict'
module.exports = function (md) {
const TOC_REGEXP = /\[toc\](?:\((?:\s+)?([^\)]+)(?:\s+)?\)?)?(?:\s+?)?$/im
let gstate
function toc(state, silent) {
while (state.src.indexOf('\n') >= 0 && state.src.indexOf('\n') < state.src.indexOf('[toc]')) {
if (state.tokens.slice(-1)[0].type === 'softbreak') {
state.src = state.src.split('\n').slice(1).join('\n')
state.pos = 0
}
}
let token
let match = TOC_REGEXP.exec(state.src)
if (!match) {
return false
}
match = match.filter(function (m) {
return m
})
if (match.length < 1) {
return false
}
if (silent) {
// don't run any pairs in validation mode
return false
}
token = state.push('toc_open', 'toc', 1)
token.markup = '[toc]'
token = state.push('toc_body', '', 0)
token = state.push('toc_close', 'toc', -1)
let offset = 0
let newline = state.src.indexOf('\n')
if (newline !== -1) {
offset = state.pos + newline
} else {
offset = state.pos + state.posMax + 1
}
state.pos = offset
return true
}
const makeSafe = function (label) {
return label
.replace(/[^\w\s]/gi, '')
.split(' ')
.join('_')
}
md.renderer.rules.heading_open = function (tokens, index) {
const level = tokens[index].tag
const label = tokens[index + 1]
if (label.type === 'inline') {
const anchor = makeSafe(label.content) + '_' + label.map[0]
return `<${level}><a id="${anchor}"></a>`
} else {
return '</h1>'
}
}
md.renderer.rules.toc_open = function (_tokens, _index) {
return ''
}
md.renderer.rules.toc_close = function (_tokens, _index) {
return ''
}
md.renderer.rules.toc_body = function (_tokens, _index) {
// Wanted to avoid linear search through tokens here,
// but this seems the only reliable way to identify headings
const headings = []
const gtokens = gstate.tokens
const size = gtokens.length
for (let i = 0; i < size; i++) {
if (gtokens[i].type !== 'heading_close') {
continue
}
const token = gtokens[i]
const heading = gtokens[i - 1]
if (heading.type === 'inline') {
headings.push({
level: +token.tag.substr(1, 1),
anchor: makeSafe(heading.content) + '_' + heading.map[0],
content: heading.content
})
}
}
let indent = 0
const list = headings.map(function (heading) {
let res = []
if (heading.level > indent) {
const ldiff = heading.level - indent
for (let i = 0; i < ldiff; i++) {
res.push('<ul>')
indent++
}
} else if (heading.level < indent) {
const ldiff = indent - heading.level
for (let i = 0; i < ldiff; i++) {
res.push('</ul>')
indent--
}
}
res.push(
`<li><a href="javascript:document.querySelector('#${heading.anchor}').scrollIntoView({behavior: 'smooth'})">${heading.content}</a></li>`
)
return res.join('')
})
return `<div class="toc-box">${list.join('') + new Array(indent + 1).join('</ul>')}</div>`
}
md.core.ruler.push('grab_state', function (state) {
gstate = state
})
md.inline.ruler.after('emphasis', 'toc', toc)
}

View File

@ -0,0 +1,47 @@
<template>
<n-layout-sider
class="right"
:width="180"
:native-scrollbar="false"
bordered
collapse-mode="width"
>
<SiderContent :menus="componentMenus" />
</n-layout-sider>
<n-layout-content class="content">
<n-scrollbar x-scrollable>
<RenderMD>
<ConfigProvider>
<RouterView />
</ConfigProvider>
</RenderMD>
</n-scrollbar>
</n-layout-content>
</template>
<script lang="ts" setup name="DocContent">
import { NLayoutContent, NLayoutSider, NScrollbar } from 'naive-ui'
import { computed } from 'vue'
import { useRoute } from 'vue-router'
import ConfigProvider from '@/components/provider/ConfigProvider.vue'
import docsRouters from '@/router/modules/docs'
import type { AppRouteRecordRaw } from '@/router/types'
import SiderContent from '../modules/components/SiderContent'
import type { MenuItem } from '../modules/components/SiderContent/type'
import RenderMD from '../RenderMD.vue'
import { getMenus } from './menus'
const route = useRoute()
const componentMenus = computed<Array<MenuItem>>(() => {
const matcheds = route.matched
const name = matcheds[1].name
const routers = docsRouters[0].children.filter((el) => el.name === name)
if (routers.length > 0 && routers[0].children && routers[0].children.length > 0) {
return getMenus(routers[0].children as Array<AppRouteRecordRaw>)
} else {
return []
}
})
</script>

View File

@ -0,0 +1,15 @@
<template>
<n-result
status="404"
title="文档正在编写中..."
description="star一下来为OpenDataV加速吧"
style="height: 100%"
>
<template #icon>
<img src="/errors/404.png" />
</template>
</n-result>
</template>
<script lang="ts" setup>
import { NResult } from 'naive-ui'
</script>

View File

@ -0,0 +1,3 @@
import Content from './Content.vue'
export default Content

View File

@ -0,0 +1,18 @@
import type { AppRouteRecordRaw } from '@/router/types'
import type { MenuItem } from '../modules/components/SiderContent'
export const getMenus = (routers: AppRouteRecordRaw[]): Array<MenuItem> => {
return routers.map((el) => {
const item = {
label: el.meta.title,
icon: el.meta.icon!,
key: el.name,
children: [] as Array<MenuItem>
}
if (el.children && el.children?.length > 0) {
item.children = getMenus(el.children || [])
}
return item
})
}

View File

@ -0,0 +1,80 @@
<template>
<n-card v-if="mode === 'debug'" :title="title" style="margin-bottom: 16px; height: 100%">
<n-tabs type="line" animated>
<n-tab-pane name="oasis" tab="效果" display-directive="show">
<Render />
</n-tab-pane>
<n-tab-pane name="attr" tab="属性" display-directive="show">
<OCodeEditor
v-model:value="form.propValue"
class="content"
mode="debug"
height="300px"
@update:value="propValueChange"
/>
</n-tab-pane>
<n-tab-pane name="style" tab="样式" display-directive="show">
<OCodeEditor
v-model:value="form.style"
class="content"
mode="debug"
height="300px"
@update:value="styleChange"
/>
</n-tab-pane>
</n-tabs>
</n-card>
<n-card v-else :title="title" style="margin-bottom: 16px; height: 100%">
<Render />
</n-card>
</template>
<script setup lang="ts">
import { NCard, NTabPane, NTabs } from 'naive-ui'
import type { CustomComponent } from 'open-data-v/base'
import { getComponentStyle, uuid } from 'open-data-v/designer/utils'
import type { ComponentOptions, ConcreteComponent } from 'vue'
import { computed, h, reactive } from 'vue'
const props = withDefaults(
defineProps<{
config: new (id?: string, name?: string) => CustomComponent
component: ConcreteComponent
propValue: Record<string, any>
style: Record<string, any>
title: string
mode?: 'view' | 'debug' | 'use'
}>(),
{
mode: 'debug'
}
)
const form = reactive<{
propValue: string
style: string
}>({
propValue: JSON.stringify(props.propValue, null, '\t'),
style: JSON.stringify(props.style, null, '\t')
})
const propValueChange = (value: string) => {
form.propValue = value
}
const styleChange = (style: string) => {
form.style = style
}
const Render = computed<ComponentOptions>(() => {
const componentInstance = new props.config(uuid())
const style = JSON.parse(form.style)
const propValue = JSON.parse(form.propValue)
componentInstance.setStyleValue({ style: style })
componentInstance.setPropValue({ propValue: propValue })
return h(props.component, {
id: componentInstance.id,
key: componentInstance.id,
class: 'component',
style: getComponentStyle(componentInstance),
component: componentInstance
})
})
</script>

71
docs/RenderMD.vue 100644
View File

@ -0,0 +1,71 @@
<template>
<n-card>
<slot></slot>
</n-card>
</template>
<script lang="ts" setup>
import '@/css/markdown.css'
import { NCard } from 'naive-ui'
import type { ComputedRef } from 'vue'
import { computed, inject } from 'vue'
const darkTheme = inject<ComputedRef<boolean>>(
'DarkTheme',
computed(() => true)
)
const backgroundColor = computed<string>(() => {
return darkTheme.value ? '#101014' : '#ffffff'
})
const textColor = computed<string>(() => {
return darkTheme.value ? '#ffffffd1' : '#333639'
})
</script>
<style lang="less" scoped>
:deep(.markdown-body) {
// max-width: max(80%, 70vw);
margin-right: 200px;
border-right: 1px solid;
.toc-box {
position: fixed;
right: 0px;
width: max(15%, 200px);
min-width: 200px;
li {
list-style: none;
}
}
body {
scroll-behavior: smooth;
}
ul {
li {
list-style-type: disc;
}
}
ol {
li {
list-style: decimal;
}
}
padding: 10px 30px;
background-color: v-bind(backgroundColor) !important;
img {
display: block;
margin: 0 auto;
background-color: v-bind(backgroundColor);
}
table {
tr {
background-color: v-bind(backgroundColor) !important;
color: v-bind(textColor);
}
}
* {
color: v-bind(textColor);
}
code {
color: #ffffffd1;
}
}
</style>

33
docs/Site.vue 100644
View File

@ -0,0 +1,33 @@
<template>
<n-layout position="absolute">
<n-layout-header style="height: 60px"><ToolBar /></n-layout-header>
<n-layout has-sider sider-placement="left" style="height: calc(99vh - 60px); overflow: hidden">
<RouterView />
<n-layout-footer position="absolute"
><div class="copyleft">
<div>OpenDataV · Made by AnsGoo</div>
</div></n-layout-footer
>
</n-layout>
</n-layout>
</template>
<script lang="ts" setup>
import '@/css/markdown.css'
import { NLayout, NLayoutFooter, NLayoutHeader } from 'naive-ui'
import ToolBar from './modules/ToolBar'
</script>
<style scoped lang="less">
:deep(.n-layout .n-layout-scroll-container) {
overflow-x: initial;
}
.copyleft {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-content: center;
justify-content: center;
align-items: center;
}
</style>

View File

View File

@ -0,0 +1,75 @@
<template>
<ToolBar :bars="toolBars" />
</template>
<script setup lang="ts">
import { h } from 'vue'
import { useRouter } from 'vue-router'
import type { ToolBarItemType } from '@/components/ToolBar'
import { ToolBar } from '@/components/ToolBar'
import docsRouters from '@/router/modules/docs'
import ActionDetail from './modules/ActionDetail.vue'
import { toggleTheme } from './modules/actions'
import Logo from './modules/Logo.vue'
import ThemeIcon from './modules/themeSwitch/ThemeIcon.vue'
const router = useRouter()
const emits = defineEmits<{
(e: 'change', value: string): void
}>()
const leftBars: ToolBarItemType[] = docsRouters[0].children.map((el) => {
return {
label: el.meta.title,
action: async () => {
emits('change', 'help')
await router.push({
name: el.name
})
},
icon: () =>
h(ActionDetail, {
icon: el.meta.icon,
label: el.meta.title
}),
location: 'left'
}
})
const toolBars: ToolBarItemType[] = [
{
label: '首页',
action: async (_e: MouseEvent) => {
await router.push({
name: 'Pages'
})
},
icon: () => h(Logo),
divider: true,
location: 'left'
},
...leftBars,
{
label: 'GitHub',
action: () => {
window.open('https://github.com/AnsGoo/openDataV', '_blank')
},
icon: 'github',
location: 'right'
},
{
label: '主题',
action: toggleTheme,
icon: () => h(ThemeIcon),
location: 'right'
}
]
</script>
<style scoped lang="less">
.tool-bar-title {
font-size: 20px;
font-weight: 400;
}
</style>

View File

@ -0,0 +1,3 @@
import ToolBar from './Toolbar.vue'
export default ToolBar

View File

@ -0,0 +1,9 @@
<template>
<x-icon :name="icon" :size="24" /><span>{{ label }}</span>
</template>
<script lang="ts" setup>
defineProps<{
icon: string
label: string
}>()
</script>

View File

@ -0,0 +1,11 @@
<template><LogoView width="65px" /><span class="title">OpenDataV</span></template>
<script lang="ts" setup>
import LogoView from '@/components/LogoView'
</script>
<style scoped>
.title {
font-size: 1.25rem;
line-height: 1.75rem;
font-weight: 500;
}
</style>

View File

@ -0,0 +1,11 @@
import { useProjectSettingStoreWithOut } from '@/store/modules/projectSetting'
// 状态管理
const projectStore = useProjectSettingStoreWithOut()
const toggleTheme = () => {
projectStore.setNavTheme(!projectStore.darkTheme ? 'light' : 'dark')
projectStore.setDarkTheme(!projectStore.darkTheme)
}
export { toggleTheme }

View File

@ -0,0 +1,13 @@
<template>
<x-icon :name="themeIcon" :size="24" />
</template>
<script lang="ts" setup>
import type { ComputedRef } from 'vue'
import { computed, inject } from 'vue'
const darkTheme = inject<ComputedRef<boolean>>(
'DarkTheme',
computed(() => true)
)
const themeIcon = computed<string>(() => (darkTheme.value ? 'sun' : 'moon'))
</script>

View File

@ -0,0 +1,59 @@
<template>
<div v-memo="menus" class="components">
<n-ul v-for="item in menus" :key="item.key">
<div class="group-label group-item" @click="goTo(item.key)">
<x-icon :name="item.icon" :size="20" />
<span> {{ item.label }}</span>
</div>
<n-li v-for="child in item.children || []" :key="child.key" @click="goTo(child.key)">
<div class="sub-group-item group-item">{{ child.label }}</div>
</n-li>
</n-ul>
</div>
</template>
<script setup lang="ts">
import { NLi, NUl } from 'naive-ui'
import { useRouter } from 'vue-router'
import type { MenuItem } from './type'
const router = useRouter()
withDefaults(
defineProps<{
menus: MenuItem[]
}>(),
{
menus: () => []
}
)
const goTo = async (key: string) => {
await router.push({
name: key
})
}
</script>
<style scoped lang="less">
.group-label {
font-size: 18px;
display: inline-flex;
flex-direction: row;
flex-wrap: nowrap;
align-content: center;
justify-content: center;
align-items: center;
}
.sub-group-item {
margin-left: 20px;
}
.group-item {
&:hover {
transform: scale(1.01);
color: #2d8cf0;
cursor: pointer;
}
}
</style>

View File

@ -0,0 +1,5 @@
import Content from './Content.vue'
import type { MenuItem } from './type'
export default Content
export { MenuItem }

View File

@ -0,0 +1,9 @@
export interface MenuItem {
label: string
icon: string
key: string
children: Array<{
label: string
key: string
}>
}

View File

@ -0,0 +1,41 @@
[toc]
# 为什么我不能保存我的页面?
openDataV 是一款基于Vue3的纯前端拖拽式低代码平台默认所有的接口都是Mock接口因此你不能进行任何写操作,因此保存更新之类的操作是无效果的;因为时间和精力不足,我们开发了一个简易的后台服务[https://gitee.com/small_bud_star/open-data-backend](https://gitee.com/small_bud_star/open-data-backend),便于大家使用,但是并不适合在生产环境中使用
我们也打算开发一个高性能的后台服务计划采用Spring套装或者Go语言感兴趣的大佬可以加入我们一起开发
# 为什么没有XXXX组件
我们对OpenDataV的定位更多是一个平台性质或者框架性质所以自带的组件更多是示例功能。
我们在实际应用中发现,组件的通用性和复杂度成反比,如果一个组件通用性很强,那么它的复杂度就很高,用户的的学习成本就会很大,这样就失去了低代码的意义;既然做不到通用性强,我们的工作重点也不会放在组件开发上;当然我们也会去开发一些有较大通用性的组件,但是站在个人经验来说,组件的开发本身应该是结合业务,根据具体业务有侧重点的开发,既能满足业务需求的变化,又简单易上手。
后期不排除我们会针对某个行业去开发一成套的组件,同时如果各位老板有定制组件的开发需求,我们也不排除去接一些定制组件开发的兼职。
# 怎么去开发一个新组件?
1. 学习开发文档
2. 学习官方组件例子
3. 关注公众号,查看往期文章
4. 和社区同伴交流
同时希望大家向本项目贡献自己的组件,被官方收录的组件请保证组件有较大的通用性、完善的示例文档,且和本项目保持一致的开源许可证, 如果未上传开源许可证,默认采用本项目的许可证。
# 我的组件依赖和其他组件公用一个`NPM`包,但是版本不一样怎么办?
整个OpenDataV是采用`Monorepo`方式管理组件以及依赖,严格意义上来说,每个组件都是独立的,每个组件有自己的`类型文件`、`依赖包`和`许可证`,删除任何一个组件不影响其他组件的使用。所以对于同一个包的不同版本,只要在各自的依赖中添加对应的版本即可,也可以像官方组件一样,多个组件共享同一个依赖。
同时对于官方组件你也可以按需引入,对于自己用不到的可以删除,不会有任何问题
# 想自己开发后台,如何知道本项目的接口?
可以查看项目 `src\api`接口文件和类型文件,当然你可以随意更改这些接口定义

View File

@ -0,0 +1,102 @@
[toc]
平台集成了`通用数据`管理功能,目前已完成`静态数据`和`Rest数据`功能开发
# 静态数据
静态数据是指平台提供了一个统一接口用来读取或者存储的数据
![](./img/015.png)
用户可以在静态页面对平台存储的静态数据进行`修改`、`删除`和`新增`
## 新增与修改
点击静态数据的`原始数据`页签,在代码编辑器里面可以对静态数据进行`修改`和`新增`,点击<x-icon name="save"/>按钮,即可将在`处理数据`页签看到,此静态数据被脚本处理完成之后的效果,点击右上角的保存按钮,即可将此静态书进行更新和保存
## 删除
在右侧静态数据列表中,鼠标右击即可删除对应的静态数据
## 设计思路
> 很多低代码平台的静态数据保存在页面中配置文件中,但是我们无法预测用户配置的静态数据大小,如果将静态数据存储在页面配置文件中,将会导致页面配置文件数据体积增大,进而影响页面渲染速度。
> 静态数据保存在平台后台的话又会带来另一个问题,静态数据在多个组件中被复用时,在某个组件中更改了静态数据内容时所带来的影响是不是可预见的,答案是否定的。
> 我们不建议在组件使用过程中更改静态数据内容,我们可以通过在每个组件中写不同的脚本来适配同一数据源在不同组件复用时数据差异。
> 静态数据的修改应该考虑数据被复用时,对使用的组件效果影响,因此我们将静态数据设计为存储在平台后端,在使用组件使用过程中不可更改,但是在静态数据页面可修改!
# Rest数据
`Rest 数据`是通过`HTTP`接口获取数据,应该是平台最主要的获取数据的方式,一个类似`PostMan`的接口调试
![](./img/16.png)
## 跨域拦截
因为浏览器的安全策略,一旦你的接口服务未配置跨域相关安全策略,那么是无法在本平台进行接口调试的,可以跟接口服务开发人员协调,支持跨域访问
## 设计思路
> `Rest数据`页面设计是类似于`PostMan`的作用,但是很遗憾,我们尚未达到`Postman`的效果,目前仅支持`application/json`数据的请求和响应,但是对于`FormData`、`Blob`和`ArrayBuffer`等数据尚未支持
> `Rest数据`的定义数据是可控的,主要是 `url`、`method`、`headers`、`params`、`data`,因此整个 `Rest数据`定义我们是保存在页面配置文件中的,我们支持在`Rest数据`页面增加或者修改接口定义,同时我们也支持在单个组件中添加接口,也就是说你修改任意一接口也不会影响已经在其他组件中被使用的该接口
# 脚本
为了提高组件的`复用性`和接口的`兼容性`,我们提供了脚本功能。
![](./img/17.png)
通过脚本你可以将`接口返回的数据`处理成`组件所需要的数据`,这样就你就无需担心你的组件和接口之间的无法兼容的问题。
目前我们的脚本只支持`javascript`,后续将支持`python`,因为`Python`在数据处理方面的先天优势十分的吸引我们,我们希望可以将`Python`的引入,可以和平台碰撞出耀眼的火光。
## Javascript脚本
`Javascript脚本` 我们使用的是js的 `new Function()`特性,因此`Javascript脚本`,你只需要书写函数体本身即可
例如
```javascript
const filterFunc = (respoptions) => {
return resp.filter(el => el.value > 30)
}
```
你只需要写
```javascript
return resp.filter(el => el.value > 30)
```
`Javascript脚本` 生成函数默认有两个位置参数`resp`和`options`
- `resp`: 需要处理的数据
- `options`: 在组件中使用,默认返回的组件的`propValue`属性
## Python脚本
待实现

View File

@ -0,0 +1,536 @@
[toc]
# 基础知识
## 入门须知
1. 本项目是基于`Vue3`开发,所有的组件都符合`Vue3`知识,对于`Vue3`知识不熟悉的同学建议,先学习`Vue3`相关知识
2. 本项目是采用`TypeScript`开发,若对于`TypeScript`知识欠缺的,可先熟悉一下`TypeScript`
3. 本项目中所有的组件只能在本项目中使用,无法保证在别的平台同样适用
# 编辑器
编辑器承载与渲染组件的容器,编辑提供了组件`拉伸`、`旋转`、`置顶`、`置底`、`组合`、`拆分`的能力,编辑器如下图:
![create](./img/006.png)
编辑器主要由以下功能分区组成:
> - 顶部的`工具栏` 常用工具按钮
> - 左侧的`资源栏` 组件和图层
> - 右侧的`属性栏` 属性分为组件属性和画布属性
> - 底部的`状态栏` 创建页面的状态
> - 中间的`画布` 承载组件的最顶层容器
# 组件
一个完整的组件又以下属性构成
> - `componentName`:组件名
> - `component` 组件对象渲染模板
> - `config`:组件配置项对象
> - `docs`:组件说明文档
## 组件名
组件名必须保证全局唯一,因为组件会被挂载到`Vue3`实例对象上
## 组件配置项对象
配置项对象是继承了 `CustomComponent` 抽象类的子类,在这里我们以静态文本为例
```typescript
/**
* {component}: 组件名
* {group}: 组件分类
* {name}: 组件label
* {id}: 组件ID
* {width}: 组件初始化长度
* {height}: 组件初始化高度
* {icon? }: 组件图标
* {DataMode? }: 数据接入模式
*/
class StaticTextComponent extends CustomComponent {
constructor(id?: string, name?: string, icon?: string) {
super({
component: componentName,
group: ComponentGroup.TEXT,
name: name ? name : '静态文本',
id,
width: 150,
height: 20,
icon,
dataMode: DataMode.SELF
})
}
}
```
各个属性含义
> - `component`: 组件名
> - `group`: 组件分类
> - `name`: 组件label
> - `id`: 组件ID
> - `width`: 组件初始化长度
> - `height`: 组件初始化高度
> - `icon? `: 组件图标
> - `DataMode? `: 数据接入模式
除过需要继承`CustomComponent` 抽象类外,还需要重新定义`_prop`和`_style`属性,
> - `_prop属性`:定义了组件可以更改的属性
> - `_style`: 定义了组件的外在的CSS样式
## 组件渲染模板对象
组件渲染模板对象是一个特殊的`Vue3`组件对象, 这个组件对象有一个`component`配置项对象,这里我展示Vue3 `setup script` 写法
```typescript
const props = defineProps<{
component: StaticTextComponent
}>()
```
## 组件分类
目前组件分为 16类分别是
- `BASIC`: 基础组件
- `BORDER`: 边框组件
- `DECORATION`: 装饰组件
- `LINE`: 线状图组件
- `BAR`: 柱状图组件
- `PIE`: 饼状图组件
- `MAP`: 地图组件
- `GAUGE`: 仪表盘组件
- `CUSTOM`: 自定义组件
- `TEXT`: 文本组件
- `TABLE`: 表格组件
- `NAVIFATION`: 导航组件
- `PROGERSS`: 进度条组件
- `THERMOMETER`: 温度计组件
- `OTHER`: 其他组件
用户按照组件特性选择对应的组件类型即可
## 组件属性
组件属性是定义了组件在编辑器右侧`属性栏`中的`属性`的可配置项
组件属性的由属性配置项对象`MetaContainerItem`和组件属性值对象构成
```typescript
[
{
label: '基础配置',
prop: 'base',
children: [
{
prop: 'type',
label: '文本类型',
type: FormType.SELECT,
props: {
defaultValue: 'text',
options: [
{ value: 'text', label: '文本' },
{ value: 'symbol', label: '符号' }
]
}
},
{
prop: 'text',
label: '自定义文本',
type: FormType.TEXT,
props: {
defaultValue: 'OpenDataV'
}
}
]
}
]
```
该配置项对应的组件属性分别是
```typescript
StaticTextType {
base: {
text: string
type: 'text' | 'symbol'
}
}
```
属性配置项对象`MetaContainerItem`
- `label`: 分类标签
- `prop`: 分类值
- `children`: `AttrType`子属性配置项集合
`AttrType`子属性配置项
- `label`: 值标签
- `prop`: 属性值
- `type`: 属性值的Form类型
- `showLabel`: 是否显示标签
- `props`: Form组件配置
- `help`: Form帮助信息
## 组件样式
### 公共样式
所有的组件都有`位置大小`这个公共样式,公共样式主要是组件在画布中的`坐标`、`尺寸`和`旋转度`三个类属性
### 其他样式
其他样式组件属性的定义类似只不过组件属性需要你在组件中根据业务将属性值渲染出来但是组件样式是里面的子属性值要求必须是CSS属性本平台已经实现了常见的CSS样式渲染
## 组件可用Form类型
本平台已经实现了常用的Form类型例如
> - `TEXT`: Input框
> - `TEXTAREA`: 文本域
> - `NUMBER`: 数字输入框
> - `SELECT`: 选择器
> - `COLOR`: 色盘
> - `RADIO`: 单选框
> - `SWITCH`: 开关
以及平台定制的Form类型 例如:
> - `FONT_STYLE`: 字体选择
> - `FONT_WEIGHT`: 字重选择
> - `ARRAY`: 动态列表Form
> - `BACKGROUND`: 背景选择
> - `LINEAR_GRADIENT`: 渐变色
还有支持用户自定义`Form`
> -`CUSTOM`: 自定义Form
**Form公共属性**
所有的Form 都有如下属性其次每一种Form类型可能拥有自己独有的属性
|属性名|含义|说明|
|----|---|---|
|editable|是否可编辑| bool型|
|disabled|是否禁用| bool型|
|required|是否必须| bool型|
|defaultValue|默认值| 任意类型|
|options|自有配置项| 任意类型|
1. TEXT Form属性
无专有属性
2. NUMBER Form属性
|属性名|含义|说明|
|----|---|---|
|min|最小值| 数字型|
|max|最大值| 数字型|
|step|步长| 数字型|
3. SELECT Form属性
|属性名|含义|说明|
|----|---|---|
|options|选项列表| `Array<{value:any, label:string}>`|
4. SWITCH Form属性
|属性名|含义|说明|
|----|---|---|
|options|选项列表| `Array<{value:any, label:string}>`|
5. RADIO Form属性
|属性名|含义|说明|
|----|---|---|
|options|选项列表| `Array<{value:any, label:string}>`|
6. ARRAY Form 属性
|属性名|含义|说明|
|----|---|---|
|count|数量| 数字型|
|type|类型| `static`长度不可变,`dynamic`长度可变|
|maxItem|最大数量| 数字型|
|minItem|最小数量| 数字型|
7. CUSTOM Form 属性
|属性名|含义|说明|
|----|---|---|
|componentType|组件| Form组件|
|args|组件参数|任何类型|
8. 自定义Form类型
我们自定义Form类型自定义Form组件需要实现属性`value`和`update:value`的`emit`方法,具体详见本平台`FONT_STYLE`或者`BACKGROUND`等平台定制Form组件
```typescript
const props = withDefaults(
defineProps<{
value?: number
}>(),
{
value: 400
}
)
const emits = defineEmits<{
(e: 'update:value', weight: number): void
(e: 'change', weight: number): void
}>()
```
## 监听组件属性
当用户通过`属性栏`的`属性`更改组件属性时,画布中的组件需要根据用户更改来,对组件进行重新渲染。
监听组件属性变化有三种方式
### watch 观测
可以通过深度监听,监听组件 `component`属性对象的`propValue`属性或者子属性来监听组件属性更改
```typescript
const customeText = ref<string>(props.propValue.base.text)
watch(
() => props.propValue.base.text,
(value: string) => {
customeText.value = value
}
)
```
### computed 计算属性
也可以通过computed计算组件 `component`属性对象的`propValue`属性或者子属性来监听组件属性更改
```typescript
const customeText = computed<string>(() => {
return props.propValue.base.text
})
```
### hooks
还可以通过平台提供的`useProp`hook来监听属性变化
```typescript
const customeText = ref<string>(props.propValue.base.text)
const propValueChange = (type:string, key:string, value:any) {
if(type === 'base' && key === 'text'){
customeText.value = value
}
}
const { propValue } = useProp<StaticTextType>(props.component, propValueChange)
```
### 注意事项
需要注意的事,只有在编辑模式下才需要监听组件属性变化,在预览模式下不需要监听组件属性变化,因此可以在组件中判断编辑器模式(`什么是编辑器模式,详见编辑器模式`),来决定是不是要对属性进行监听依次可以提升组件性能
## 数据
组件配置项对象在有一个`DataMode` 数据接入模式的属性,他定义了组件可以从那里接入数据
接入模式分为三类:
> - `SELF`: 组件自己内部自行接入数据
> - `UNIVERSAL`: 组件采用通用的方式接入数据
> - `GLOBAL`: 组件从订阅全局数据
### SELF
组件自己在内部通过不管通过`HTTP`或者`WebSocket` 自己处理数据的请求和响应,这时候组件的`属性栏`没有`数据`配置项
```typescript
const activeCount = ref<string>(props.propValue.base.count)
const getData = async () => {
const resp = await http.get({url: '/getdata'})
activeCount.value = resp.data
}
const intervalId:number = 0
onMounted( () => {
clearInterval(intervalId)
intervalId = setInterval( getData, 30000)
})
onUnmounted( () => {
clearInterval(intervalId)
})
```
### UNIVERSAL
组件采用`useData`hook来统一处理数据通用数据处理方式目前提供了三种数据接入方式分别是`示例数据`、`静态数据`、`Rest数据`
> - `示例数据`: 示例数据无法更改,主要 用来组件的展示,不建议在生产环境下使用
> - `静态数据`: 静态数据从后台数据库中存储的静态数据中加载
> - `Rest数据`: 根据用户提供的`REST`接口发起HTTP请求获取数据
```typescript
import { useData } from 'open-data-v/base/hooks'
let chartData:
| Array<{ label: string; value: number }>
| RequestResponse<Array<{ label: string; value: number }>>['afterData'] = []
const dataChange = (resp: any, _: DataType) => {
if (resp.status >= 0) {
chartData = resp.afterData
doSomething(chartData)
}
}
useData(props.component, dataChange)
```
`useData`钩子的第二个参数是一个数据处理回调,入参是获取到的数据,用户可以在回调中根据数据处理组件的渲染
### GLOBAL
待实现
# 监听组件尺寸
不管是在`画布`中`拉伸`组件还是通过`属性栏`的`样式`里面的`位置大小`属性进行配置,都会引起组件大小变动,可以通过`v-resize`指令来监听组件大小的更改,具体详见`常见指令`章节
# 编辑器模式
1. 模式分类
编辑器分为`编辑模式(EDIT)`、`预览模式(PREVIEW)`、`视图模式(VIEW)`
2. 模式作用
在组件中可以根据编辑器的模式来切换不同的表现形式,例如:
>
> - 在`编辑模式`下我们需要监听组件属性或者样式的变化,但是在`预览模式`和`视图模式`下我们不需要监听组件属性的变化,这时候我们可以获取编辑器模式,来在不同模式下采用不同的逻辑。
>
> - 在`编辑模式`下我们采用示例数据来渲染组件,但是在`预览模式`和`视图模式`下我们采用生产数据渲染组件。
3. 获取组件模式
我们可以通过`canvasState`来获取编辑器模式,例如
```typescript
import useCanvasState from 'open-data-v/designer/state/canvas'
const canvasState = useCanvasState()
// editoMode 即为编辑器模式
canvasState.editMode
// isEditMode 可以判断编辑器是否处于编辑模式
canvasState.isEditMode
```
# 常用指令
`v-resize` 组件缩放
组件编辑器中经常需要被拉伸进行放大或者缩小,可以采用我们封装的`v-resize`指令,来监听组件的大小变化,以便做出对应的响应
```html
<div v-size="resizeHander">我是组件</div>
```
```TypeScript
const resizeHandler = (entry: ResizeObserverEntry) => {
const {width, height}: DOMRectReadOnly = entry.contentRect
doSomething()
}
```
# 组件文档
本项目主体文档采用`Markdown`编写支持在Markdown中渲染Vue组件为了方便文档书写我们提供了一个工具组件`RenderComponent`,支持渲染任意组件,并提供`palyground`
```vue
<RenderComponent
:config="StaticTextComponent"
:component="StaticText"
:prop-value="{
base: {
text: '我们一起建设OpenDataV吧',
type: 'text'
}
}"
:style="{
color: '#d03050',
fontSize: 40,
fontWeight: 800,
width: 550,
height: 100
}
"
title="静态文本"
mode="debug"
/>
```
> - `mode`: 模式,可选项`debug`|`view`, 在`debug`下提供了`playground`功能
> - `component`: 组件模板
> - `config`:组件配置项类
> - `propValue`:组件属性初始化配置项
> - `style`:组件样式初始化配置项
大家在撰写组件文档时需要有如下内容
**1. 属性或者样式的解释**
**2. 效果示例**
**3. 可交互的`playground`**

View File

@ -0,0 +1,155 @@
[toc]
![](@/assets/logo.png)
<div class="badge">
![](https://img.shields.io/github/license/AnsGoo/openDataV)
![](https://img.shields.io/github/stars/AnsGoo/openDataV)
![](https://img.shields.io/github/issues/AnsGoo/openDataV)
![](https://img.shields.io/github/forks/AnsGoo/openDataV)
</div>
## 简介
🎃OpenDataV 是一个纯前端的`拖拽式`、`可视化`、`低代码`数据可视化 🌈 开发平台,你可以用它自由的拼接成各种 ✨ 炫酷的大屏,同时支持用户方便的开发自己的组件并接入平台。
## 体验
🧙国外:[http://datav.byteportrait.com/](http://datav.byteportrait.com/)
🧙国内:[http://small_bud_star.gitee.io/opendatav](http://small_bud_star.gitee.io/opendatav)
🧙国内:[http://opendatav.xingxingzaixian.fun/](http://opendatav.xingxingzaixian.fun/) (已对接后端)
## 源码地址:
🍨github:[https://github.com/AnsGoo/openDataV](https://github.com/AnsGoo/openDataV)
🍨gitee:[https://gitee.com/small_bud_star/DataV](https://gitee.com/small_bud_star/DataV)
gitee 仅做代码同步issues 或者 PR 请在 github 提交。
目前该项目在不断的完善中,🎉 欢迎 issuer,🌹 欢迎 start, 🎨 欢迎 commit, 🚀 欢迎 use...,💪 欢迎一切技术交流活动
## 👁️ 预览
### 🤿 页面编辑
1. 组件添加
![screenshots2.gif](https://s2.loli.net/2022/10/31/nApiFm7PogI1dHS.gif)
2. 组件操作
![screenshots1.gif](https://s2.loli.net/2022/10/31/9lkiR15sVMLapIe.gif)
3. 组件数据配置
![screenshots.gif](https://s2.loli.net/2022/10/31/28lf6NK35EaY9wJ.gif)
### 🖇️ 接口管理
![API.png](https://s2.loli.net/2022/10/31/f1GuMTIp2rzEPQF.png)
## 💒 功能
- 🎊 编辑器页面基本功能完成,包括编辑、预览、导入、导出、保存
- 🪄 图层的置顶、置底、上下移动、显示、隐藏、复制、剪切、粘贴
- 🖼️ 组件的缩放、旋转、拖动、复制、粘贴、组合、拆分、移除、自动对齐
- 🔮 支持用户操作记录的恢复、撤销功能
- 🧶 支持用户自定义组件
- 📔 支持组件的用户自定组件配置项
- 🏪 支持明暗主题切换
- 🧬 使用 Monorepo 模式进行组件和依赖管理
- 🧶 组件数据自定义接入
- 🔌 数据动态处理JS 已完成)
- 🖇️ 接口管理(示例数据、静态数据、HTTP 接口数据已完成适配)
## 🎢 技术点
本项目采用`Vue3` + `vite` + `TypeScript`开发,界面库使用`NaiveUI`,使用面向对象方式封装了路由、请求、存储,组件采用自动扫描注册、异步加载,提升渲染速度;使用 IndexDB 存储快照数据,减少快照数据内存占用,加快访问速度;组件独立依赖,解耦了组件和基础框架的依赖库,方便后续独立开发组件。
目前仅开发了部分组件,后续还会继续完善。
## ⌛ 计划功能
- [ ] 组件管理
- [ ] 脚本管理Python 待支持)
- [ ] GraphQL、GRPC、WebScoket、MQTT、SocketIO 多种数据接口适配
- [ ] 代码生成
- [ ] 动态文档
- [ ] 全局数据接入
## 💂 开发
### 🧊 开发环境
| 名称 | 版本 |
| ---- | ------- |
| node | 16.14.x |
| pnpm | 7.9.3 |
| vue | 3.2.37 |
🚥 目前仅在`Chrome`和`Microsoft Edge`最新版浏览器测试过,其他浏览器未测试
### 🎮 启动项目
```Bash
# 安装依赖
pnpm install
pnpm bootstrap
# 运行项目
pnpm dev
# 打包项目
pnpm build
```
## 🧑‍💻 代码提交
git commit 信息请按照如下规范进行书写
- feat: 新功能
- fix: 修复 Bug
- docs: 文档修改
- perf: 性能优化
- revert: 版本回退
- ci: CICD 集成相关
- test: 添加测试代码
- refactor: 代码重构
- build: 影响项目构建或依赖修改
- style: 不影响程序逻辑的代码修改
- other: 不属于以上类型的其他类型(日常事务)
## ☎️联系方式
**技术交流,请加微信群**
<img src="/wechat.png" style="width:430px">
**更新动态请关注公众号**
![wechat](/OfficialAccounts.jpg)
<script setup >
</script>
<style scoped>
.badge {
display: flex;
align-items: center;
align-content: center;
flex-wrap: nowrap;
flex-direction: row;
justify-content: center;
}
</style>

View File

@ -0,0 +1,224 @@
[toc]
## 开源合作
本项目使用`Apache-2.0`开源协议,二次开发及其商用请遵守开源协议即可,若想采用商用授权请联系`OpenDataV`的作者,可邮件 **haiven_123@163.com**
## 免责声明
任何企业或者个人使用`OpenDataV`原始项目或者二次开发,对自己或者他人造成的任何形式的损失或者危害,`OpenDataV`开发者不承担任何法律风险
## 权益声明
本项目使用`Apache-2.0`开源协议,二次开发及其商用请遵守开源协议即可,如若擅自违反开源协议产生的法律纠纷,`OpenDataV`将会追究到底
```license
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 2022 AnsGoo
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.
```

View File

@ -0,0 +1,220 @@
[toc](目录)
快速创建一个属于自己的页面并发布
# 快速开始
接下来我们将带领大家创建一个页面,来熟悉该低代码平台的使用
## 安装
```Bash
git clone https://github.com/AnsGoo/openDataV.git
npm install pnpm -g
pnpm install
```
## 启动
```Bash
pnpm dev
```
## 编译
```Bash
pnpm build
```
# 访问布局管理
```Bash
/Pages
```
![管理页面](./img/005.png)
当前会展示出项目已有的页面,你可以点击页面卡片进行查看,也可以对已有页面进行二次编辑,还可以自己新创建一个页面
## 打开创建页面
点击`新建按钮`号会自动进入`创建`页面
![create](./img/006.png)
整个页面被分为:
- 顶部的`工具栏` 常用工具按钮
- 左侧的`资源栏` 组件和图层
- 右侧的`属性栏` 属性分为组件属性和画布属性
- 底部的`状态栏` 创建页面的状态
- 中间的`画布` 承载组件的最顶层容器
接下来我们将开始创建一个页面
# 添加第一组件
我们设计一个`HelloWord`文本页面
## 拖动组件
我们在左侧`资源栏`的组件中找到`柱状图`->`基础柱状图`组件,并将其拖动到画布中央,并单击鼠标左键选中组件
![text](./img/007.png)
## 修改组件属性
修改组件下列属性
- 数据配置的大值为`70`
- 坐标轴配置的X网格线`不显示`
- 坐标轴配置的轴线颜色为 `#18A058`
![text](./img/008.png)
同时我们可以对组件进行`拖动`、`旋转`、`拉伸`
<video muted autoplay="true" loop="true">
<source src="./img/010.mp4" type="video/mp4" />
</video>
## 配置组件数据
将组件的示例数据改为静态数据,切换组件数据类型,这里我们选择静态数据,并选择具体的静态数据
<video muted autoplay="true" loop="true">
<source src="./img/011.mp4" type="video/mp4" />
</video>
## 修改组件样式
所有的组件都有`位置大小`的公共样式,主要是用来设置组件的`坐标`、`尺寸`和`旋转`情况,你除过可以通过`拉伸`、`拖动`、`旋转`组件本身来改变这些值以外,还可以通过公共样式`位置大小`来精确更改。
这里我让将组件旋转状态改为0
<video muted autoplay="true" loop="true">
<source src="./img/012.mp4" type="video/mp4" />
</video>
# 组件间的操作
我们向画布中追加一个边框组件
![imag](./img/014.png)
## 选中
这里我们通过框选,选中`边框`和`柱状图`
- 框选
<video muted autoplay="true" loop="true">
<source src="./img/020.mp4" type="video/mp4" />
</video>
- ctr + '鼠标单击'
<video muted autoplay="true" loop="true">
<source src="./img/021.mp4" type="video/mp4" />
</video>
- 通过图层选中
<video muted autoplay="true" loop="true">
<source src="./img/022.mp4" type="video/mp4" />
</video>
## 组合
在选中框上右击组合进行组件的组合,这时候两个或者多个组件就被变成一个`分组`组件
<video muted autoplay="true" loop="true">
<source src="./img/023.mp4" type="video/mp4" />
</video>
## 拆分
我们也可以通过选中分组组件,然后鼠标右击对`分组`组件进行`拆分`
<video muted autoplay="true" loop="true">
<source src="./img/024.mp4" type="video/mp4" />
</video>
# 图层编辑
图层是组件的层级关系,越是靠近顶层的组件图层级别越高,组件图层级别高的可以遮盖图层级别低的组件,图层级别高低关系着组件的渲染顺序,从而影响用户页面的`视觉效果`(比如一张图片覆盖了另一张图片)和`交互效果`(点击效果无法触发)等;点击左侧`资源栏`一列的图层即可看到我们画图中所有组件之间的图层关系,默认组件的名称就是图层的名称。
## 更名
选中组件之后,每一个组件的都有属性一栏都有一个`公共属性`,可以通过更改`公共属性`的`名称`一栏来修改图层名称
## 移动
可以通过选中组件来,然后鼠标右击菜单栏里面的`置顶`、`上移一层`、`置底`、`下移一层`来更改组件的图层顺序
<video muted autoplay="true" loop="true">
<source src="./img/025.mp4" type="video/mp4" />
</video>
也可以通过`图层`一栏,鼠标右击的菜单栏里面的`置顶`、`上移一层`、`置底`、`下移一层`来更改组件的图层顺序
<video muted autoplay="true" loop="true">
<source src="./img/026.mp4" type="video/mp4" />
</video>
还可以通过拖动组件到对应的位置来修改图层顺序
<video muted autoplay="true" loop="true">
<source src="./img/027.mp4" type="video/mp4" />
</video>
## 隐藏、显示
在`图层`一栏,通过在图层上鼠标右击菜单栏里面的`显示`\\`隐藏`来显示或者隐藏组件,需要注意的是显示或者隐藏组件只是在画图中组件较多时,通过隐藏组件来减少画布中组件数量,便于用户编辑组件,在非编辑模式下无任何效果,只对编辑模式有效
# 修改画布
## 分辨率
我们支持针对不同的分辨率的设备,当设备分辨率比差异较大的情况下,可以通过指定分辨率使设计出来的页面达到最好的效果
## 页面背景
默认页面使背景色,我们支持页面修改为`背景色`、`渐变色`、`背景图`
<video muted autoplay="true" loop="true">
<source src="./img/028.mp4" type="video/mp4" />
</video>
# 预览并发布
当我们完成页面的设计,我们可以通过`工具栏`里面的预览按钮去预览一下页面效果效果,也可以通过`保存`按钮来保存我们的页面,还可以通过`导出`按钮将我们页面设计在本地进行保存。
<video muted autoplay="true" loop="true">
<source src="./img/029.mp4" type="video/mp4" />
</video>
至此一个简单的页面就设计完成了!

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

44
examples/App.vue 100644
View File

@ -0,0 +1,44 @@
<script setup lang="ts">
// This starter template is using Vue 3 <script setup> SFCs
// Check out https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup
import { NGlobalStyle, NLoadingBarProvider } from 'naive-ui'
import { useCanvasState } from 'open-data-v/designer'
import { computed, provide, readonly, ref } from 'vue'
import { RouterView, useRouter } from 'vue-router'
import ConfigProvider from '@/components/provider/ConfigProvider.vue'
import { useProjectSettingStoreWithOut } from '@/store/modules/projectSetting'
const canvasState = useCanvasState()
const projectStore = useProjectSettingStoreWithOut()
provide('DarkTheme', readonly(computed(() => projectStore.darkTheme)))
const overflow = ref<string>(
(() => {
if (!canvasState.isEditMode) {
return 'hidden'
} else {
return 'auto'
}
})()
)
const { currentRoute } = useRouter()
</script>
<template>
<ConfigProvider>
<n-loading-bar-provider>
<RouterView :key="currentRoute.path" :style="{ overflow }" />
<n-global-style />
</n-loading-bar-provider>
</ConfigProvider>
</template>
<style lang="less">
html,
body,
#app {
height: 100vh;
width: 100vw;
// overflow: hidden;
}
</style>

View File

@ -0,0 +1,64 @@
import type { AxiosResponse } from 'axios'
import { apiHttp as http } from '@/utils/http'
import type { AfterScriptDetail } from './type'
/**
* id
* @param id ID
*/
export const getAfterScriptApi = async (id: string): Promise<AxiosResponse<AfterScriptDetail>> => {
return http.get<AfterScriptDetail>({
url: `/dataset/script/${id}/`
})
}
/**
*
*/
export const getAfterScriptListApi = async (): Promise<AxiosResponse<AfterScriptDetail[]>> => {
return http.get<AfterScriptDetail[]>({
url: '/dataset/script/'
})
}
/**
*
* @param id id
* @param data
*/
export const updateAfterScriptApi = async (
id: string,
data: Partial<Pick<AfterScriptDetail, 'name' | 'code'>>
): Promise<AxiosResponse<AfterScriptDetail>> => {
return http.put<AfterScriptDetail>({
url: `/dataset/script/${id}/`,
data: { ...data, id }
})
}
/**
*
* @param data
*/
export const createAfterScriptApi = async (
data: Pick<AfterScriptDetail, 'name' | 'code'>
): Promise<AxiosResponse<AfterScriptDetail>> => {
return http.post<AfterScriptDetail>({
url: '/dataset/script/',
data: data
})
}
/**
*
* @param id ID
*/
export const deleteAfterScriptApi = async (
id: string
): Promise<AxiosResponse<AfterScriptDetail>> => {
return http.post<AfterScriptDetail>({
url: `/dataset/script/${id}/`
})
}

View File

@ -0,0 +1,29 @@
import {
createRestDataApi,
deleteRestDataApi,
getRestDataApi,
getRestDataListApi,
updateRestDataApi
} from './restData'
import {
createStaticDataApi,
deleteStaticDataApi,
getStaticDataApi,
getStaticDataListApi,
updateStaticDataApi
} from './staticData'
import type { StaticDataDetail } from './type'
export {
createRestDataApi,
createStaticDataApi,
deleteRestDataApi,
deleteStaticDataApi,
getRestDataApi,
getRestDataListApi,
getStaticDataApi,
getStaticDataListApi,
updateRestDataApi,
updateStaticDataApi
}
export type { StaticDataDetail }

View File

@ -0,0 +1,62 @@
import type { AxiosResponse } from 'axios'
import { apiHttp as http } from '@/utils/http'
import type { RestDataDetail } from './type'
/**
* id Rest
* @param id RestID
*/
export const getRestDataApi = async (id: string): Promise<AxiosResponse<RestDataDetail>> => {
return http.get<RestDataDetail>({
url: `/dataset/rest/${id}/`
})
}
/**
* Rest
*/
export const getRestDataListApi = async (): Promise<AxiosResponse<RestDataDetail[]>> => {
return http.get<RestDataDetail[]>({
url: '/dataset/rest'
})
}
/**
* Rest
* @param id Restid
* @param data Rest
*/
export const updateRestDataApi = async (
id: string,
data: Partial<Omit<RestDataDetail, 'id' | 'author' | 'createDate' | 'updateDate'>>
): Promise<AxiosResponse<RestDataDetail>> => {
return http.put<RestDataDetail>({
url: `/dataset/rest/${id}/`,
data: data
})
}
/**
* Rest
* @param data Rest
*/
export const createRestDataApi = async (
data: Omit<RestDataDetail, 'id' | 'author' | 'createDate' | 'updateDate'>
): Promise<AxiosResponse<RestDataDetail>> => {
return http.post<RestDataDetail>({
url: '/dataset/rest/',
data: data
})
}
/**
* Rest
* @param id RestID
*/
export const deleteRestDataApi = async (id: string): Promise<AxiosResponse<RestDataDetail>> => {
return http.delete<RestDataDetail>({
url: `/dataset/rest/${id}/`
})
}

View File

@ -0,0 +1,62 @@
import type { AxiosResponse } from 'axios'
import { apiHttp as http } from '@/utils/http'
import type { StaticDataDetail } from './type'
/**
* id
* @param id ID
*/
export const getStaticDataApi = async (id: string): Promise<AxiosResponse<StaticDataDetail>> => {
return http.get<StaticDataDetail>({
url: `/dataset/static/${id}/`
})
}
/**
*
*/
export const getStaticDataListApi = async (): Promise<AxiosResponse<StaticDataDetail[]>> => {
return http.get<StaticDataDetail[]>({
url: '/dataset/static/'
})
}
/**
*
* @param id id
* @param data
*/
export const updateStaticDataApi = async (
id: string,
data: Partial<Pick<StaticDataDetail, 'name' | 'data'>>
): Promise<AxiosResponse<StaticDataDetail>> => {
return http.put<StaticDataDetail>({
url: `/dataset/static/${id}/`,
data: { ...data }
})
}
/**
*
* @param data
*/
export const createStaticDataApi = async (
data: Pick<StaticDataDetail, 'name' | 'data'>
): Promise<AxiosResponse<StaticDataDetail>> => {
return http.post<StaticDataDetail>({
url: '/dataset/static/',
data: data
})
}
/**
*
* @param id id
*/
export const deleteStaticDataApi = async (id: string): Promise<AxiosResponse<StaticDataDetail>> => {
return http.post<StaticDataDetail>({
url: `/dataset/static/${id}/`
})
}

View File

@ -0,0 +1,36 @@
import type { Method } from 'axios'
export interface AfterScript {
code: string
}
export interface StaticDataDetail {
readonly id: string
name: string
readonly author: string
readonly createDate: string
readonly updateDate: string
data: any
}
export interface RestDataDetail {
method: Method
url: string
headers: Record<string, any>
params: Record<string, any>
data: Record<string, any>
afterScript?: AfterScript
readonly id: string
name: string
readonly author: string
readonly createDate: string
readonly updateDate: string
}
export interface AfterScriptDetail extends AfterScript {
readonly id: string
name: string
readonly author?: string
readonly createDate?: string
readonly updateDate?: string
}

View File

@ -0,0 +1,24 @@
import type { AxiosResponse } from 'axios'
import { apiHttp as http } from '@/utils/http'
import type { ImageFile } from './type'
// 获取页面数据
export const getImageListApi = async (): Promise<AxiosResponse<ImageFile[]>> => {
return http.get<ImageFile[]>({
url: '/image/image/'
})
}
export const uploadImageFileApi = async (
file: FormData
): Promise<AxiosResponse<{ url: string }>> => {
return http.post<{ url: string }>({
url: '/image/uploadImage/',
data: file,
headers: {
CONTENT_TYPE: 'multipart/form-data'
}
})
}

View File

@ -0,0 +1,5 @@
import { getImageListApi, uploadImageFileApi } from './image'
import type { ImageFile } from './type'
export { getImageListApi, uploadImageFileApi }
export type { ImageFile }

View File

@ -0,0 +1,7 @@
export interface ImageFile {
id: string
name: string
md5: string
url: string
createTime: string
}

View File

@ -0,0 +1,5 @@
import { deletePageApi, getPageApi, getPageListApi, savePageApi, updatePageApi } from './pages'
import type { LayoutData, SimpleLayoutData } from './type'
export type { LayoutData, SimpleLayoutData }
export { deletePageApi, getPageApi, getPageListApi, savePageApi, updatePageApi }

View File

@ -0,0 +1,61 @@
import type { AxiosResponse } from 'axios'
import { apiHttp as http } from '@/utils/http'
import type { LayoutData, SimpleLayoutData } from './type'
/***
*
* @param index ID
* url: `/api/api/report/list/${index}` `/page/page/${index}/`
*/
export const getPageApi = async (index: string): Promise<AxiosResponse<string>> => {
return http.get<string>({
url: `/api/api/report/list/${index}`
})
}
/***
*
*/
export const getPageListApi = async (): Promise<AxiosResponse<SimpleLayoutData[]>> => {
return http.get({
url: '/api/api/report/list'
})
}
/**
*
* @param componentData
*/
export const savePageApi = (componentData: LayoutData): Promise<AxiosResponse<LayoutData>> => {
return http.post<LayoutData>({
url: '/api/api/report',
data: componentData
})
}
/**
*
* @param id ID
* @param componentData
*/
export const updatePageApi = (
id: string,
componentData: LayoutData
): Promise<AxiosResponse<LayoutData>> => {
return http.put<LayoutData>({
url: `/api/api/report/${id}`,
data: componentData
})
}
/**
*
* @param id ID
*/
export const deletePageApi = (id: string): Promise<AxiosResponse<string>> => {
return http.delete<string>({
url: `/api/api/report/${id}`
})
}

View File

@ -0,0 +1,17 @@
import type { ComponentDataType } from 'open-data-v/base'
import type { CanvasStyleData } from 'open-data-v/designer'
export interface LayoutData {
id?: string
name?: string
thumbnail?: string
author?: string
createTime?: string
isHome?: boolean
canvasData: ComponentDataType[]
canvasStyle: CanvasStyleData
isPublish?: boolean
dataSlotters: Array<{ type: string; config: any }>
}
export type SimpleLayoutData = Omit<LayoutData, 'canvasStyle' | 'canvasData'>

View File

@ -0,0 +1,3 @@
import { loginApi } from './user'
export { loginApi }

View File

@ -0,0 +1,16 @@
import type { AxiosResponse } from 'axios'
import type { LoginData } from '@/types/user'
import { apiHttp as http } from '@/utils/http'
//
/***
*
* @param data
*/
export const loginApi = async (data: LoginData): Promise<AxiosResponse<string>> => {
return http.post<string>({
url: '/user/login',
data: data
})
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

View File

@ -0,0 +1,538 @@
/* Logo 字体 */
@font-face {
font-family: "iconfont logo";
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834');
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') format('embedded-opentype'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') format('svg');
}
.logo {
font-family: "iconfont logo";
font-size: 160px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* tabs */
.nav-tabs {
position: relative;
}
.nav-tabs .nav-more {
position: absolute;
right: 0;
bottom: 0;
height: 42px;
line-height: 42px;
color: #666;
}
#tabs {
border-bottom: 1px solid #eee;
}
#tabs li {
cursor: pointer;
width: 100px;
height: 40px;
line-height: 40px;
text-align: center;
font-size: 16px;
border-bottom: 2px solid transparent;
position: relative;
z-index: 1;
margin-bottom: -1px;
color: #666;
}
#tabs .active {
border-bottom-color: #f00;
color: #222;
}
.tab-container .content {
display: none;
}
/* 页面布局 */
.main {
padding: 30px 100px;
width: 960px;
margin: 0 auto;
}
.main .logo {
color: #333;
text-align: left;
margin-bottom: 30px;
line-height: 1;
height: 110px;
margin-top: -50px;
overflow: hidden;
*zoom: 1;
}
.main .logo a {
font-size: 160px;
color: #333;
}
.helps {
margin-top: 40px;
}
.helps pre {
padding: 20px;
margin: 10px 0;
border: solid 1px #e7e1cd;
background-color: #fffdef;
overflow: auto;
}
.icon_lists {
width: 100% !important;
overflow: hidden;
*zoom: 1;
}
.icon_lists li {
width: 100px;
margin-bottom: 10px;
margin-right: 20px;
text-align: center;
list-style: none !important;
cursor: default;
}
.icon_lists li .code-name {
line-height: 1.2;
}
.icon_lists .icon {
display: block;
height: 100px;
line-height: 100px;
font-size: 42px;
margin: 10px auto;
color: #333;
-webkit-transition: font-size 0.25s linear, width 0.25s linear;
-moz-transition: font-size 0.25s linear, width 0.25s linear;
transition: font-size 0.25s linear, width 0.25s linear;
}
.icon_lists .icon:hover {
font-size: 100px;
}
.icon_lists .svg-icon {
/* 通过设置 font-size 来改变图标大小 */
width: 1em;
/* 图标和文字相邻时,垂直对齐 */
vertical-align: -0.15em;
/* 通过设置 color 来改变 SVG 的颜色/fill */
fill: currentColor;
/* path stroke viewBox IE
normalize.css */
overflow: hidden;
}
.icon_lists li .name,
.icon_lists li .code-name {
color: #666;
}
/* markdown 样式 */
.markdown {
color: #666;
font-size: 14px;
line-height: 1.8;
}
.highlight {
line-height: 1.5;
}
.markdown img {
vertical-align: middle;
max-width: 100%;
}
.markdown h1 {
color: #404040;
font-weight: 500;
line-height: 40px;
margin-bottom: 24px;
}
.markdown h2,
.markdown h3,
.markdown h4,
.markdown h5,
.markdown h6 {
color: #404040;
margin: 1.6em 0 0.6em 0;
font-weight: 500;
clear: both;
}
.markdown h1 {
font-size: 28px;
}
.markdown h2 {
font-size: 22px;
}
.markdown h3 {
font-size: 16px;
}
.markdown h4 {
font-size: 14px;
}
.markdown h5 {
font-size: 12px;
}
.markdown h6 {
font-size: 12px;
}
.markdown hr {
height: 1px;
border: 0;
background: #e9e9e9;
margin: 16px 0;
clear: both;
}
.markdown p {
margin: 1em 0;
}
.markdown>p,
.markdown>blockquote,
.markdown>.highlight,
.markdown>ol,
.markdown>ul {
width: 80%;
}
.markdown ul>li {
list-style: circle;
}
.markdown>ul li,
.markdown blockquote ul>li {
margin-left: 20px;
padding-left: 4px;
}
.markdown>ul li p,
.markdown>ol li p {
margin: 0.6em 0;
}
.markdown ol>li {
list-style: decimal;
}
.markdown>ol li,
.markdown blockquote ol>li {
margin-left: 20px;
padding-left: 4px;
}
.markdown code {
margin: 0 3px;
padding: 0 5px;
background: #eee;
border-radius: 3px;
}
.markdown strong,
.markdown b {
font-weight: 600;
}
.markdown>table {
border-collapse: collapse;
border-spacing: 0px;
empty-cells: show;
border: 1px solid #e9e9e9;
width: 95%;
margin-bottom: 24px;
}
.markdown>table th {
white-space: nowrap;
color: #333;
font-weight: 600;
}
.markdown>table th,
.markdown>table td {
border: 1px solid #e9e9e9;
padding: 8px 16px;
text-align: left;
}
.markdown>table th {
background: #F7F7F7;
}
.markdown blockquote {
font-size: 90%;
color: #999;
border-left: 4px solid #e9e9e9;
padding-left: 0.8em;
margin: 1em 0;
}
.markdown blockquote p {
margin: 0;
}
.markdown .anchor {
opacity: 0;
transition: opacity 0.3s ease;
margin-left: 8px;
}
.markdown .waiting {
color: #ccc;
}
.markdown h1:hover .anchor,
.markdown h2:hover .anchor,
.markdown h3:hover .anchor,
.markdown h4:hover .anchor,
.markdown h5:hover .anchor,
.markdown h6:hover .anchor {
opacity: 1;
display: inline-block;
}
.markdown>br,
.markdown>p>br {
clear: both;
}
.hljs {
display: block;
background: white;
padding: 0.5em;
color: #333333;
overflow-x: auto;
}
.hljs-comment,
.hljs-meta {
color: #969896;
}
.hljs-string,
.hljs-variable,
.hljs-template-variable,
.hljs-strong,
.hljs-emphasis,
.hljs-quote {
color: #df5000;
}
.hljs-keyword,
.hljs-selector-tag,
.hljs-type {
color: #a71d5d;
}
.hljs-literal,
.hljs-symbol,
.hljs-bullet,
.hljs-attribute {
color: #0086b3;
}
.hljs-section,
.hljs-name {
color: #63a35c;
}
.hljs-tag {
color: #333333;
}
.hljs-title,
.hljs-attr,
.hljs-selector-id,
.hljs-selector-class,
.hljs-selector-attr,
.hljs-selector-pseudo {
color: #795da3;
}
.hljs-addition {
color: #55a532;
background-color: #eaffea;
}
.hljs-deletion {
color: #bd2c00;
background-color: #ffecec;
}
.hljs-link {
text-decoration: underline;
}
/* 代码高亮 */
/* PrismJS 1.15.0
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */
/**
* prism.js default theme for JavaScript, CSS and HTML
* Based on dabblet (http://dabblet.com)
* @author Lea Verou
*/
code[class*="language-"],
pre[class*="language-"] {
color: black;
background: none;
text-shadow: 0 1px white;
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
pre[class*="language-"]::-moz-selection,
pre[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection,
code[class*="language-"] ::-moz-selection {
text-shadow: none;
background: #b3d4fc;
}
pre[class*="language-"]::selection,
pre[class*="language-"] ::selection,
code[class*="language-"]::selection,
code[class*="language-"] ::selection {
text-shadow: none;
background: #b3d4fc;
}
@media print {
code[class*="language-"],
pre[class*="language-"] {
text-shadow: none;
}
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: .5em 0;
overflow: auto;
}
:not(pre)>code[class*="language-"],
pre[class*="language-"] {
background: #f5f2f0;
}
/* Inline code */
:not(pre)>code[class*="language-"] {
padding: .1em;
border-radius: .3em;
white-space: normal;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: slategray;
}
.token.punctuation {
color: #999;
}
.namespace {
opacity: .7;
}
.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.deleted {
color: #905;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: #690;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
color: #9a6e3a;
background: hsla(0, 0%, 100%, .5);
}
.token.atrule,
.token.attr-value,
.token.keyword {
color: #07a;
}
.token.function,
.token.class-name {
color: #DD4A68;
}
.token.regex,
.token.important,
.token.variable {
color: #e90;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,351 @@
@font-face {
font-family: "iconfont"; /* Project id 2946854 */
src: url('iconfont.woff2?t=1640069620094') format('woff2'),
url('iconfont.woff?t=1640069620094') format('woff'),
url('iconfont.ttf?t=1640069620094') format('truetype');
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-zu:before {
content: "\e854";
}
.icon-zhuxingtu:before {
content: "\e626";
}
.icon-yibiaopan:before {
content: "\eb67";
}
.icon-tubiao-bingtu:before {
content: "\eb95";
}
.icon-tubiao-qiapian:before {
content: "\eb96";
}
.icon-tubiao-zhexiantu:before {
content: "\eb97";
}
.icon-anniuzu:before {
content: "\e782";
}
.icon-daohang:before {
content: "\e77d";
}
.icon-liebiao:before {
content: "\ec6b";
}
.icon-caimeiji:before {
content: "\e603";
}
.icon-qita:before {
content: "\e63b";
}
.icon-dingzhi:before {
content: "\e609";
}
.icon-jichu:before {
content: "\e60f";
}
.icon-chartt:before {
content: "\e60a";
}
.icon-zhuangshizhuangxiu:before {
content: "\e6a6";
}
.icon-wenduji:before {
content: "\e6de";
}
.icon-tiaoxingjindutu:before {
content: "\e66b";
}
.icon-biankuang:before {
content: "\e6b7";
}
.icon-jindutu:before {
content: "\f24b";
}
.icon-jinggao1:before {
content: "\e601";
}
.icon-yujingdeng:before {
content: "\e67f";
}
.icon-zidongxitongjinggaodeng1:before {
content: "\e605";
}
.icon-yujing1:before {
content: "\e644";
}
.icon-yujing3:before {
content: "\e6fe";
}
.icon-jinggaodeng:before {
content: "\e64b";
}
.icon-baojing1:before {
content: "\e630";
}
.icon-hong:before {
content: "\e60d";
}
.icon-yujingdeng1:before {
content: "\e623";
}
.icon-a-baojingjinggaoyujingjinggaodeng:before {
content: "\e628";
}
.icon-light:before {
content: "\e63a";
}
.icon-gaojingdeng9:before {
content: "\e664";
}
.icon-jinggaodeng1:before {
content: "\e613";
}
.icon-yujing5:before {
content: "\e671";
}
.icon-shanchu:before {
content: "\e74b";
}
.icon-shanchu1:before {
content: "\e625";
}
.icon-shouye:before {
content: "\e62d";
}
.icon-chakan:before {
content: "\e600";
}
.icon-bianji:before {
content: "\e602";
}
.icon-fuzhi:before {
content: "\e608";
}
.icon-shouye-moren:before {
content: "\e619";
}
.icon-jiesuo:before {
content: "\e669";
}
.icon-xuanzhuan:before {
content: "\e680";
}
.icon-kaiguan:before {
content: "\e61f";
}
.icon-clock:before {
content: "\e63e";
}
.icon-fankuixinxi:before {
content: "\e660";
}
.icon-leijianzhuxiulix:before {
content: "\e695";
}
.icon-weibiaoti-:before {
content: "\e618";
}
.icon-sds_di37leijianzhuxiuli:before {
content: "\e61e";
}
.icon-liandongkongzhi:before {
content: "\e624";
}
.icon-unlock-full:before {
content: "\e882";
}
.icon-ascend:before {
content: "\e6b4";
}
.icon-falling:before {
content: "\e6b6";
}
.icon-buttonoff:before {
content: "\e614";
}
.icon-buttonon:before {
content: "\e615";
}
.icon-jiantoushang:before {
content: "\e62c";
}
.icon-jiantouyou:before {
content: "\e632";
}
.icon-jiantouxia:before {
content: "\e633";
}
.icon-24gl-next:before {
content: "\ea6b";
}
.icon-24gl-pauseCircle:before {
content: "\ea6f";
}
.icon-24gl-previous:before {
content: "\ea73";
}
.icon-24gl-stopCircle:before {
content: "\ea79";
}
.icon-anniu_kaiqi:before {
content: "\e659";
}
.icon-anniu_guanbi:before {
content: "\e65b";
}
.icon-jiantoushangzuo-copy:before {
content: "\ec63";
}
.icon-xiangzuo2-copy:before {
content: "\ec64";
}
.icon-guanbibofang:before {
content: "\e61d";
}
.icon-xiangshang3:before {
content: "\e76f";
}
.icon-xiangxia5:before {
content: "\e774";
}
.icon-xiangyou2:before {
content: "\e776";
}
.icon-youbofang:before {
content: "\e62e";
}
.icon-zuobofang:before {
content: "\e62f";
}
.icon-bofang2:before {
content: "\e606";
}
.icon-bofang5:before {
content: "\e607";
}
.icon-xiangzuojiaohuan:before {
content: "\e8f8";
}
.icon-xiangyoujiaohuan:before {
content: "\e8f9";
}
.icon-bofang6:before {
content: "\e87c";
}
.icon-24gl-playCircle:before {
content: "\ea6e";
}
.icon-xiangshang7:before {
content: "\e63c";
}
.icon-bofang26:before {
content: "\e60e";
}
.icon-play1:before {
content: "\ea8d";
}
.icon-bofang-kuaitui:before {
content: "\e68a";
}
.icon-bofang09:before {
content: "\e690";
}
.icon-bofang-kuaijin:before {
content: "\e6d8";
}
.icon-xiangxia7:before {
content: "\ec62";
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,597 @@
{
"id": "2946854",
"name": "方向",
"font_family": "iconfont",
"css_prefix_text": "icon-",
"description": "",
"glyphs": [
{
"icon_id": "11291350",
"name": "组",
"font_class": "zu",
"unicode": "e854",
"unicode_decimal": 59476
},
{
"icon_id": "1817751",
"name": "柱形图",
"font_class": "zhuxingtu",
"unicode": "e626",
"unicode_decimal": 58918
},
{
"icon_id": "3868284",
"name": "仪表盘",
"font_class": "yibiaopan",
"unicode": "eb67",
"unicode_decimal": 60263
},
{
"icon_id": "4354243",
"name": "图表-饼图",
"font_class": "tubiao-bingtu",
"unicode": "eb95",
"unicode_decimal": 60309
},
{
"icon_id": "4354246",
"name": "图表-卡片",
"font_class": "tubiao-qiapian",
"unicode": "eb96",
"unicode_decimal": 60310
},
{
"icon_id": "4354248",
"name": "图表-折线图",
"font_class": "tubiao-zhexiantu",
"unicode": "eb97",
"unicode_decimal": 60311
},
{
"icon_id": "4906240",
"name": "按钮组",
"font_class": "anniuzu",
"unicode": "e782",
"unicode_decimal": 59266
},
{
"icon_id": "5383645",
"name": "导航",
"font_class": "daohang",
"unicode": "e77d",
"unicode_decimal": 59261
},
{
"icon_id": "5961366",
"name": "列表",
"font_class": "liebiao",
"unicode": "ec6b",
"unicode_decimal": 60523
},
{
"icon_id": "5971297",
"name": "采煤机",
"font_class": "caimeiji",
"unicode": "e603",
"unicode_decimal": 58883
},
{
"icon_id": "7040619",
"name": "其他",
"font_class": "qita",
"unicode": "e63b",
"unicode_decimal": 58939
},
{
"icon_id": "7556180",
"name": "定制",
"font_class": "dingzhi",
"unicode": "e609",
"unicode_decimal": 58889
},
{
"icon_id": "9752796",
"name": "基础",
"font_class": "jichu",
"unicode": "e60f",
"unicode_decimal": 58895
},
{
"icon_id": "9921108",
"name": "chart",
"font_class": "chartt",
"unicode": "e60a",
"unicode_decimal": 58890
},
{
"icon_id": "14475731",
"name": "装饰装修",
"font_class": "zhuangshizhuangxiu",
"unicode": "e6a6",
"unicode_decimal": 59046
},
{
"icon_id": "16562592",
"name": "温度计",
"font_class": "wenduji",
"unicode": "e6de",
"unicode_decimal": 59102
},
{
"icon_id": "22712019",
"name": "条形进度图",
"font_class": "tiaoxingjindutu",
"unicode": "e66b",
"unicode_decimal": 58987
},
{
"icon_id": "22761375",
"name": "边框",
"font_class": "biankuang",
"unicode": "e6b7",
"unicode_decimal": 59063
},
{
"icon_id": "22885432",
"name": "进度图",
"font_class": "jindutu",
"unicode": "f24b",
"unicode_decimal": 62027
},
{
"icon_id": "124495",
"name": "警告",
"font_class": "jinggao1",
"unicode": "e601",
"unicode_decimal": 58881
},
{
"icon_id": "1846445",
"name": "预警灯",
"font_class": "yujingdeng",
"unicode": "e67f",
"unicode_decimal": 59007
},
{
"icon_id": "2239579",
"name": "自动系统警告灯",
"font_class": "zidongxitongjinggaodeng1",
"unicode": "e605",
"unicode_decimal": 58885
},
{
"icon_id": "4988542",
"name": "预警",
"font_class": "yujing1",
"unicode": "e644",
"unicode_decimal": 58948
},
{
"icon_id": "9974390",
"name": "预警",
"font_class": "yujing3",
"unicode": "e6fe",
"unicode_decimal": 59134
},
{
"icon_id": "10817515",
"name": "警告灯",
"font_class": "jinggaodeng",
"unicode": "e64b",
"unicode_decimal": 58955
},
{
"icon_id": "14151833",
"name": "报警",
"font_class": "baojing1",
"unicode": "e630",
"unicode_decimal": 58928
},
{
"icon_id": "16965399",
"name": "警告灯",
"font_class": "hong",
"unicode": "e60d",
"unicode_decimal": 58893
},
{
"icon_id": "20379044",
"name": "预警灯",
"font_class": "yujingdeng1",
"unicode": "e623",
"unicode_decimal": 58915
},
{
"icon_id": "23819336",
"name": "报警 警告 预警 警告灯",
"font_class": "a-baojingjinggaoyujingjinggaodeng",
"unicode": "e628",
"unicode_decimal": 58920
},
{
"icon_id": "24312218",
"name": "light",
"font_class": "light",
"unicode": "e63a",
"unicode_decimal": 58938
},
{
"icon_id": "24614518",
"name": "告警灯",
"font_class": "gaojingdeng9",
"unicode": "e664",
"unicode_decimal": 58980
},
{
"icon_id": "25567211",
"name": "警告灯",
"font_class": "jinggaodeng1",
"unicode": "e613",
"unicode_decimal": 58899
},
{
"icon_id": "12041083",
"name": "预警",
"font_class": "yujing5",
"unicode": "e671",
"unicode_decimal": 58993
},
{
"icon_id": "577357",
"name": "删除",
"font_class": "shanchu",
"unicode": "e74b",
"unicode_decimal": 59211
},
{
"icon_id": "1986988",
"name": "删除",
"font_class": "shanchu1",
"unicode": "e625",
"unicode_decimal": 58917
},
{
"icon_id": "12795401",
"name": "首页",
"font_class": "shouye",
"unicode": "e62d",
"unicode_decimal": 58925
},
{
"icon_id": "201556",
"name": "查看",
"font_class": "chakan",
"unicode": "e600",
"unicode_decimal": 58880
},
{
"icon_id": "201638",
"name": "编辑",
"font_class": "bianji",
"unicode": "e602",
"unicode_decimal": 58882
},
{
"icon_id": "11729723",
"name": "复制",
"font_class": "fuzhi",
"unicode": "e608",
"unicode_decimal": 58888
},
{
"icon_id": "7269302",
"name": "首页-默认",
"font_class": "shouye-moren",
"unicode": "e619",
"unicode_decimal": 58905
},
{
"icon_id": "658044",
"name": "解锁",
"font_class": "jiesuo",
"unicode": "e669",
"unicode_decimal": 58985
},
{
"icon_id": "10097949",
"name": "旋转",
"font_class": "xuanzhuan",
"unicode": "e680",
"unicode_decimal": 59008
},
{
"icon_id": "374774",
"name": "开关",
"font_class": "kaiguan",
"unicode": "e61f",
"unicode_decimal": 58911
},
{
"icon_id": "122162",
"name": "时钟",
"font_class": "clock",
"unicode": "e63e",
"unicode_decimal": 58942
},
{
"icon_id": "1242185",
"name": "反馈信息",
"font_class": "fankuixinxi",
"unicode": "e660",
"unicode_decimal": 58976
},
{
"icon_id": "2468505",
"name": "37类建筑修理x16",
"font_class": "leijianzhuxiulix",
"unicode": "e695",
"unicode_decimal": 59029
},
{
"icon_id": "11747267",
"name": "时间",
"font_class": "weibiaoti-",
"unicode": "e618",
"unicode_decimal": 58904
},
{
"icon_id": "14794143",
"name": "sds_第37类 建筑修理",
"font_class": "sds_di37leijianzhuxiuli",
"unicode": "e61e",
"unicode_decimal": 58910
},
{
"icon_id": "17762035",
"name": "联动控制",
"font_class": "liandongkongzhi",
"unicode": "e624",
"unicode_decimal": 58916
},
{
"icon_id": "18165278",
"name": "锁,密码,开锁,解锁",
"font_class": "unlock-full",
"unicode": "e882",
"unicode_decimal": 59522
},
{
"icon_id": "288554",
"name": "ascend",
"font_class": "ascend",
"unicode": "e6b4",
"unicode_decimal": 59060
},
{
"icon_id": "288556",
"name": "falling",
"font_class": "falling",
"unicode": "e6b6",
"unicode_decimal": 59062
},
{
"icon_id": "815867",
"name": "按钮-关",
"font_class": "buttonoff",
"unicode": "e614",
"unicode_decimal": 58900
},
{
"icon_id": "815872",
"name": "按钮-开",
"font_class": "buttonon",
"unicode": "e615",
"unicode_decimal": 58901
},
{
"icon_id": "2674878",
"name": "箭头 上",
"font_class": "jiantoushang",
"unicode": "e62c",
"unicode_decimal": 58924
},
{
"icon_id": "2674928",
"name": "箭头 右",
"font_class": "jiantouyou",
"unicode": "e632",
"unicode_decimal": 58930
},
{
"icon_id": "2674929",
"name": "箭头 下",
"font_class": "jiantouxia",
"unicode": "e633",
"unicode_decimal": 58931
},
{
"icon_id": "7594038",
"name": "24gl-next",
"font_class": "24gl-next",
"unicode": "ea6b",
"unicode_decimal": 60011
},
{
"icon_id": "7594046",
"name": "24gl-pauseCircle",
"font_class": "24gl-pauseCircle",
"unicode": "ea6f",
"unicode_decimal": 60015
},
{
"icon_id": "7594068",
"name": "24gl-previous",
"font_class": "24gl-previous",
"unicode": "ea73",
"unicode_decimal": 60019
},
{
"icon_id": "7594086",
"name": "24gl-stopCircle",
"font_class": "24gl-stopCircle",
"unicode": "ea79",
"unicode_decimal": 60025
},
{
"icon_id": "10268256",
"name": "按钮_开启",
"font_class": "anniu_kaiqi",
"unicode": "e659",
"unicode_decimal": 58969
},
{
"icon_id": "10268257",
"name": "按钮_关闭",
"font_class": "anniu_guanbi",
"unicode": "e65b",
"unicode_decimal": 58971
},
{
"icon_id": "25807963",
"name": "箭头 上",
"font_class": "jiantoushangzuo-copy",
"unicode": "ec63",
"unicode_decimal": 60515
},
{
"icon_id": "25808146",
"name": "向右2",
"font_class": "xiangzuo2-copy",
"unicode": "ec64",
"unicode_decimal": 60516
},
{
"icon_id": "166602",
"name": "关闭播放",
"font_class": "guanbibofang",
"unicode": "e61d",
"unicode_decimal": 58909
},
{
"icon_id": "577396",
"name": "向上3",
"font_class": "xiangshang3",
"unicode": "e76f",
"unicode_decimal": 59247
},
{
"icon_id": "577401",
"name": "向下5",
"font_class": "xiangxia5",
"unicode": "e774",
"unicode_decimal": 59252
},
{
"icon_id": "577403",
"name": "向右2",
"font_class": "xiangyou2",
"unicode": "e776",
"unicode_decimal": 59254
},
{
"icon_id": "1159910",
"name": "右播放",
"font_class": "youbofang",
"unicode": "e62e",
"unicode_decimal": 58926
},
{
"icon_id": "1159911",
"name": "左播放",
"font_class": "zuobofang",
"unicode": "e62f",
"unicode_decimal": 58927
},
{
"icon_id": "1185945",
"name": "播放",
"font_class": "bofang2",
"unicode": "e606",
"unicode_decimal": 58886
},
{
"icon_id": "1488892",
"name": "播放",
"font_class": "bofang5",
"unicode": "e607",
"unicode_decimal": 58887
},
{
"icon_id": "1727559",
"name": "323向左交换",
"font_class": "xiangzuojiaohuan",
"unicode": "e8f8",
"unicode_decimal": 59640
},
{
"icon_id": "1727561",
"name": "324向右交换",
"font_class": "xiangyoujiaohuan",
"unicode": "e8f9",
"unicode_decimal": 59641
},
{
"icon_id": "2076218",
"name": "播放2",
"font_class": "bofang6",
"unicode": "e87c",
"unicode_decimal": 59516
},
{
"icon_id": "7594051",
"name": "24gl-playCircle",
"font_class": "24gl-playCircle",
"unicode": "ea6e",
"unicode_decimal": 60014
},
{
"icon_id": "11121478",
"name": "向上",
"font_class": "xiangshang7",
"unicode": "e63c",
"unicode_decimal": 58940
},
{
"icon_id": "16388177",
"name": "播放",
"font_class": "bofang26",
"unicode": "e60e",
"unicode_decimal": 58894
},
{
"icon_id": "18176557",
"name": "播放",
"font_class": "play1",
"unicode": "ea8d",
"unicode_decimal": 60045
},
{
"icon_id": "24267227",
"name": "播放-快退",
"font_class": "bofang-kuaitui",
"unicode": "e68a",
"unicode_decimal": 59018
},
{
"icon_id": "24267260",
"name": "播放09",
"font_class": "bofang09",
"unicode": "e690",
"unicode_decimal": 59024
},
{
"icon_id": "24268374",
"name": "播放-快进",
"font_class": "bofang-kuaijin",
"unicode": "e6d8",
"unicode_decimal": 59096
},
{
"icon_id": "25807740",
"name": "向上",
"font_class": "xiangxia7",
"unicode": "ec62",
"unicode_decimal": 60514
}
]
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
<title>Group 5 Copy 5</title>
<desc>Created with Sketch.</desc>
<defs>
<filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox"
id="filter-1">
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1"
result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0" type="matrix"
in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
<feMerge>
<feMergeNode in="shadowMatrixOuter1"></feMergeNode>
<feMergeNode in="SourceGraphic"></feMergeNode>
</feMerge>
</filter>
<rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect>
<filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox"
id="filter-4">
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1"
result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0" type="matrix"
in="shadowBlurOuter1"></feColorMatrix>
</filter>
</defs>
<g id="配置面板" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="setting-copy-2" transform="translate(-1190.000000, -136.000000)">
<g id="Group-8" transform="translate(1167.000000, 0.000000)">
<g id="Group-5-Copy-5" filter="url(#filter-1)" transform="translate(25.000000, 137.000000)">
<mask id="mask-3" fill="white">
<use xlink:href="#path-2"></use>
</mask>
<g id="Rectangle-18">
<use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use>
<use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use>
</g>
<rect id="Rectangle-11" fill="#303648" mask="url(#mask-3)" x="-1" y="0" width="49"
height="10"></rect>
<rect id="Rectangle-18" fill="#303648" mask="url(#mask-3)" x="0" y="0" width="16"
height="44"></rect>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
<title>Group 5 Copy 5</title>
<desc>Created with Sketch.</desc>
<defs>
<filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox"
id="filter-1">
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1"
result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0" type="matrix"
in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
<feMerge>
<feMergeNode in="shadowMatrixOuter1"></feMergeNode>
<feMergeNode in="SourceGraphic"></feMergeNode>
</feMerge>
</filter>
<rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect>
<filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox"
id="filter-4">
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1"
result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0" type="matrix"
in="shadowBlurOuter1"></feColorMatrix>
</filter>
</defs>
<g id="配置面板" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="setting-copy-2" transform="translate(-1190.000000, -136.000000)">
<g id="Group-8" transform="translate(1167.000000, 0.000000)">
<g id="Group-5-Copy-5" filter="url(#filter-1)" transform="translate(25.000000, 137.000000)">
<mask id="mask-3" fill="white">
<use xlink:href="#path-2"></use>
</mask>
<g id="Rectangle-18">
<use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use>
<use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use>
</g>
<rect id="Rectangle-11" fill="#FFFFFF" mask="url(#mask-3)" x="-1" y="0" width="49"
height="10"></rect>
<rect id="Rectangle-18" fill="#303648" mask="url(#mask-3)" x="0" y="0" width="16"
height="44"></rect>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
<title>Group 5</title>
<desc>Created with Sketch.</desc>
<defs>
<filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox"
id="filter-1">
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1"
result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0" type="matrix"
in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
<feMerge>
<feMergeNode in="shadowMatrixOuter1"></feMergeNode>
<feMergeNode in="SourceGraphic"></feMergeNode>
</feMerge>
</filter>
<rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect>
<filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox"
id="filter-4">
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1"
result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0" type="matrix"
in="shadowBlurOuter1"></feColorMatrix>
</filter>
</defs>
<g id="配置面板" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="setting-copy-2" transform="translate(-1254.000000, -136.000000)">
<g id="Group-8" transform="translate(1167.000000, 0.000000)">
<g id="Group-5" filter="url(#filter-1)" transform="translate(89.000000, 137.000000)">
<mask id="mask-3" fill="white">
<use xlink:href="#path-2"></use>
</mask>
<g id="Rectangle-18">
<use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use>
<use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use>
</g>
<rect id="Rectangle-18" fill="#FFFFFF" mask="url(#mask-3)" x="0" y="0" width="16"
height="44"></rect>
<rect id="Rectangle-11" fill="#FFFFFF" mask="url(#mask-3)" x="-1" y="0" width="49"
height="10"></rect>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

View File

@ -0,0 +1,155 @@
<template>
<div class="editor">
<div class="tool-bar">
<slot name="tool-bar"> </slot>
</div>
<div class="main" :style="{ maxHeight: codemirrorConfig.height }">
<codemirror
:model-value="value || ''"
:style="{
width: '100%',
height: codemirrorConfig.height,
backgroundColor: '#fff',
color: '#333'
}"
placeholder="Please enter the code."
:extensions="extensions"
:autofocus="codemirrorConfig.autofocus"
:disabled="codemirrorConfig.disabled"
:indent-with-tab="codemirrorConfig.indentWithTab"
:tab-size="codemirrorConfig.tabSize"
@ready="handleReady"
@focus="log('focus', $event)"
@blur="log('blur', $event)"
@update:model-value="codeChange"
/>
</div>
<div class="footer"><slot name="footer"></slot></div>
</div>
</template>
<script lang="ts" setup>
import { redo, undo } from '@codemirror/commands'
import { javascript } from '@codemirror/lang-javascript'
import type { EditorState, Extension } from '@codemirror/state'
import { oneDark } from '@codemirror/theme-one-dark'
import type { EditorView, ViewUpdate } from '@codemirror/view'
import type { ComputedRef } from 'vue'
import { computed, inject } from 'vue'
import { Codemirror } from 'vue-codemirror'
import type { CodemirrorOption } from './type'
const Logger = console
const props = withDefaults(
defineProps<{
value?: string
height?: string
disabled?: boolean
mode?: 'debug' | 'use'
}>(),
{
value: '',
height: '600px',
disabled: false,
mode: 'use'
}
)
const codemirrorConfig = computed<CodemirrorOption>(() => {
return {
height: props.height,
tabSize: 4,
indentWithTab: true,
autofocus: true,
disabled: props.disabled,
line: false
}
})
const emits = defineEmits<{
(e: 'update:value', value: string): void
(e: 'change', value: string, viewUpdate: ViewUpdate): void
}>()
let cmView: EditorView
const darkTheme = inject<ComputedRef<boolean>>(
'DarkTheme',
computed(() => true)
)
const extensions = computed(() => {
const result: Extension[] = [javascript()]
if (darkTheme.value) {
result.push(oneDark)
}
return result
})
const handleReady = ({
view,
state: _state,
container: _container
}: {
view: EditorView
state: EditorState
container: HTMLDivElement
}) => {
cmView = view
return true
}
const log = Logger.log
const codeChange = (value: string, viewUpdate: ViewUpdate) => {
emits('update:value', value)
emits('change', value, viewUpdate)
return true
}
const handleRedo = () => {
if (cmView) {
redo({
state: cmView.state,
dispatch: cmView.dispatch
})
}
}
const handleUndo = () => {
if (cmView) {
undo({
state: cmView.state,
dispatch: cmView.dispatch
})
}
}
defineExpose({ handleRedo, handleUndo })
</script>
<style lang="less" scoped>
.editor {
.main {
display: flex;
overflow: hidden;
::-webkit-scrollbar {
/*滚动条整体样式*/
width: 6px; /*高宽分别对应横竖滚动条的尺寸*/
height: 6px;
}
::-webkit-scrollbar-thumb {
background-color: #8b8b8b;
-webkit-border-radius: 2em;
-moz-border-radius: 2em;
border-radius: 2em;
}
::-webkit-scrollbar-track {
background-color: #ccc;
}
.code {
width: 30%;
height: 100px;
margin: 0;
padding: 0.4em;
font-family: monospace;
}
}
}
</style>

Some files were not shown because too many files have changed in this diff Show More