项目初始化
This commit is contained in:
commit
36fbdc5ed2
32
.cz-config.js
Normal file
32
.cz-config.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
module.exports = {
|
||||||
|
types: [
|
||||||
|
{value: 'feat', name: 'feat: 新功能'},
|
||||||
|
{value: 'fix', name: 'fix: 修复'},
|
||||||
|
{value: 'docs', name: 'docs: 文档变更'},
|
||||||
|
{value: 'style', name: 'style: 代码格式(不影响代码运行的变动)'},
|
||||||
|
{value: 'cli', name: 'cli: 脚手架优化(不影响代码运行的变动)'},
|
||||||
|
{value: 'refactor', name: 'refactor: 重构(既不是增加feature,也不是修复bug)'},
|
||||||
|
{value: 'perf', name: 'perf: 性能优化'},
|
||||||
|
{value: 'test', name: 'test: 增加测试'},
|
||||||
|
{value: 'chore', name: 'chore: 构建过程或辅助工具的变动'},
|
||||||
|
{value: 'revert', name: 'revert: 回退'},
|
||||||
|
{value: 'build', name: 'build: 打包'}
|
||||||
|
],
|
||||||
|
// override the messages, defaults are as follows
|
||||||
|
messages: {
|
||||||
|
type: '请选择提交类型:',
|
||||||
|
scope: '请输入文件修改范围(可选):',
|
||||||
|
// used if allowCustomScopes is true
|
||||||
|
customScope: '请输入修改范围(可选):',
|
||||||
|
subject: '请简要描述提交(必填):',
|
||||||
|
body: '请输入详细描述(可选,待优化去除,跳过即可):',
|
||||||
|
// breaking: 'List any BREAKING CHANGES (optional):\n',
|
||||||
|
footer: '请输入要关闭的issue(待优化去除,跳过即可):',
|
||||||
|
confirmCommit: '确认使用以上信息提交?(y/n/e/h)'
|
||||||
|
},
|
||||||
|
allowCustomScopes: true,
|
||||||
|
// allowBreakingChanges: ['feat', 'fix'],
|
||||||
|
skipQuestions: ['body', 'footer'],
|
||||||
|
// limit subject length, commitlint默认是72
|
||||||
|
subjectLimit: 72
|
||||||
|
}
|
6
.eslintignore
Normal file
6
.eslintignore
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
dist
|
||||||
|
node_modules
|
||||||
|
**/dist
|
||||||
|
**/node_modules
|
||||||
|
**/examples
|
||||||
|
**/*.d.ts
|
30
.eslintrc.js
Normal file
30
.eslintrc.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
module.exports = {
|
||||||
|
'env': {
|
||||||
|
'browser': true,
|
||||||
|
'node': true,
|
||||||
|
'es6': true,
|
||||||
|
'jest': true,
|
||||||
|
'commonjs': true
|
||||||
|
},
|
||||||
|
'rules': {
|
||||||
|
'indent': [
|
||||||
|
'error',
|
||||||
|
4
|
||||||
|
],
|
||||||
|
'quotes': [
|
||||||
|
'error',
|
||||||
|
'single'
|
||||||
|
],
|
||||||
|
'block-spacing': 'error',
|
||||||
|
'no-unused-vars': 'warn',
|
||||||
|
'no-irregular-whitespace': 'warn',
|
||||||
|
'no-useless-escape': 'warn',
|
||||||
|
'no-empty': 'warn',
|
||||||
|
'object-curly-spacing': 'error',
|
||||||
|
'no-console': 'warn',
|
||||||
|
'vue/valid-v-model': 'warn',
|
||||||
|
'vue/no-template-key': 'warn',
|
||||||
|
'vue/valid-v-for': 'warn',
|
||||||
|
'vue/require-v-for-key': 'warn',
|
||||||
|
}
|
||||||
|
};
|
67
.gitignore
vendored
Normal file
67
.gitignore
vendored
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
stats.html
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# TypeScript v1 declaration files
|
||||||
|
typings/
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variables file
|
||||||
|
.env
|
||||||
|
|
||||||
|
# next.js build output
|
||||||
|
.next
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# custom
|
||||||
|
dist
|
||||||
|
/locale
|
187
LICENSE
Normal file
187
LICENSE
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
FormCreate设计器商业版 软件使用协议
|
||||||
|
|
||||||
|
西安锦强未来科技有限公司版权所有 (c) 2024-至今
|
||||||
|
|
||||||
|
使用FormCreate设计器商业版,必须遵守以下协议:
|
||||||
|
1. 被授权者在软件程序使用过程中应遵守中国现行法律法规,我们不对被授权者的经营行为负任何法律责任。
|
||||||
|
2. 免责声明:任何情况下根据相关法律,我们不对被授权者因使用本软件产生的数据损坏或丢失、软硬件故障和违法犯罪等问题承担任何责任。
|
||||||
|
3. 本授权仅限于被授权主体(个人、企业或组织)使用,未经授权不得使用、修改或移除版权信息。
|
||||||
|
4. 授权者务必尊重知识产权,严格保证不恶意传播产品源码、不得直接对授权的产品本身进行二次转售或倒卖、不得对授权的产品进行简单包装后声称为自己的产品等。否则我们有权利收回产品授权,并根据事态轻重追究相应法律责任。
|
||||||
|
5. 我们有义务为被授权者提供有效期内的产品下载、更新和维护,一旦过期,授权者无法享有相应权限。终身授权则不受限制。
|
||||||
|
6. 禁止进行反编译、逆向工程、破解或篡改本软件的授权机制。
|
||||||
|
7. 本协议及本协议任何条款内容的最终解释权及修改权归西安锦强未来科技有限公司所有。
|
||||||
|
|
||||||
|
本软件使用到的开源软件协议
|
||||||
|
|
||||||
|
|
||||||
|
Vue
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2018-present, Yuxi (Evan) You
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
|
@form-create/element-ui
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 xaboy
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
|
Element-plus
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2020 Element Plus
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
|
vuedraggable
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2016-2019 David Desmaisons
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
|
wangeditor
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2015-present wangeditor-team
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
|
codemirror
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (C) 2017 by Marijn Haverbeke <marijnh@gmail.com> and others
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
|
||||||
|
Highlight.js
|
||||||
|
BSD 3-Clause License
|
||||||
|
|
||||||
|
Copyright (c) 2006, Ivan Sagalaev.
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
* Neither the name of the copyright holder nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
102
README.md
Normal file
102
README.md
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
<p align="center">
|
||||||
|
<a href="https://www.form-create.com">
|
||||||
|
<img width="300" alt="FormCreate" src="https://static.form-create.com/file/img/info-logo2.png">
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://www.form-create.com/" target="_blank">官网</a>
|
||||||
|
<span> | </span>
|
||||||
|
<a href="https://pro.form-create.com/doc/" target="_blank">帮助文档</a>
|
||||||
|
<span> | </span>
|
||||||
|
<a href="https://pro.form-create.com/view/" target="_blank">可视化表单设计器</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
**FcDesigner Pro版是一款基于Vue的低代码可视化表单设计器工具,通过数据驱动表单渲染。可以通过拖拽的方式快速创建表单,提高开发者对表单的开发效率,节省开发者的时间。目前,在OA系统、ERP系统、电商系统、流程管理等系统中已稳定应用。**
|
||||||
|
|
||||||
|
-----
|
||||||
|
本项目采用 Vue3.0 和 ElementPlus 进行页面构建,内置多语言解决方案,支持二次扩展开发,支持自定义组件扩展。
|
||||||
|
|
||||||
|
|
||||||
|
## 特点
|
||||||
|
- 多语言配置
|
||||||
|
> 轻松在设计器中为表单各元素添加多语种文本,一键切换语言体系,打破语言隔阂,拓展全球业务版图,使您在应对全球化的过程中无忧无虑,轻松自如。
|
||||||
|
|
||||||
|
- 绑定事件
|
||||||
|
> 产品提供了可配置的组件和表单事件功能,为您处理各种动态交互提供了便利。无论用户需求何种复杂度与多样性,我们都能迎刃而解,提供满足使用者需求的解决方案。
|
||||||
|
|
||||||
|
- 丰富的组件
|
||||||
|
> 产品内置了50+种常用组件,广泛覆盖多种场景需求,以满足不同的用户需求。更为重要的是,我们支持灵活扩展自定义组件,以满足您独特、个人化的需求,为您提供更丰富的使用体验。
|
||||||
|
|
||||||
|
- 灵活的布局
|
||||||
|
> 产品提供了多种复杂表单布局方式,包括栅格、弹性盒子、表格等,这些功能让复杂的表单布局变得趋于简洁明了。此举不仅拓宽用户选择范围,更是为用户提供了贴心可靠的使用体验。
|
||||||
|
|
||||||
|
- 阅读模式
|
||||||
|
> 致力于实现表单编辑与数据查看模式的无缝切换,高效地提升代码复用性。这种改进将大大提高生产效率,同时也能让用户在任何情况下都能享受到流畅的使用体验。
|
||||||
|
|
||||||
|
- 公式计算
|
||||||
|
> 内置了52种常用的函数计算公式,这不仅可以大幅度提高数据分析效率,而且也能够灵活满足您在实际业务中的特定计算需求,从而保证数据的准确有效性。
|
||||||
|
|
||||||
|
- 数据联动
|
||||||
|
> 提供更灵活的条件设置和组件值联动功能。用户可为组件设置条件,条件满足时触发联动显示,例如动态展示其他组件的值,实现组件间值的实时同步。
|
||||||
|
|
||||||
|
- 可视化
|
||||||
|
> 产品以可视化操作为主导,使您可以轻而易举地完成表单页面的编辑。通过直观的图形界面,无需深入繁琐复杂的代码便可完成操作,大大降低了使用门槛,让编辑工作变得更轻松、更高效。
|
||||||
|
|
||||||
|
- 行内布局
|
||||||
|
> 行内布局功能打破传统表单组件单一纵向堆叠模式,通过简洁直观的操作界面,允许开发者自由拖拽组件,使其能够在同一行内 “并肩齐驱”。
|
||||||
|
|
||||||
|
- 5 种主题色切换
|
||||||
|
> 预设5种主题色(蓝色、绿色、橙色、紫色、粉色主题),用户可以一键切换表单主题风格,多样化的主题选择,不仅美观、易用,同时也可以满足不同场景和品牌风格需求。
|
||||||
|
|
||||||
|
## 编译文件
|
||||||
|
|
||||||
|
```
|
||||||
|
├─dist 编译文件目录
|
||||||
|
│ ├─index.[es|umd].js 完整包,包含PC端设计器+移动端自适应预览,需要安装vant和@form-create/vant
|
||||||
|
│ ├─pc/index.[es|umd].js PC端设计器,不包含移动端自适应预览,无需安装vant和@form-create/vant
|
||||||
|
│ ├─render/vant/form-create.[es|umd].js 移动端runtime环境的渲染器, 无需导入设计器
|
||||||
|
│ ├─render/element-plus/form-create.[es|umd].js PC端runtime环境的渲染器, 无需导入设计器
|
||||||
|
│
|
||||||
|
```
|
||||||
|
|
||||||
|
## 命令说明
|
||||||
|
|
||||||
|
运行开发环境
|
||||||
|
```
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
完整打包设计器
|
||||||
|
```
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
打包多语言文件
|
||||||
|
```
|
||||||
|
npm run build:locale
|
||||||
|
```
|
||||||
|
|
||||||
|
打包PC端渲染器
|
||||||
|
```
|
||||||
|
npm run build:elm
|
||||||
|
```
|
||||||
|
|
||||||
|
打包移动端渲染器
|
||||||
|
```
|
||||||
|
npm run build:mobile
|
||||||
|
```
|
||||||
|
|
||||||
|
打包预览页面,输出 index.html
|
||||||
|
```
|
||||||
|
npm run build:preview
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## 版权声明
|
||||||
|
|
||||||
|
FcDesigner Pro是由西安锦强未来科技有限公司的FormCreate团队负责的更新与维护,若需在您的项目中应用,需购买我们[授权](https://www.form-create.com/price.html)。
|
||||||
|
|
||||||
|
## 联系
|
||||||
|
|
||||||
|

|
4
babel.config.js
Normal file
4
babel.config.js
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
module.exports = {
|
||||||
|
'presets': [['@vue/cli-plugin-babel/preset', {'useBuiltIns': false}]],
|
||||||
|
'plugins': ['@vue/babel-plugin-jsx']
|
||||||
|
}
|
1134
examples/App.vue
Normal file
1134
examples/App.vue
Normal file
File diff suppressed because one or more lines are too long
187
examples/components/AiTool.vue
Normal file
187
examples/components/AiTool.vue
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
<script>
|
||||||
|
import {defineComponent} from 'vue'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: "AiTool",
|
||||||
|
emits: ['chat'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
aiIndex: 0,
|
||||||
|
aiPlaceholders: ['追加一个用户信息表单', '生成一个商品表单,并且增加品牌信息', '当单选框选中"选项1"时显示输入框组件', '输入框必填且长度必须大于13,否则提示错误信息', '修改输入框名称为商品名称'],
|
||||||
|
aiPlaceholder: '请告诉我您希望生成的表单内容,例如:追加一个用户信息表单',
|
||||||
|
showAi: true,
|
||||||
|
message: '',
|
||||||
|
aiLoading: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
toChat() {
|
||||||
|
this.$emit('chat');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.key = setInterval(() => {
|
||||||
|
this.aiPlaceholder = '请告诉我您希望生成的表单内容,例如:' + this.aiPlaceholders[(++this.aiIndex) % this.aiPlaceholders.length];
|
||||||
|
}, 6000);
|
||||||
|
|
||||||
|
},
|
||||||
|
unmounted() {
|
||||||
|
clearInterval(this.key);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="ai-tool" v-if="showAi">
|
||||||
|
<div class="ai-tool-con">
|
||||||
|
<el-input :placeholder="aiPlaceholder" readonly @click="toChat">
|
||||||
|
<template #suffix>
|
||||||
|
<i class="fc-icon icon-send" @click="toChat"
|
||||||
|
:class="{disabled:!message || !message.trim()}"></i>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
<svg @click="showAi = false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
||||||
|
<path fill="currentColor"
|
||||||
|
d="M764.288 214.592 512 466.88 259.712 214.592a31.936 31.936 0 0 0-45.12 45.12L466.752 512 214.528 764.224a31.936 31.936 0 1 0 45.12 45.184L512 557.184l252.288 252.288a31.936 31.936 0 0 0 45.12-45.12L557.12 512.064l252.288-252.352a31.936 31.936 0 1 0-45.12-45.184z"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ai-tool-btn" @click="showAi = true" v-else>
|
||||||
|
<svg class="fc-icon" viewBox="0 0 1331 1024" width="20" height="20">
|
||||||
|
<path
|
||||||
|
d="M653.067264 89.6a371.3536 371.3536 0 0 0-362.5984 291.328l-4.7616 21.6576-21.1456 6.7072A268.8 268.8 0 0 0 345.867264 934.4h614.4a268.8 268.8 0 0 0 81.3056-525.1072l-21.1456-6.656-4.7616-21.7088a371.3536 371.3536 0 0 0-362.5984-291.328zM220.632064 343.3984a448.1536 448.1536 0 0 1 864.8704 0A345.7024 345.7024 0 0 1 960.267264 1011.2h-614.4A345.6 345.6 0 0 1 220.632064 343.3984z"
|
||||||
|
fill="#FFFFFF"></path>
|
||||||
|
<path
|
||||||
|
d="M909.067264 805.1712v10.9568h-170.24v-10.9056h14.0288c6.9632 0 13.1072-0.512 18.3296-1.3312a24.8832 24.8832 0 0 0 13.568-7.4752 41.0624 41.0624 0 0 0 7.8848-17.92c2.048-8.192 3.072-19.0976 3.072-32.768V488.7552a231.936 231.936 0 0 0-0.8704-22.3232 61.952 61.952 0 0 0-2.2016-14.848 17.7664 17.7664 0 0 0-4.352-8.7552 33.1776 33.1776 0 0 0-8.2944-6.6048 56.9856 56.9856 0 0 0-27.136-6.9632h-14.0288V418.304h170.24v10.9568h-14.0288c-6.9632 0-13.2608 0.5632-18.7904 1.7408a23.552 23.552 0 0 0-13.1584 7.8848 41.1648 41.1648 0 0 0-8.2944 17.92 153.6512 153.6512 0 0 0-2.6112 31.9488v256.8704c0 9.3184 0.1536 16.896 0.4096 22.7328 0.3072 5.888 1.024 10.7008 2.2016 14.4896a24.832 24.832 0 0 0 4.8128 9.1648 52.8384 52.8384 0 0 0 35.4304 13.1072h14.0288z"
|
||||||
|
fill="#FFFFFF"></path>
|
||||||
|
<path
|
||||||
|
d="M726.027264 405.504H921.867264v36.608h-26.8288c-6.2464 0-11.6224 0.512-16.0768 1.4336a10.8032 10.8032 0 0 0-6.144 3.6864l-0.3072 0.3584a29.2352 29.2352 0 0 0-5.2736 12.1856 142.592 142.592 0 0 0-2.2528 29.0304v256.8704c0 9.216 0.1536 16.5888 0.4096 22.1184a47.9744 47.9744 0 0 0 1.6384 11.3152 14.2848 14.2848 0 0 0 1.792 3.9936 40.0896 40.0896 0 0 0 26.2144 9.3184H921.867264v36.5056h-195.84v-36.5568h26.8288c6.3488 0 11.6224-0.3584 15.9744-1.024a12.0832 12.0832 0 0 0 6.4-3.4304 29.7472 29.7472 0 0 0 4.9152-12.1856l0.1024-0.3584c1.6896-6.656 2.6624-16.4864 2.6624-29.696V488.8064c0-8.8064-0.256-15.8208-0.8192-21.0432l-0.0512-0.6656a50.3808 50.3808 0 0 0-1.6384-11.7248l-0.1024-0.4608-0.1024-0.4096c-0.4608-1.8944-1.024-2.6112-1.024-2.6112l-0.256-0.3072-0.256-0.3072a20.1728 20.1728 0 0 0-4.9152-3.7888 44.0832 44.0832 0 0 0-20.8896-5.376h-26.8288V405.504z m69.632 397.824c4.6592-6.0416 7.6288-13.6192 9.472-21.9136 2.3552-9.5744 3.3792-21.6064 3.3792-35.7376V488.8064c0-9.1136-0.256-16.896-0.9216-23.2448a75.1616 75.1616 0 0 0-2.56-17.3056 30.3616 30.3616 0 0 0-10.3936-17.1008h58.2656c-5.12 5.9904-8.3456 13.9776-10.496 22.5792l-0.1024 0.3584c-2.048 9.216-2.9184 20.8896-2.9184 34.7136v256.8704c0 9.4208 0.1536 17.2544 0.4608 23.3984 0.3072 6.4512 1.1264 12.3904 2.7136 17.5616 1.536 5.0688 3.8912 9.8304 7.424 13.824l0.512 0.6656 0.6656 0.5632 1.9968 1.6384h-57.4976z"
|
||||||
|
fill="#FFFFFF"></path>
|
||||||
|
<path
|
||||||
|
d="M564.696064 683.1104H410.584064l-27.136 62.5664c-6.4 15.4624-9.6256 26.9824-9.6256 34.56 0 3.2256 0.4608 6.144 1.3312 8.7552 1.1264 2.6112 3.328 5.12 6.5536 7.4752 3.4816 2.048 8.192 3.9424 13.9776 5.632 6.144 1.536 14.1824 2.56 24.064 3.072v10.9568H294.667264v-10.9056c9.9328-1.4848 17.92-3.6864 24.064-6.6048a48.64 48.64 0 0 0 15.7696-11.776 81.408 81.408 0 0 0 11.776-20.1728c3.84-8.192 8.3456-18.2272 13.6192-30.208L499.928064 409.6h10.0352l138.2912 330.3936c5.5296 13.7216 10.6496 24.7808 15.36 33.28 4.9152 8.192 9.8816 14.5408 14.848 19.2512 5.2224 4.608 10.752 7.8848 16.5888 9.6256 6.144 1.7408 13.4656 2.7648 21.9136 3.072v10.9056H559.883264v-10.9056c15.7184-0.6144 26.368-3.072 31.8976-7.4752a20.7872 20.7872 0 0 0 8.7552-16.64c-0.256-8.448-4.1984-22.1696-11.776-41.1136l-24.064-56.8832z m-8.3456-21.9136L488.971264 500.6848l-69.12 160.5632h136.4992z"
|
||||||
|
fill="#FFFFFF"></path>
|
||||||
|
<path
|
||||||
|
d="M491.480064 396.8h26.9824l141.6704 338.432c5.376 13.312 10.24 23.808 14.5408 31.5904 4.4544 7.3728 8.6528 12.6464 12.3904 16.2304 4.0448 3.5328 7.8848 5.632 11.6224 6.8096 4.864 1.3824 11.008 2.304 18.7392 2.56l12.288 0.4096v36.096H547.083264v-36.0448l12.288-0.512a94.208 94.208 0 0 0 17.408-1.9456 19.4048 19.4048 0 0 0 7.0656-2.7136c3.2256-2.56 3.84-4.608 3.8912-6.4-0.256-6.0416-3.3792-17.7664-10.8544-36.4544l-20.6848-48.9472H419.032064l-23.7568 54.784c-6.3488 15.2576-8.6016 24.7296-8.6016 29.5424 0 1.5872 0.1536 2.8672 0.4096 3.8912a9.6256 9.6256 0 0 0 1.6896 1.536c2.2528 1.28 5.632 2.6624 10.2912 4.096 5.12 1.1776 12.1856 2.0992 21.504 2.6624l12.032 0.7168v35.84H281.867264v-34.816l10.9568-1.6384c9.216-1.3312 15.872-3.2768 20.4288-5.4272a35.9424 35.9424 0 0 0 11.52-8.6016c3.328-4.096 6.656-9.5744 9.728-16.896l0.2048-0.3072c3.7376-8.0384 8.192-18.0224 13.4144-29.9008l143.36-334.592z m13.3632 33.7408l-68.3008 159.488L489.124864 467.968l86.528 206.1312h-0.9216l25.9072 61.184c7.5776 18.944 12.288 34.5088 12.6976 45.4656v0.4096a32.256 32.256 0 0 1-8.8064 22.2208h66.8672a83.3536 83.3536 0 0 1-1.4336-1.2288l-0.3072-0.256a104.704 104.704 0 0 1-17.0496-21.9648l-0.256-0.4608a327.1168 327.1168 0 0 1-15.9232-34.56L504.843264 430.592zM400.651264 673.9968l-5.2224 12.1344 5.2224-12.0832z m-31.744 73.8304c-4.096 9.2672-7.68 17.3056-10.8544 24.064a93.9008 93.9008 0 0 1-13.6704 23.1424l-0.256 0.256c-2.6112 2.9696-5.4784 5.632-8.6016 8.0384h34.6112a29.0304 29.0304 0 0 1-6.6048-9.1136L363.275264 793.6l-0.2048-0.6144a40.3456 40.3456 0 0 1-1.9456-12.8c0-8.704 2.9696-19.7632 7.7824-32.4096z m70.4-99.3792h97.8432l-48.2816-115.0976-49.5616 115.0976z"
|
||||||
|
fill="#FFFFFF"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.ai-tool {
|
||||||
|
position: fixed;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 120px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
z-index: 2021;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-tool-con {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-tool-con::before {
|
||||||
|
content: "";
|
||||||
|
background: linear-gradient(90deg, #FF0000, #FF8400, #FF8400, #0066FF, #0066FF, #FF8400, #FF8400, #FF0000);
|
||||||
|
background-size: 400%;
|
||||||
|
border-radius: 24px;
|
||||||
|
bottom: -2px;
|
||||||
|
left: -2px;
|
||||||
|
position: absolute;
|
||||||
|
right: -2px;
|
||||||
|
top: -2px;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-tool-con:has(.is-focus)::before {
|
||||||
|
animation: move-ab150fae 20s infinite ease;
|
||||||
|
filter: blur(5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-tool-con > svg {
|
||||||
|
height: 14px;
|
||||||
|
width: 14px;
|
||||||
|
background: #ccc;
|
||||||
|
border-radius: 15px;
|
||||||
|
padding: 4px;
|
||||||
|
position: absolute;
|
||||||
|
right: -6px;
|
||||||
|
top: -10px;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 2001;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-tool .el-input {
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 15px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-tool input {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-tool .el-loading-spinner {
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-tool .el-input__wrapper {
|
||||||
|
width: 100%;
|
||||||
|
box-shadow: none;
|
||||||
|
border-radius: 24px;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-tool .el-input__wrapper.is-focus {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-tool .el-input .fc-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 15px;
|
||||||
|
position: absolute;
|
||||||
|
right: 12px;
|
||||||
|
bottom: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
background: var(--fc-style-color-1);
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-tool .el-input .fc-icon.disabled {
|
||||||
|
background: var(--fc-text-color-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-tool .el-loading-mask {
|
||||||
|
border-radius: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-tool-btn {
|
||||||
|
position: fixed;
|
||||||
|
right: 0px;
|
||||||
|
bottom: 100px;
|
||||||
|
color: #FFF;
|
||||||
|
background-color: var(--fc-style-color-1);
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 4px;
|
||||||
|
border-radius: 5px 0px 0px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes move-ab150fae {
|
||||||
|
100% {
|
||||||
|
background-position: -400% 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
273
examples/components/MakeDragRule.vue
Normal file
273
examples/components/MakeDragRule.vue
Normal file
@ -0,0 +1,273 @@
|
|||||||
|
<script>
|
||||||
|
import {defineComponent} from 'vue'
|
||||||
|
import makeRule from '../rule'
|
||||||
|
import StructEditor from "../../src/components/StructEditor.vue";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: "MakeDragRule",
|
||||||
|
components: {StructEditor},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
visible: false,
|
||||||
|
formData: {},
|
||||||
|
rule: makeRule(),
|
||||||
|
value: {},
|
||||||
|
preview: false,
|
||||||
|
uni: 0,
|
||||||
|
options: {
|
||||||
|
"form": {
|
||||||
|
"inline": false,
|
||||||
|
"hideRequiredAsterisk": true,
|
||||||
|
"labelPosition": "right",
|
||||||
|
"size": "default",
|
||||||
|
"labelWidth": "125px"
|
||||||
|
},
|
||||||
|
"resetBtn": {
|
||||||
|
"show": false,
|
||||||
|
},
|
||||||
|
"submitBtn": {
|
||||||
|
"show": false,
|
||||||
|
},
|
||||||
|
"wrap": {
|
||||||
|
"style": {
|
||||||
|
"marginBottom": "8px"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ignoreHiddenFields": true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
preview(val) {
|
||||||
|
if (val) {
|
||||||
|
this.value = this.makeValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
makeValue() {
|
||||||
|
const dragRule = {
|
||||||
|
menu: 'aide',
|
||||||
|
icon: 'icon-tag',
|
||||||
|
name: this.formData.name,
|
||||||
|
label: this.formData.label,
|
||||||
|
}
|
||||||
|
if (this.formData.validate) {
|
||||||
|
dragRule.validate = this.formData.validate;
|
||||||
|
}
|
||||||
|
if (this.formData.event) {
|
||||||
|
dragRule.event = this.formData.event.map(item => item.name);
|
||||||
|
}
|
||||||
|
const rule = {
|
||||||
|
type: this.formData.name,
|
||||||
|
}
|
||||||
|
if (this.formData.type !== '1') {
|
||||||
|
rule.title = this.formData.label;
|
||||||
|
rule.field = this.formData.name;
|
||||||
|
rule.$required = false;
|
||||||
|
dragRule.menu = 'main';
|
||||||
|
dragRule.icon = 'icon-input';
|
||||||
|
}
|
||||||
|
rule.props = {};
|
||||||
|
if (this.formData.options && this.formData.options.length) {
|
||||||
|
rule.options = this.formData.options;
|
||||||
|
}
|
||||||
|
dragRule.rule = (new Function('return function () { return' + JSON.stringify(rule) + '}'))();
|
||||||
|
const props = [];
|
||||||
|
(this.formData.props || []).forEach((item, index) => {
|
||||||
|
const prop = {
|
||||||
|
type: item.props_type,
|
||||||
|
field: item.field,
|
||||||
|
title: item.name
|
||||||
|
}
|
||||||
|
if (props.type === 'select') {
|
||||||
|
prop.options = props.options || []
|
||||||
|
}
|
||||||
|
props.push(prop);
|
||||||
|
})
|
||||||
|
dragRule.props = (new Function('return function () { return' + JSON.stringify(props) + '}'))();
|
||||||
|
return dragRule;
|
||||||
|
},
|
||||||
|
demo1() {
|
||||||
|
this.uni++;
|
||||||
|
this.rule = makeRule();
|
||||||
|
this.formData = {
|
||||||
|
"type": "2",
|
||||||
|
"name": "input",
|
||||||
|
"label": "输入框",
|
||||||
|
"validate": [
|
||||||
|
"string"
|
||||||
|
],
|
||||||
|
"event": [
|
||||||
|
{
|
||||||
|
"name": "change"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "blur"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"props": [
|
||||||
|
{
|
||||||
|
"name": "类型",
|
||||||
|
"field": "type",
|
||||||
|
"props_type": "select",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"label": "输入框",
|
||||||
|
"value": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "多行输入框",
|
||||||
|
"value": "textarea"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "禁用",
|
||||||
|
"field": "disabled",
|
||||||
|
"props_type": "switch",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "占位显示",
|
||||||
|
"field": "placeholder",
|
||||||
|
"props_type": "input",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
demo2() {
|
||||||
|
this.uni++;
|
||||||
|
this.rule = makeRule();
|
||||||
|
this.formData = {
|
||||||
|
"type": "3",
|
||||||
|
"name": "checkbox",
|
||||||
|
"label": "多选框",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"label": "选项1",
|
||||||
|
"value": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "选项2",
|
||||||
|
"value": "2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "选项3",
|
||||||
|
"value": "3"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"validate": [
|
||||||
|
"array"
|
||||||
|
],
|
||||||
|
"event": [
|
||||||
|
{
|
||||||
|
"name": "change"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"props": [
|
||||||
|
{
|
||||||
|
"field": "disabled",
|
||||||
|
"name": "是否禁用",
|
||||||
|
"props_type": "switch"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"field": "type",
|
||||||
|
"name": "按钮类型",
|
||||||
|
"props_type": "select",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"label": "默认",
|
||||||
|
"value": "default"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "按钮",
|
||||||
|
"value": "button"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
demo3() {
|
||||||
|
this.uni++;
|
||||||
|
this.rule = makeRule();
|
||||||
|
this.formData = {
|
||||||
|
"type": "1",
|
||||||
|
"name": "button",
|
||||||
|
"label": "按钮",
|
||||||
|
"event": [
|
||||||
|
{
|
||||||
|
"name": "click"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"props": [
|
||||||
|
{
|
||||||
|
"field": "disabled",
|
||||||
|
"name": "是否禁用",
|
||||||
|
"props_type": "switch"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"field": "formCreateChild",
|
||||||
|
"name": "内容",
|
||||||
|
"props_type": "input",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="_fd-make-drag-rule">
|
||||||
|
<span @click="visible = true">生成拖拽规则</span>
|
||||||
|
<el-dialog class="_fd-make-drag-pop" v-model="visible">
|
||||||
|
<template #header>
|
||||||
|
生成拖拽规则 <span style="font-size: 12px;color:var(--fc-text-color-2);">可以生成任意 Vue 组件或 UI 组件的拖拽规则</span>
|
||||||
|
</template>
|
||||||
|
<template v-if="!preview">
|
||||||
|
<formCreate :key="uni" :rule="rule" v-model="formData" :option="options"></formCreate>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<StructEditor ref="editor" v-model="value"></StructEditor>
|
||||||
|
</template>
|
||||||
|
<template #footer>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<template v-if="!preview">
|
||||||
|
<el-button @click="demo1">示例1</el-button>
|
||||||
|
<el-button @click="demo2">示例2</el-button>
|
||||||
|
<el-button @click="demo3">示例3</el-button>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<el-button v-if="!preview" @click="preview = true" type="primary">下一步</el-button>
|
||||||
|
<el-button v-else @click="preview = false" type="primary">上一步</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fd-make-drag-rule > span {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--el-text-color-regular);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-make-drag-pop .el-dialog__body {
|
||||||
|
height: 500px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-make-drag-pop .el-dialog__footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-make-drag-pop .el-dialog__footer > div {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-make-drag-pop ._fd-struct-editor, ._fd-make-drag-pop .CodeMirror {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
154
examples/forms.js
Normal file
154
examples/forms.js
Normal file
File diff suppressed because one or more lines are too long
18
examples/index.html
Normal file
18
examples/index.html
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Element Plus版本低代码设计器FcDesigner Pro在线演示 | FormCreate</title>
|
||||||
|
<link rel="icon" href="/logo.png">
|
||||||
|
<meta name="keywords" content="vue,FormCreate,form-create-designer,fc-designer,开源,低代码,低代码表单,表单设计器,element-plus,element-ui">
|
||||||
|
<meta name="description" content="FcDesigner Pro版是一款基于Vue3.0的低代码可视化表单设计器工具,通过数据驱动表单渲染。可以通过拖拽的方式快速创建表单,提高开发者对表单的开发效率,节省开发者的时间。目前,在OA系统、ERP系统、电商系统、流程管理等系统中已稳定应用。">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app">
|
||||||
|
</div>
|
||||||
|
<script
|
||||||
|
type="module"
|
||||||
|
src="./main.js"
|
||||||
|
></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
54
examples/main.js
Normal file
54
examples/main.js
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import {createApp} from 'vue';
|
||||||
|
import ELEMENT from 'element-plus';
|
||||||
|
import vant from 'vant';
|
||||||
|
import 'element-plus/dist/index.css';
|
||||||
|
import 'vant/lib/index.css';
|
||||||
|
import formCreate from '@form-create/element-ui';
|
||||||
|
import App from './App.vue';
|
||||||
|
import FcDesigner from '../src/index';
|
||||||
|
import 'element-plus/theme-chalk/dark/css-vars.css'
|
||||||
|
// import install from "@form-create/element-ui/auto-import";
|
||||||
|
// import 'element-plus/es/components/message/style/css';
|
||||||
|
|
||||||
|
const app = createApp(App);
|
||||||
|
|
||||||
|
// formCreate.use(install);
|
||||||
|
app.use(ELEMENT);
|
||||||
|
app.use(vant);
|
||||||
|
app.use(formCreate);
|
||||||
|
app.use(FcDesigner);
|
||||||
|
FcDesigner.setFormula([
|
||||||
|
{
|
||||||
|
menu: 'math',
|
||||||
|
name: 'test',
|
||||||
|
info: '扩展自定义计算函数示例',
|
||||||
|
example: 'test(val) == !!val',
|
||||||
|
handle(val) {
|
||||||
|
return !!val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
FcDesigner.setBehavior([
|
||||||
|
{
|
||||||
|
menu: 'other',
|
||||||
|
name: 'test',
|
||||||
|
label: '扩展自定义行为',
|
||||||
|
info: '扩展自定义行为示例',
|
||||||
|
rule() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
field: 'custom',
|
||||||
|
title: '自定义配置'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
handle(config) {
|
||||||
|
console.log(config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
app.mount('#app')
|
342
examples/rule.js
Normal file
342
examples/rule.js
Normal file
@ -0,0 +1,342 @@
|
|||||||
|
export default function makeRule() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"type": "radio",
|
||||||
|
"field": "type",
|
||||||
|
"title": "组件类型",
|
||||||
|
"effect": {
|
||||||
|
"fetch": ""
|
||||||
|
},
|
||||||
|
"$required": false,
|
||||||
|
"props": {
|
||||||
|
"type": "button"
|
||||||
|
},
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"label": "表单组件",
|
||||||
|
"value": "2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "选项类表单组件",
|
||||||
|
"value": "3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "辅助组件",
|
||||||
|
"value": "1"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"_fc_id": "id_F89qm5ulpnp9adc",
|
||||||
|
"name": "ref_Fsdxm5ulpnp9aec",
|
||||||
|
"_fc_drag_tag": "radio",
|
||||||
|
"display": true,
|
||||||
|
"hidden": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "input",
|
||||||
|
"field": "name",
|
||||||
|
"title": "组件名称",
|
||||||
|
"$required": false,
|
||||||
|
"_fc_id": "id_F1njm5ultg7fajc",
|
||||||
|
"name": "ref_Fgcnm5ultg7fakc",
|
||||||
|
"_fc_drag_tag": "input",
|
||||||
|
"display": true,
|
||||||
|
"hidden": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "input",
|
||||||
|
"field": "label",
|
||||||
|
"title": "组件别名",
|
||||||
|
"$required": false,
|
||||||
|
"_fc_id": "id_F1nxm5uluzpfamc",
|
||||||
|
"name": "ref_Ftxnm5uluzpfanc",
|
||||||
|
"_fc_drag_tag": "input",
|
||||||
|
"display": true,
|
||||||
|
"hidden": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "tableForm",
|
||||||
|
"field": "options",
|
||||||
|
"title": "选择项",
|
||||||
|
"props": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"label": "名称",
|
||||||
|
"required": false,
|
||||||
|
"style": {
|
||||||
|
"width": "auto"
|
||||||
|
},
|
||||||
|
"rule": [
|
||||||
|
{
|
||||||
|
"type": "input",
|
||||||
|
"field": "label",
|
||||||
|
"title": "输入框",
|
||||||
|
"$required": false,
|
||||||
|
"_fc_id": "id_F0xkm5umkj3abyc",
|
||||||
|
"name": "ref_Fu3am5umbd2obsc",
|
||||||
|
"_fc_drag_tag": "input",
|
||||||
|
"display": true,
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "值",
|
||||||
|
"required": false,
|
||||||
|
"style": {
|
||||||
|
"width": "auto"
|
||||||
|
},
|
||||||
|
"rule": [
|
||||||
|
{
|
||||||
|
"type": "input",
|
||||||
|
"field": "value",
|
||||||
|
"title": "值",
|
||||||
|
"$required": false,
|
||||||
|
"_fc_id": "id_Fg4xm5unpihhcec",
|
||||||
|
"name": "ref_Fhrsm5unpihhcgc",
|
||||||
|
"_fc_drag_tag": "input",
|
||||||
|
"display": true,
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"_fc_id": "id_F1n2m5umkj3abzc",
|
||||||
|
"name": "ref_Ff4dm5umkj3abxc",
|
||||||
|
"_fc_drag_tag": "tableForm",
|
||||||
|
"display": true,
|
||||||
|
"hidden": false,
|
||||||
|
"computed": {
|
||||||
|
"hidden": {
|
||||||
|
"mode": "AND",
|
||||||
|
"group": [
|
||||||
|
{
|
||||||
|
"field": "type",
|
||||||
|
"condition": "==",
|
||||||
|
"value": "3"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"invert": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "checkbox",
|
||||||
|
"field": "validate",
|
||||||
|
"title": "Value的数据类型",
|
||||||
|
"effect": {
|
||||||
|
"fetch": ""
|
||||||
|
},
|
||||||
|
"$required": false,
|
||||||
|
"props": {
|
||||||
|
"type": "button"
|
||||||
|
},
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"label": "字符串",
|
||||||
|
"value": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "数字",
|
||||||
|
"value": "number"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "数组",
|
||||||
|
"value": "array"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"_fc_id": "id_Fs4dm5ulyrpmazc",
|
||||||
|
"name": "ref_F7q3m5ulyrpmb0c",
|
||||||
|
"_fc_drag_tag": "checkbox",
|
||||||
|
"display": true,
|
||||||
|
"hidden": false,
|
||||||
|
"computed": {
|
||||||
|
"hidden": {
|
||||||
|
"mode": "OR",
|
||||||
|
"group": [
|
||||||
|
{
|
||||||
|
"field": "type",
|
||||||
|
"condition": "!=",
|
||||||
|
"value": "1"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"invert": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "tableForm",
|
||||||
|
"field": "event",
|
||||||
|
"title": "事件",
|
||||||
|
"props": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"label": "事件名称",
|
||||||
|
"required": false,
|
||||||
|
"style": {
|
||||||
|
"width": "auto"
|
||||||
|
},
|
||||||
|
"rule": [
|
||||||
|
{
|
||||||
|
"type": "input",
|
||||||
|
"field": "name",
|
||||||
|
"title": "事件",
|
||||||
|
"$required": false,
|
||||||
|
"_fc_id": "id_Ffl9m5ulxopqawc",
|
||||||
|
"name": "ref_Fn0om5ulxopqaxc",
|
||||||
|
"_fc_drag_tag": "input",
|
||||||
|
"display": true,
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"_fc_id": "id_Fwcmm5ulxatsasc",
|
||||||
|
"name": "ref_Fwzrm5ulxatsatc",
|
||||||
|
"_fc_drag_tag": "tableForm",
|
||||||
|
"display": true,
|
||||||
|
"hidden": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "group",
|
||||||
|
"field": "props",
|
||||||
|
"title": "配置项",
|
||||||
|
"$required": false,
|
||||||
|
"props": {
|
||||||
|
"expand": 1,
|
||||||
|
"rule": [
|
||||||
|
{
|
||||||
|
"type": "input",
|
||||||
|
"field": "field",
|
||||||
|
"title": "字段名",
|
||||||
|
"$required": false,
|
||||||
|
"_fc_id": "id_Fl1ym5um4ygwb6c",
|
||||||
|
"name": "ref_F28mm5um4ygwb7c",
|
||||||
|
"_fc_drag_tag": "input",
|
||||||
|
"display": true,
|
||||||
|
"hidden": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "input",
|
||||||
|
"field": "name",
|
||||||
|
"title": "配置名称",
|
||||||
|
"$required": false,
|
||||||
|
"_fc_id": "id_Fl1ym5um4ygwb6c",
|
||||||
|
"name": "ref_F28mm5um4ygwb7c",
|
||||||
|
"_fc_drag_tag": "input",
|
||||||
|
"display": true,
|
||||||
|
"hidden": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "radio",
|
||||||
|
"field": "props_type",
|
||||||
|
"title": "配置类型",
|
||||||
|
"effect": {
|
||||||
|
"fetch": ""
|
||||||
|
},
|
||||||
|
"$required": false,
|
||||||
|
"props": {
|
||||||
|
"type": "button"
|
||||||
|
},
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"label": "输入框",
|
||||||
|
"value": "input"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "数字",
|
||||||
|
"value": "inputNumber"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "布尔",
|
||||||
|
"value": "switch"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "选项",
|
||||||
|
"value": "select"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"_fc_id": "id_Fl8zm5um9v76bkc",
|
||||||
|
"name": "ref_Fnaim5um9v76blc",
|
||||||
|
"_fc_drag_tag": "radio",
|
||||||
|
"display": true,
|
||||||
|
"hidden": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "tableForm",
|
||||||
|
"field": "options",
|
||||||
|
"title": "选择项",
|
||||||
|
"props": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"label": "名称",
|
||||||
|
"required": false,
|
||||||
|
"style": {
|
||||||
|
"width": "auto"
|
||||||
|
},
|
||||||
|
"rule": [
|
||||||
|
{
|
||||||
|
"type": "input",
|
||||||
|
"field": "label",
|
||||||
|
"title": "名称",
|
||||||
|
"$required": false,
|
||||||
|
"_fc_id": "id_Fffzm5umbd2obrc",
|
||||||
|
"name": "ref_Fu3am5umbd2obsc",
|
||||||
|
"_fc_drag_tag": "input",
|
||||||
|
"display": true,
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "值",
|
||||||
|
"required": false,
|
||||||
|
"style": {
|
||||||
|
"width": "auto"
|
||||||
|
},
|
||||||
|
"rule": [
|
||||||
|
{
|
||||||
|
"type": "input",
|
||||||
|
"field": "value",
|
||||||
|
"title": "值",
|
||||||
|
"$required": false,
|
||||||
|
"_fc_id": "id_Fbzcm5umprcqc2c",
|
||||||
|
"name": "ref_Fzc0m5umprcqc4c",
|
||||||
|
"_fc_drag_tag": "input",
|
||||||
|
"display": true,
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"_fc_id": "id_Fx1am5umaxcibnc",
|
||||||
|
"name": "ref_F5c0m5umaxciboc",
|
||||||
|
"_fc_drag_tag": "tableForm",
|
||||||
|
"display": true,
|
||||||
|
"hidden": false,
|
||||||
|
"computed": {
|
||||||
|
"hidden": {
|
||||||
|
"mode": "AND",
|
||||||
|
"group": [
|
||||||
|
{
|
||||||
|
"field": "props_type",
|
||||||
|
"condition": "==",
|
||||||
|
"value": "select"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"invert": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"_fc_id": "id_Fg4ym5um923gbec",
|
||||||
|
"name": "ref_Fzo9m5um923gbfc",
|
||||||
|
"_fc_drag_tag": "group",
|
||||||
|
"display": true,
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
14
gulpfile.js
Normal file
14
gulpfile.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
const gulp = require('gulp');
|
||||||
|
const execa = require('execa');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
gulp.task('default', async function (cb) {
|
||||||
|
await execa('node_modules/.bin/rimraf', ['locale']);
|
||||||
|
fs.readdirSync('src/locale').forEach(async function (file) {
|
||||||
|
const res = /^(.*)\.js$/.exec(file);
|
||||||
|
if (res) {
|
||||||
|
await execa('./node_modules/.bin/vite', ['build', '--config', './vite.config.locale.js', '-m', res[1]]);
|
||||||
|
}
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
});
|
24
index.html
Normal file
24
index.html
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<title>Element Plus版本低代码设计器FcDesigner Pro在线演示 | FormCreate</title>
|
||||||
|
<link rel="icon" href="/logo.png">
|
||||||
|
<meta name="keywords" content="vue,FormCreate,form-create-designer,fc-designer,开源,移动端,低代码,低代码表单,表单设计器,element-plus,element-ui">
|
||||||
|
<meta name="description" content="FcDesigner Pro是一款基于Vue3.0的低代码可视化表单设计器工具,通过数据驱动表单渲染。可以通过拖拽的方式快速创建表单,提高开发者对表单的开发效率,节省开发者的时间。目前,在OA系统、ERP系统、电商系统、流程管理等系统中已稳定应用。">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app">
|
||||||
|
</div>
|
||||||
|
<script type="module" src="./examples/main.js"></script>
|
||||||
|
<script>
|
||||||
|
var _hmt = _hmt || [];
|
||||||
|
(function() {
|
||||||
|
var hm = document.createElement("script");
|
||||||
|
hm.src = "https://hm.baidu.com/hm.js?931b182e4333e09676463bcc8248f71e";
|
||||||
|
var s = document.getElementsByTagName("script")[0];
|
||||||
|
s.parentNode.insertBefore(hm, s);
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
133
package.json
Normal file
133
package.json
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
{
|
||||||
|
"name": "fc-designer-pro",
|
||||||
|
"version": "5.7.0",
|
||||||
|
"description": "FormCreate设计器商业版",
|
||||||
|
"unpkg": "./dist/index.umd.js",
|
||||||
|
"jsdelivr": "./dist/index.umd.js",
|
||||||
|
"typings": "./types/index.d.ts",
|
||||||
|
"main": "./dist/index.umd.js",
|
||||||
|
"module": "./dist/index.es.js",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"import": "./dist/index.es.js",
|
||||||
|
"types": "./types/index.d.ts",
|
||||||
|
"require": "./dist/index.umd.js"
|
||||||
|
},
|
||||||
|
"./*": "./*"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"clean": "rimraf dist/",
|
||||||
|
"dev": "vite --config vite.dev.config.js",
|
||||||
|
"rollup": "rollup -c ./rollup.config.ts",
|
||||||
|
"build": "vite build --config ./vite.config.build.js && cross-env ONLY_PC=true vite build --config ./vite.config.pc.js && vite build --config ./vite.config.mobile.js && vite build --config ./vite.config.elm.js",
|
||||||
|
"build:mobile": "vite build --config ./vite.config.mobile.js",
|
||||||
|
"build:elm": "vite build --config ./vite.config.elm.js",
|
||||||
|
"build:locale": "gulp -f gulpfile.js",
|
||||||
|
"build:preview": "vite build --config ./vite.config.preview.js"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/xaboy/form-create-designer.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"表单设计器",
|
||||||
|
"@form-create",
|
||||||
|
"form-builder",
|
||||||
|
"form-designer",
|
||||||
|
"draggable",
|
||||||
|
"form",
|
||||||
|
"components",
|
||||||
|
"vue3",
|
||||||
|
"element-ui",
|
||||||
|
"json-form",
|
||||||
|
"dynamic-form"
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
"README.md",
|
||||||
|
"package.json",
|
||||||
|
"LICENSE",
|
||||||
|
"types",
|
||||||
|
"dist",
|
||||||
|
"locale"
|
||||||
|
],
|
||||||
|
"license": "仅限于被授权主体(个人、企业或组织)使用,未经授权不得使用、修改或移除版权信息",
|
||||||
|
"author": "FormCreate Team",
|
||||||
|
"homepage": "http://form-create.com",
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"private": true,
|
||||||
|
"devDependencies": {
|
||||||
|
"@element-plus/icons-vue": "^0.2.6",
|
||||||
|
"@sixian/css-url": "^1.0.3",
|
||||||
|
"@types/chalk": "^2.2.0",
|
||||||
|
"@types/shelljs": "^0.8.9",
|
||||||
|
"@vitejs/plugin-vue": "^3.1.2",
|
||||||
|
"@vitejs/plugin-vue-jsx": "^2.0.1",
|
||||||
|
"@vue/babel-plugin-jsx": "^1.0.7",
|
||||||
|
"@vue/cli-plugin-babel": "^4.5.13",
|
||||||
|
"@vue/cli-service": "^4.5.3",
|
||||||
|
"@vue/compiler-sfc": "^3.0.11",
|
||||||
|
"babel-eslint": "^10.1.0",
|
||||||
|
"babel-loader": "^8.4.1",
|
||||||
|
"chalk": "^4.1.2",
|
||||||
|
"codemirror": "^6.65.7",
|
||||||
|
"commander": "^6.0.0",
|
||||||
|
"cross-env": "^7.0.2",
|
||||||
|
"css-loader": "^4.2.1",
|
||||||
|
"cssnano": "^5.1.13",
|
||||||
|
"cssnano-preset-advanced": "^5.3.8",
|
||||||
|
"element-plus": "^2.9.8",
|
||||||
|
"eslint": "^7.7.0",
|
||||||
|
"eslint-plugin-vue": "^7.2.2",
|
||||||
|
"esno": "^0.9.1",
|
||||||
|
"execa": "^5.1.1",
|
||||||
|
"fast-glob": "^3.2.7",
|
||||||
|
"figlet": "^1.5.0",
|
||||||
|
"fs-extra": "^10.0.0",
|
||||||
|
"gulp": "^4.0.2",
|
||||||
|
"html-webpack-plugin": "^4.3.0",
|
||||||
|
"humps": "^2.0.1",
|
||||||
|
"husky": "^4.2.5",
|
||||||
|
"javascript-obfuscator": "^4.1.0",
|
||||||
|
"jsonlint-mod": "^1.7.6",
|
||||||
|
"lint-staged": "^10.2.11",
|
||||||
|
"npm-run-all": "^4.1.5",
|
||||||
|
"ora": "^5.0.0",
|
||||||
|
"postcss": "^8.4.17",
|
||||||
|
"rimraf": "^3.0.2",
|
||||||
|
"rollup-plugin-visualizer": "^5.8.2",
|
||||||
|
"shelljs": "^0.8.4",
|
||||||
|
"stringify-author": "^0.1.3",
|
||||||
|
"tslib": "^2.3.1",
|
||||||
|
"typescript": "^4.4.3",
|
||||||
|
"unplugin-preprocessor-directives": "^1.0.3",
|
||||||
|
"vant": "^4",
|
||||||
|
"vite": "^3.2.11",
|
||||||
|
"vite-plugin-banner": "^0.5.0",
|
||||||
|
"vite-plugin-css-injected-by-js": "^2.1.0",
|
||||||
|
"vite-plugin-javascript-obfuscator": "^3.1.0",
|
||||||
|
"vue": "^3.1.5",
|
||||||
|
"vue-loader": "^15.9.3",
|
||||||
|
"vue-style-loader": "^4.1.2",
|
||||||
|
"vue-template-compiler": "^2.6.11"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@form-create/component-elm-select": "^3.1",
|
||||||
|
"@form-create/component-elm-tree": "^3.1",
|
||||||
|
"@form-create/component-elm-upload": "^3.1",
|
||||||
|
"@form-create/component-wangeditor": "^3.1",
|
||||||
|
"@form-create/element-ui": "^3.2.8",
|
||||||
|
"@form-create/utils": "^3.1.23",
|
||||||
|
"@form-create/vant": "^3.2.8",
|
||||||
|
"axios": "^1.9.0",
|
||||||
|
"dayjs": "^1.11.13",
|
||||||
|
"js-beautify": "^1.15.1",
|
||||||
|
"jsbarcode": "^3.11.6",
|
||||||
|
"marked": "^15.0.9",
|
||||||
|
"qr-code-styling": "^1.9.1",
|
||||||
|
"signature_pad": "^5.0.4",
|
||||||
|
"snowflake-id": "^1.1.0",
|
||||||
|
"vuedraggable": "4.1.0"
|
||||||
|
}
|
||||||
|
}
|
15286
pnpm-lock.yaml
Normal file
15286
pnpm-lock.yaml
Normal file
File diff suppressed because it is too large
Load Diff
4
src/assets/svg/dv-folder.svg
Normal file
4
src/assets/svg/dv-folder.svg
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M0.75 3C0.75 2.58579 1.08579 2.25 1.5 2.25H7.78647C8.07055 2.25 8.33025 2.4105 8.45729 2.66459L9 3.75H16.5C16.9142 3.75 17.25 4.08579 17.25 4.5V15C17.25 15.4142 16.9142 15.75 16.5 15.75H1.5C1.08579 15.75 0.75 15.4142 0.75 15V3Z" fill="#FFA53D"/>
|
||||||
|
<path d="M0.75 4.5C0.75 4.08579 1.08579 3.75 1.5 3.75H16.5C16.9142 3.75 17.25 4.08579 17.25 4.5V15C17.25 15.4142 16.9142 15.75 16.5 15.75H1.5C1.08579 15.75 0.75 15.4142 0.75 15V4.5Z" fill="#FFC60A"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 558 B |
11
src/assets/svg/icon_dataset.svg
Normal file
11
src/assets/svg/icon_dataset.svg
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0_181_24877)">
|
||||||
|
<path d="M0 4C0 1.79086 2.13127 0 4.76033 0H19.2397C21.8687 0 24 1.79086 24 4V20C24 22.2091 21.8687 24 19.2397 24H4.76033C2.13127 24 0 22.2091 0 20V4Z" fill="#16c0ff"/>
|
||||||
|
<path d="M4.66669 9.09589C4.66669 8.83447 4.81949 8.59716 5.05749 8.48898L11.7242 5.45868C11.8994 5.37901 12.1006 5.37901 12.2759 5.45868L18.9426 8.48898C19.1806 8.59716 19.3334 8.83447 19.3334 9.0959V15.5879C19.3334 15.8404 19.1907 16.0713 18.9648 16.1842L12.2982 19.5175C12.1105 19.6114 11.8896 19.6114 11.7019 19.5175L5.03521 16.1842C4.80936 16.0713 4.66669 15.8404 4.66669 15.5879V9.09589ZM16.8119 8.98512L12 6.7979L7.16215 8.99693L11.9733 11.0694L16.8119 8.98512ZM12.6667 12.2225V17.8426L18 15.1759V9.9251L12.6667 12.2225ZM6.00002 9.9481V15.1759L11.3334 17.8426V12.2455L6.00002 9.9481Z" fill="white"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_181_24877">
|
||||||
|
<rect width="24" height="24" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.0 KiB |
162
src/components/City.vue
Normal file
162
src/components/City.vue
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_fc-city">
|
||||||
|
<el-select :disabled="disabled" :clearable="clearable" :modelValue="value.p"
|
||||||
|
@update:modelValue="changeProvince" @change="onInput">
|
||||||
|
<template v-for="item in province">
|
||||||
|
<el-option :label="item.n" :value="item.n"></el-option>
|
||||||
|
</template>
|
||||||
|
</el-select>
|
||||||
|
<el-select :disabled="disabled" :clearable="clearable" v-if="level > 1 && city.length" :modelValue="value.c"
|
||||||
|
@update:modelValue="changeCity" @change="onInput">
|
||||||
|
<template v-for="item in city">
|
||||||
|
<el-option :label="item.n" :value="item.n"></el-option>
|
||||||
|
</template>
|
||||||
|
</el-select>
|
||||||
|
<el-select :disabled="disabled" :clearable="clearable" v-if="level > 2 && area.length" v-model="value.a"
|
||||||
|
@change="onInput">
|
||||||
|
<template v-for="item in area">
|
||||||
|
<el-option :label="item.n" :value="item.n"></el-option>
|
||||||
|
</template>
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent, markRaw} from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'FcCity',
|
||||||
|
props: {
|
||||||
|
modelValue: Array,
|
||||||
|
clearable: Boolean,
|
||||||
|
disabled: Boolean,
|
||||||
|
filter: Function,
|
||||||
|
level: {
|
||||||
|
type: Number,
|
||||||
|
default: 3
|
||||||
|
},
|
||||||
|
api: String,
|
||||||
|
},
|
||||||
|
emits: ['update:modelValue', 'change'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
value: {
|
||||||
|
p: '',
|
||||||
|
c: '',
|
||||||
|
a: ''
|
||||||
|
},
|
||||||
|
oldValue: '',
|
||||||
|
province: [],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
modelValue: {
|
||||||
|
handler(val) {
|
||||||
|
if (JSON.stringify(val) !== this.oldValue) {
|
||||||
|
this.updateValue();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
city() {
|
||||||
|
if (this.value.p) {
|
||||||
|
for (let i = 0; i < this.province.length; i++) {
|
||||||
|
if (this.province[i].n === this.value.p) {
|
||||||
|
return this.province[i].d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
area() {
|
||||||
|
if (this.value.c) {
|
||||||
|
for (let i = 0; i < this.city.length; i++) {
|
||||||
|
if (this.city[i].n === this.value.c) {
|
||||||
|
return this.city[i]?.d || [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateValue() {
|
||||||
|
const str = JSON.stringify(this.modelValue);
|
||||||
|
if (str !== JSON.stringify(this.oldValue)) {
|
||||||
|
this.value = {
|
||||||
|
p: this.modelValue?.[0] || '',
|
||||||
|
c: this.modelValue?.[1] || '',
|
||||||
|
a: this.modelValue?.[2] || '',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.oldValue = str;
|
||||||
|
},
|
||||||
|
changeProvince(val) {
|
||||||
|
this.value.p = val;
|
||||||
|
this.value.c = '';
|
||||||
|
this.value.a = '';
|
||||||
|
},
|
||||||
|
changeCity(val) {
|
||||||
|
this.value.c = val;
|
||||||
|
this.value.a = '';
|
||||||
|
},
|
||||||
|
onInput() {
|
||||||
|
let value = [];
|
||||||
|
if (this.value.p) {
|
||||||
|
value = [this.value.p, this.value.c, this.value.a].filter(item => !!item);
|
||||||
|
if (this.level < 3 && value.length !== this.level) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.level > 2 && (value.length < 2 || value.length === 2 && this.area.length)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.oldValue = JSON.stringify(value);
|
||||||
|
this.$emit('update:modelValue', value);
|
||||||
|
this.$emit('change', value);
|
||||||
|
},
|
||||||
|
loadData(uri) {
|
||||||
|
return fetch(uri).then((res) => {
|
||||||
|
return res.json();
|
||||||
|
}).then((res) => {
|
||||||
|
this.province = markRaw(this.filter ? this.filter(res) || [] : res);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
if (this.api) {
|
||||||
|
this.loadData(this.api);
|
||||||
|
} else {
|
||||||
|
this.loadData('https://unpkg.com/@province-city-china/level/level.min.json').catch(() => {
|
||||||
|
this.loadData('https://cdn.jsdelivr.net/npm/@province-city-china/level/level.min.json').catch(() => {
|
||||||
|
this.loadData('https://npm.onmicrosoft.cn/@province-city-china/level/level.min.json');
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.updateValue();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fc-city .el-select {
|
||||||
|
width: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-create-m ._fc-city {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-create-m ._fc-city .el-select {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-create ._fc-city .el-select + .el-select {
|
||||||
|
margin-left: 12px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
53
src/components/DragBox.vue
Normal file
53
src/components/DragBox.vue
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<script>
|
||||||
|
import {defineComponent, h} from 'vue';
|
||||||
|
import draggable from 'vuedraggable/src/vuedraggable';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'DragBox',
|
||||||
|
props: ['rule', 'tag', 'formCreateInject', 'list'],
|
||||||
|
render(ctx) {
|
||||||
|
const attrs = {...ctx.$props.rule.props, ...ctx.$attrs};
|
||||||
|
let _class = '_fd-' + ctx.$props.tag + '-drag _fd-drag-box';
|
||||||
|
if (!Object.keys(ctx.$slots).length) {
|
||||||
|
_class += ' drag-holder';
|
||||||
|
}
|
||||||
|
attrs.class = _class;
|
||||||
|
attrs.modelValue = ctx.$props.list || [...ctx.$props.formCreateInject.children];
|
||||||
|
|
||||||
|
const keys = {};
|
||||||
|
if (ctx.$slots.default) {
|
||||||
|
const children = ctx.$slots.default();
|
||||||
|
children.forEach(v => {
|
||||||
|
if (v.key) {
|
||||||
|
keys[v.key] = v;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return h(draggable, attrs, {
|
||||||
|
item: ({element, index}) => {
|
||||||
|
let inline = '';
|
||||||
|
if(element?._menu?.inline || element?._config?.inline){
|
||||||
|
inline = ' is-inline'
|
||||||
|
}
|
||||||
|
const key = element?.__fc__?.key;
|
||||||
|
if (key) {
|
||||||
|
let vnode = keys['_' + element.slot];
|
||||||
|
if (vnode) {
|
||||||
|
vnode.children.forEach(v => {
|
||||||
|
if (v.key === key + 'fc') {
|
||||||
|
vnode = v
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
vnode = keys[key + 'fc'];
|
||||||
|
}
|
||||||
|
if (vnode) {
|
||||||
|
return h('div', {class: '_fc-' + ctx.$props.tag + '-item _fd-drag-item' + inline, key}, vnode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return h('div', {class: '_fc-' + ctx.$props.tag + '-item _fd-drag-item', key: index}, null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
280
src/components/DragTool.vue
Normal file
280
src/components/DragTool.vue
Normal file
@ -0,0 +1,280 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_fd-drag-tool" @click.stop="active"
|
||||||
|
:class="{active: fcx.active === id, 'is-inside': inside, 'is-inline': inline}">
|
||||||
|
<div class="_fd-drag-mask" v-if="mask"></div>
|
||||||
|
<div class="_fd-drag-hidden" v-if="hidden">
|
||||||
|
<i class="fc-icon icon-eye-close"></i> {{ t('props.hide') }}
|
||||||
|
</div>
|
||||||
|
<div class="_fd-drag-l" v-if="!hiddenBtn" @click.stop>
|
||||||
|
<div class="_fd-drag-btn" v-if="dragBtn !== false" v-show="fcx.active === id" style="cursor: move;">
|
||||||
|
<i class="fc-icon icon-move"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="_fd-drag-r" v-if="btns !== false && !hiddenMenu">
|
||||||
|
<slot name="handle">
|
||||||
|
<div class="_fd-drag-btn" v-if="actions && actions.length > 0" @click.stop>
|
||||||
|
<el-dropdown trigger="click" @command="command">
|
||||||
|
<i class="fc-icon icon-setting"></i>
|
||||||
|
<template #dropdown>
|
||||||
|
<el-dropdown-menu>
|
||||||
|
<template v-for="(label, idx) in actions">
|
||||||
|
<el-dropdown-item :command="idx">
|
||||||
|
{{ t(label) || label }}
|
||||||
|
</el-dropdown-item>
|
||||||
|
</template>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</template>
|
||||||
|
</el-dropdown>
|
||||||
|
</div>
|
||||||
|
<div class="_fd-drag-btn" @click.stop v-if="isCreate && (btns === true || btns.indexOf('create') > -1)"
|
||||||
|
@click="$emit('create')">
|
||||||
|
<i class="fc-icon icon-add"></i>
|
||||||
|
</div>
|
||||||
|
<div class="_fd-drag-btn" @click.stop v-if="!only && (btns === true || btns.indexOf('copy') > -1)"
|
||||||
|
@click="$emit('copy')">
|
||||||
|
<i class="fc-icon icon-copy"></i>
|
||||||
|
</div>
|
||||||
|
<div class="_fd-drag-btn" @click.stop v-if="children && (btns === true || btns.indexOf('addChild') > -1)"
|
||||||
|
@click="$emit('addChild')">
|
||||||
|
<i class="fc-icon icon-add-child"></i>
|
||||||
|
</div>
|
||||||
|
<div class="_fd-drag-btn _fd-drag-danger" @click.stop v-if="btns === true || btns.indexOf('delete') > -1"
|
||||||
|
@click="$emit('delete')">
|
||||||
|
<i class="fc-icon icon-delete"></i>
|
||||||
|
</div>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
<slot name="default"></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'DragTool',
|
||||||
|
emits: ['create', 'copy', 'addChild', 'delete', 'active', 'action', 'fc.el'],
|
||||||
|
props: {
|
||||||
|
dragBtn: Boolean,
|
||||||
|
children: String,
|
||||||
|
inside: Boolean,
|
||||||
|
inline: Boolean,
|
||||||
|
hidden: Boolean,
|
||||||
|
mask: Boolean,
|
||||||
|
actions: Array,
|
||||||
|
handleBtn: [Boolean, Array],
|
||||||
|
formCreateInject: Object,
|
||||||
|
unique: String,
|
||||||
|
only: Boolean
|
||||||
|
},
|
||||||
|
inject: {
|
||||||
|
fcx: {
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
designer: {
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
dragTool: {
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
},
|
||||||
|
provide() {
|
||||||
|
return {
|
||||||
|
dragTool: this
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isCreate() {
|
||||||
|
return this.dragTool ? !!this.dragTool.children : false;
|
||||||
|
},
|
||||||
|
btns() {
|
||||||
|
if (Array.isArray(this.handleBtn)) {
|
||||||
|
return this.handleBtn.length ? this.handleBtn : false;
|
||||||
|
}
|
||||||
|
return this.handleBtn !== false;
|
||||||
|
},
|
||||||
|
id() {
|
||||||
|
return this.unique || this.formCreateInject.id;
|
||||||
|
},
|
||||||
|
hiddenMenu() {
|
||||||
|
return this.designer.setupState.hiddenDragMenu;
|
||||||
|
},
|
||||||
|
t() {
|
||||||
|
return this.designer.setupState.t;
|
||||||
|
},
|
||||||
|
hiddenBtn() {
|
||||||
|
return this.designer.setupState.hiddenDragBtn;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
command(idx) {
|
||||||
|
this.$emit('action', idx);
|
||||||
|
},
|
||||||
|
active() {
|
||||||
|
if (this.fcx.active === this.id) return;
|
||||||
|
this.fcx.active = this.id;
|
||||||
|
this.$emit('active');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$emit('fc.el', this);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fd-drag-tool {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
min-height: 20px;
|
||||||
|
min-width: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 2px;
|
||||||
|
outline: 1px dashed var(--fc-tool-border-color);
|
||||||
|
overflow: hidden;
|
||||||
|
word-wrap: break-word;
|
||||||
|
word-break: break-all;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-drag-tool ._fd-drag-tool {
|
||||||
|
margin: 2px;
|
||||||
|
max-width: calc(100% - 4px);
|
||||||
|
max-height: calc(100% - 7px);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-drag-tool.is-inline {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-drag-tool.is-inside {
|
||||||
|
width: inherit;
|
||||||
|
height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-drag-tool:hover {
|
||||||
|
outline-color: var(--fc-style-color-1);
|
||||||
|
outline-style: solid;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-drag-tool:has(._fd-drag-tool:hover) {
|
||||||
|
outline-style: dashed;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-drag-tool:not(.active):hover > div > ._fd-drag-btn {
|
||||||
|
display: flex !important;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-drag-tool:has(._fd-drag-tool:not(.active):hover, ._fd-drag-tool.active:hover) > div > ._fd-drag-btn {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-drag-tool:has(._fd-drag-tool) {
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-drag-tool + ._fd-drag-tool {
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-drag-tool.active {
|
||||||
|
outline: 2px solid var(--fc-style-color-1) !important;
|
||||||
|
z-index: 2;
|
||||||
|
min-width: 80px;
|
||||||
|
min-height: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-drag-tool.active > div > ._fd-drag-btn {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-drag-tool._fd-drop-hover ._fd-drag-box {
|
||||||
|
padding-top: 15px !important;
|
||||||
|
padding-bottom: 15px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*._fd-drag-tool._fd-drop-hover ._fd-drag-box ._fd-drag-item {
|
||||||
|
padding-top: 5px !important;
|
||||||
|
padding-bottom: 5px !important;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
._fd-drag-tool._fd-drop-hover:hover {
|
||||||
|
outline: 1px dashed var(--fc-tool-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-drag-tool ._fd-drag-btn {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-drag-r {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: calc(100% - 20px);
|
||||||
|
padding: 0 2px 2px 0;
|
||||||
|
z-index: 1904;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-drag-l {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 1904
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-drag-btn {
|
||||||
|
height: 18px;
|
||||||
|
width: 18px;
|
||||||
|
color: #fff;
|
||||||
|
background-color: var(--fc-style-color-1);
|
||||||
|
line-height: 20px;
|
||||||
|
padding-bottom: 1px;
|
||||||
|
float: left;
|
||||||
|
cursor: pointer;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-drag-btn .el-dropdown {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-drag-btn + ._fd-drag-btn {
|
||||||
|
margin-left: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-drag-danger {
|
||||||
|
background-color: var(--fc-style-color-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-drag-btn i {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-drag-mask, ._fd-drag-hidden {
|
||||||
|
z-index: 1900;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-drag-hidden {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: rgba(51, 51, 51, .7);
|
||||||
|
color: #FFFFFF;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-drag-tool:hover ._fd-drag-hidden, ._fd-drag-tool.active ._fd-drag-hidden, ._fd-drag-tool:has(._fd-drag-tool.active) ._fd-drag-hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-drag-hidden .fc-icon {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
</style>
|
961
src/components/EventConfig.vue
Normal file
961
src/components/EventConfig.vue
Normal file
@ -0,0 +1,961 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_fd-event">
|
||||||
|
<el-badge :value="eventNum" type="warning" :hidden="eventNum < 1">
|
||||||
|
<el-button class="_fd-plain-button" plain size="small" @click="visible=true">{{
|
||||||
|
t('event.title')
|
||||||
|
}}
|
||||||
|
</el-button>
|
||||||
|
</el-badge>
|
||||||
|
<el-dialog class="_fd-event-dialog _fd-config-dialog" :title="t('event.title')" v-model="visible"
|
||||||
|
destroy-on-close
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
append-to-body
|
||||||
|
width="1080px">
|
||||||
|
<el-container class="_fd-event-con" style="height: 600px">
|
||||||
|
<el-aside style="width:300px;">
|
||||||
|
<el-container class="_fd-event-l">
|
||||||
|
<el-header class="_fd-event-head" height="40px">
|
||||||
|
<el-dropdown popper-class="_fd-event-dropdown" trigger="click" size="default"
|
||||||
|
:placement="'bottom-start'">
|
||||||
|
<el-button link type="primary" size="default">
|
||||||
|
{{ t('event.create') }}<i class="fc-icon icon-down" style="font-size: 14px;"></i>
|
||||||
|
</el-button>
|
||||||
|
<template #dropdown>
|
||||||
|
<el-dropdown-menu>
|
||||||
|
<el-dropdown-item v-for="name in eventName" :key="name" @click="add(name)"
|
||||||
|
:disabled="useEventKeys.indexOf(name) > -1">
|
||||||
|
<div class="_fd-event-item">
|
||||||
|
<span>{{ name }}</span>
|
||||||
|
<span class="_fd-label" v-if="eventInfo[name]">
|
||||||
|
{{ eventInfo[name] }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</el-dropdown-item>
|
||||||
|
<template v-for="(hook, idx) in hookList">
|
||||||
|
<el-dropdown-item :divided="eventName.length > 0 && !idx"
|
||||||
|
@click="add(hook)"
|
||||||
|
:disabled="useEventKeys.indexOf(hook) > -1">
|
||||||
|
<div class="_fd-event-item">
|
||||||
|
<div> {{ hook }}</div>
|
||||||
|
<span class="_fd-label">
|
||||||
|
{{ eventInfo[hook] }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</el-dropdown-item>
|
||||||
|
</template>
|
||||||
|
<el-dropdown-item :divided="eventName.length > 0 || hook" @click="cusEvent">
|
||||||
|
<div>{{ t('props.custom') }}</div>
|
||||||
|
</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</template>
|
||||||
|
</el-dropdown>
|
||||||
|
</el-header>
|
||||||
|
<el-main>
|
||||||
|
<div class="_fd-menu">
|
||||||
|
<template v-for="(item, idx) in event" :key="item.id">
|
||||||
|
<div class="_fd-menu-item" :class="{'is-active': item.id === defActive }">
|
||||||
|
<div class="_fd-event-title"
|
||||||
|
@click.stop="edit(idx)">
|
||||||
|
<div class="_fd-event-method">
|
||||||
|
<span>function<span>{{
|
||||||
|
item.name
|
||||||
|
}}</span></span>
|
||||||
|
<span class="_fd-label"
|
||||||
|
v-if="eventInfo[item.name]">{{ eventInfo[item.name] }}</span>
|
||||||
|
</div>
|
||||||
|
<el-tooltip
|
||||||
|
effect="dark"
|
||||||
|
:content="t('behavior.add')"
|
||||||
|
placement="top"
|
||||||
|
:hide-after="0"
|
||||||
|
v-if="item.name !== 'hook_load'"
|
||||||
|
>
|
||||||
|
<i class="fc-icon icon-task-add"
|
||||||
|
@click.stop="addBehavior(idx)"></i>
|
||||||
|
</el-tooltip>
|
||||||
|
<i class="fc-icon icon-delete-circle"
|
||||||
|
@click.stop="rm(idx)"></i>
|
||||||
|
</div>
|
||||||
|
<div class="_fd-event-behaviors">
|
||||||
|
<fcDraggable :group="{name:'behavior', put:false}" :sort="true"
|
||||||
|
handle=".icon-drag" direction="vertical" :animation="0"
|
||||||
|
itemKey="_fc_id"
|
||||||
|
:list="item.behaviors">
|
||||||
|
<template #item="{element,index}">
|
||||||
|
<div class="_fd-event-behavior"
|
||||||
|
:class="{'is-active': element.id === defActive }"
|
||||||
|
@click.stop="editBehavior(idx,index)">
|
||||||
|
<div class="_fd-event-behavior-label">
|
||||||
|
<div>
|
||||||
|
<i class="fc-icon icon-drag"></i>
|
||||||
|
<span>{{
|
||||||
|
t('behavior.' + element.method + '.name')
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
<i class="fc-icon icon-delete-circle"
|
||||||
|
@click.stop="rmBehavior(idx, index)"></i>
|
||||||
|
</div>
|
||||||
|
<div class="_fd-event-behavior-info">
|
||||||
|
{{
|
||||||
|
t('behavior.' + element.method + '.info') || t('behavior.' + element.method + '.name')
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</fcDraggable>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="_fd-menu-item" v-if="cus" style="padding-left: 10px;">
|
||||||
|
<div class="_fd-event-title">
|
||||||
|
<el-input type="text" v-model="cusValue" size="default"
|
||||||
|
@keydown.enter="addCus"
|
||||||
|
:placeholder="t('event.placeholder')">
|
||||||
|
</el-input>
|
||||||
|
<div>
|
||||||
|
<i class="fc-icon icon-add" @click.stop="addCus"></i>
|
||||||
|
<i class="fc-icon icon-delete" @click.stop="closeCus"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-main>
|
||||||
|
</el-container>
|
||||||
|
</el-aside>
|
||||||
|
<el-main>
|
||||||
|
<el-container class="_fd-event-r">
|
||||||
|
<el-header class="_fd-event-head" height="40px" v-if="activeData || activeBehavior">
|
||||||
|
<el-button size="small" @click="close">{{ t('props.cancel') }}</el-button>
|
||||||
|
<el-button size="small" type="primary" @click="save">{{
|
||||||
|
t('props.save')
|
||||||
|
}}
|
||||||
|
</el-button>
|
||||||
|
</el-header>
|
||||||
|
<el-main v-if="activeData">
|
||||||
|
<el-tabs v-model="eventType" class="_fc-tabs" :key="activeData.key">
|
||||||
|
<el-tab-pane :label="t('props.custom')" name="fn" lazy>
|
||||||
|
<FnEditor ref="fn" v-model="eventStr" body :name="activeData.name"
|
||||||
|
:args="fnArgs"
|
||||||
|
style="height: 519px;"/>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane :label="t('form.globalEvent')" name="event">
|
||||||
|
<div class="_fd-event-select">
|
||||||
|
<el-select v-model="eventKey" clearable filterable
|
||||||
|
style="width: 240px;margin-left: 15px;">
|
||||||
|
<el-option
|
||||||
|
v-for="item in options"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<span class="_fc-manage-text" @click="openConfig"><i
|
||||||
|
class="fc-icon icon-setting"/></span>
|
||||||
|
</div>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
</el-main>
|
||||||
|
<el-main v-if="activeBehavior" class="is-behavior">
|
||||||
|
<el-aside width="220px" class="_fd-event-behavior-list">
|
||||||
|
<div class="_fd-event-behavior-title">
|
||||||
|
{{ t('behavior.props.execute') }}
|
||||||
|
</div>
|
||||||
|
<el-menu :defaultActive="activeBehavior.method" @select="handleSelect">
|
||||||
|
<template v-for="item in behaviorMenu">
|
||||||
|
<el-sub-menu :index="item.label">
|
||||||
|
<template #title>
|
||||||
|
<span>{{ t('props.' + item.label) }}</span>
|
||||||
|
</template>
|
||||||
|
<template v-for="data in item.children" :key="data.value">
|
||||||
|
<el-menu-item :index="data.value">{{
|
||||||
|
t('behavior.' + data.label + '.name')
|
||||||
|
}}
|
||||||
|
</el-menu-item>
|
||||||
|
</template>
|
||||||
|
</el-sub-menu>
|
||||||
|
</template>
|
||||||
|
</el-menu>
|
||||||
|
</el-aside>
|
||||||
|
<el-main class="_fd-event-behavior-con">
|
||||||
|
<div class="_fd-event-behavior-title">
|
||||||
|
{{ t('behavior.props.info') }}
|
||||||
|
<div>{{
|
||||||
|
t('behavior.' + activeBehavior.method + '.info') || t('behavior.' + activeBehavior.method + '.name')
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="_fd-event-behavior-title" v-if="form.rule && form.rule.length">
|
||||||
|
{{ t('designer.rule') }}
|
||||||
|
</div>
|
||||||
|
<DragForm v-if="form.rule && form.rule.length"
|
||||||
|
:rule="form.rule" :option="form.options"
|
||||||
|
v-model="form.formData" v-model:api="form.api">
|
||||||
|
<template #title="scope">
|
||||||
|
<template v-if="scope.rule.warning">
|
||||||
|
<Warning :tooltip="scope.rule.warning">
|
||||||
|
{{ scope.rule.title }}
|
||||||
|
</Warning>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
{{ scope.rule.title }}
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</DragForm>
|
||||||
|
<div class="_fd-event-behavior-title">
|
||||||
|
{{ t('designer.advanced') }}
|
||||||
|
</div>
|
||||||
|
<el-form size="small" labelWidth="auto">
|
||||||
|
<el-form-item :label="t('behavior.props.ignoreError')">
|
||||||
|
<el-radio-group v-model="activeBehavior.ignoreError">
|
||||||
|
<el-radio-button :value="true">{{
|
||||||
|
t('behavior.props.continue')
|
||||||
|
}}
|
||||||
|
</el-radio-button>
|
||||||
|
<el-radio-button :value="false">{{
|
||||||
|
t('behavior.props.stop')
|
||||||
|
}}
|
||||||
|
</el-radio-button>
|
||||||
|
</el-radio-group>
|
||||||
|
<div class="_fd-form-item-warning">{{ t('warning.behaviorIgnoreError') }}</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="t('behavior.props.expression')">
|
||||||
|
<ComputedConfig v-model="activeBehavior.expression"
|
||||||
|
:title="t('behavior.props.setFormula')"
|
||||||
|
:invertLabel="t('behavior.props.break')"
|
||||||
|
:validLabel="t('behavior.props.continue')"></ComputedConfig>
|
||||||
|
<div class="_fd-form-item-warning">{{ t('warning.behaviorExpression') }}</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="t('behavior.props.stopPropagation')">
|
||||||
|
<ComputedConfig v-model="activeBehavior.stopPropagation"
|
||||||
|
:title="t('behavior.props.setFormula')"
|
||||||
|
:invertLabel="t('behavior.props.continue')"
|
||||||
|
:validLabel="t('behavior.props.stop')"></ComputedConfig>
|
||||||
|
<div class="_fd-form-item-warning">{{ t('warning.behaviorStopPropagation') }}</div>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-main>
|
||||||
|
</el-main>
|
||||||
|
</el-container>
|
||||||
|
</el-main>
|
||||||
|
</el-container>
|
||||||
|
<template #footer>
|
||||||
|
<div>
|
||||||
|
<el-button size="default" @click="visible=false">{{ t('props.cancel') }}</el-button>
|
||||||
|
<el-button type="primary" size="default" @click="submit">{{
|
||||||
|
t('props.ok')
|
||||||
|
}}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {deepCopy} from '@form-create/utils/lib/deepextend';
|
||||||
|
import is from '@form-create/utils/lib/type';
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
import FnEditor from './FnEditor.vue';
|
||||||
|
import {getInjectArg} from '../utils';
|
||||||
|
import {behaviorRules, behaviorTree} from '../utils/behavior';
|
||||||
|
import ComputedConfig from './computed/ComputedConfig.vue';
|
||||||
|
import {designerForm} from '../utils/form';
|
||||||
|
import fcDraggable from 'vuedraggable/src/vuedraggable';
|
||||||
|
import Warning from './Warning.vue';
|
||||||
|
|
||||||
|
const $T = '$FNX:';
|
||||||
|
|
||||||
|
const isFNX = v => {
|
||||||
|
return is.String(v) && v.indexOf($T) === 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'EventConfig',
|
||||||
|
emits: ['update:modelValue'],
|
||||||
|
props: {
|
||||||
|
modelValue: [Object, undefined, null],
|
||||||
|
componentName: '',
|
||||||
|
hook: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
eventName: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
inject: ['designer'],
|
||||||
|
components: {
|
||||||
|
Warning,
|
||||||
|
ComputedConfig,
|
||||||
|
FnEditor,
|
||||||
|
fcDraggable,
|
||||||
|
DragForm: designerForm.$form(),
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
visible: false,
|
||||||
|
activeData: null,
|
||||||
|
activeBehavior: null,
|
||||||
|
val: null,
|
||||||
|
defActive: 'no',
|
||||||
|
hookList: ['hook_load', 'hook_mounted', 'hook_deleted', 'hook_watch', 'hook_value', 'hook_hidden'],
|
||||||
|
event: [],
|
||||||
|
cus: false,
|
||||||
|
cusValue: '',
|
||||||
|
eventType: 'fn',
|
||||||
|
eventKey: '',
|
||||||
|
eventStr: '',
|
||||||
|
eventNum: 0,
|
||||||
|
id: 0,
|
||||||
|
form: {
|
||||||
|
rule: [],
|
||||||
|
options: {
|
||||||
|
form: {
|
||||||
|
labelPosition: 'right',
|
||||||
|
size: 'small',
|
||||||
|
labelWidth: 'auto',
|
||||||
|
},
|
||||||
|
appendValue: false,
|
||||||
|
submitBtn: false,
|
||||||
|
},
|
||||||
|
api: {},
|
||||||
|
formData: {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
useEventKeys() {
|
||||||
|
const events = {};
|
||||||
|
this.event.forEach(item => {
|
||||||
|
events[item.name] = true;
|
||||||
|
});
|
||||||
|
return Object.keys(events);
|
||||||
|
},
|
||||||
|
behaviorMenu() {
|
||||||
|
const tree = [];
|
||||||
|
behaviorTree.forEach(item => {
|
||||||
|
tree.push({
|
||||||
|
label: item.key,
|
||||||
|
children: item.children.map(k => {
|
||||||
|
return {
|
||||||
|
label: k,
|
||||||
|
value: k
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
return tree;
|
||||||
|
},
|
||||||
|
t() {
|
||||||
|
return this.designer.setupState.t;
|
||||||
|
},
|
||||||
|
activeRule() {
|
||||||
|
return this.designer.setupState.activeRule;
|
||||||
|
},
|
||||||
|
eventInfo() {
|
||||||
|
const info = {};
|
||||||
|
this.eventName.forEach(v => {
|
||||||
|
info[v] = this.t('com.' + this.componentName + '.event.' + v) || this.t('eventInfo.' + v) || '';
|
||||||
|
})
|
||||||
|
this.hookList.forEach(v => {
|
||||||
|
info[v] = this.t('eventInfo.' + v) || '';
|
||||||
|
})
|
||||||
|
return info;
|
||||||
|
},
|
||||||
|
globalEvent() {
|
||||||
|
return this.designer.setupState.formOptions.globalEvent || {};
|
||||||
|
},
|
||||||
|
options() {
|
||||||
|
return Object.keys(this.globalEvent).map(k => {
|
||||||
|
return {
|
||||||
|
label: this.globalEvent[k].label,
|
||||||
|
value: '$GLOBAL:' + k
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
fnArgs() {
|
||||||
|
return [getInjectArg(this.t)];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
visible(v) {
|
||||||
|
if (!v) {
|
||||||
|
this.destroy();
|
||||||
|
this.closeCus();
|
||||||
|
} else {
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
openConfig() {
|
||||||
|
this.designer.setupState.openGlobalEventDialog();
|
||||||
|
},
|
||||||
|
addCus() {
|
||||||
|
const val = this.cusValue && this.cusValue.trim();
|
||||||
|
if (val) {
|
||||||
|
this.closeCus();
|
||||||
|
this.add(val);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
closeCus() {
|
||||||
|
this.cus = false;
|
||||||
|
this.cusValue = '';
|
||||||
|
},
|
||||||
|
cusEvent() {
|
||||||
|
this.cus = true;
|
||||||
|
},
|
||||||
|
loadFnStr(v) {
|
||||||
|
if (isFNX(v)) {
|
||||||
|
return v.replace($T, '');
|
||||||
|
} else if (is.Function(v)) {
|
||||||
|
const json = v.__json || '';
|
||||||
|
if (!json) {
|
||||||
|
return '' + v;
|
||||||
|
} else if (isFNX(json)) {
|
||||||
|
return json.replace($T, '');
|
||||||
|
} else {
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
} else if (v && v.indexOf('$GLOBAL:') === 0) {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
parseBehavior(behavior) {
|
||||||
|
behavior.id = this.id++;
|
||||||
|
if (behavior.method === 'callback') {
|
||||||
|
const fn = this.loadFnStr(behavior.callback);
|
||||||
|
if (fn) {
|
||||||
|
behavior.callback = fn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return behavior;
|
||||||
|
},
|
||||||
|
init() {
|
||||||
|
const behaviors = this.activeRule ? deepCopy(this.activeRule.$behavior || {}) : {};
|
||||||
|
const hooks = this.activeRule ? {...this.activeRule.hook || {}} : {};
|
||||||
|
const ons = {...deepCopy(this.modelValue || {})};
|
||||||
|
Object.keys(hooks).forEach(k => {
|
||||||
|
ons['hook_' + k] = hooks[k];
|
||||||
|
})
|
||||||
|
const event = [];
|
||||||
|
Object.keys(ons).forEach(k => {
|
||||||
|
const val = Array.isArray(ons[k]) ? ons[k] : [ons[k]];
|
||||||
|
val.forEach(v => {
|
||||||
|
const item = {
|
||||||
|
name: k,
|
||||||
|
id: this.id++,
|
||||||
|
};
|
||||||
|
const fn = this.loadFnStr(v);
|
||||||
|
if (fn) {
|
||||||
|
item.handle = fn;
|
||||||
|
}
|
||||||
|
item.behaviors = (behaviors[k] || []).map(this.parseBehavior);
|
||||||
|
delete behaviors[k];
|
||||||
|
event.push(item);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
Object.keys(behaviors).forEach(k => {
|
||||||
|
event.push({
|
||||||
|
name: k,
|
||||||
|
id: this.id++,
|
||||||
|
handle: '',
|
||||||
|
behaviors: (behaviors[k] || []).map(this.parseBehavior)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.event = event;
|
||||||
|
this.eventNum = event.length;
|
||||||
|
},
|
||||||
|
getValue() {
|
||||||
|
const on = {};
|
||||||
|
const behaviors = {};
|
||||||
|
const hooks = {};
|
||||||
|
let num = 0;
|
||||||
|
|
||||||
|
this.event.forEach(item => {
|
||||||
|
let flag = false;
|
||||||
|
if (item.handle) {
|
||||||
|
flag = true;
|
||||||
|
let list = on;
|
||||||
|
const handle = item.handle.indexOf('$GLOBAL:') !== 0 ? ($T + item.handle) : item.handle;
|
||||||
|
if (item.name.indexOf('hook_') > -1) {
|
||||||
|
hooks[item.name.replace('hook_', '')] = handle;
|
||||||
|
} else {
|
||||||
|
if (!list[item.name]) {
|
||||||
|
list[item.name] = [];
|
||||||
|
}
|
||||||
|
list[item.name].push(handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (item.behaviors && item.behaviors.length) {
|
||||||
|
flag = true;
|
||||||
|
behaviors[item.name] = item.behaviors.map(behavior => {
|
||||||
|
delete behavior.id;
|
||||||
|
return behavior;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (flag) {
|
||||||
|
num++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.keys(on).forEach(k => {
|
||||||
|
on[k] = on[k].length === 1 ? on[k][0] : on[k];
|
||||||
|
});
|
||||||
|
|
||||||
|
return {on, behaviors, hooks, num};
|
||||||
|
},
|
||||||
|
add(name) {
|
||||||
|
this.event.push({
|
||||||
|
name,
|
||||||
|
id: this.id++,
|
||||||
|
behaviors: [],
|
||||||
|
})
|
||||||
|
if (!this.activeData) {
|
||||||
|
this.edit(this.event.length - 1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
edit(idx) {
|
||||||
|
if (this.defActive === this.event[idx].id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.destroy();
|
||||||
|
this.activeData = this.event[idx];
|
||||||
|
this.val = this.activeData.handle || '';
|
||||||
|
this.eventType = this.val.indexOf('$GLOBAL:') === 0 ? 'event' : 'fn';
|
||||||
|
if (this.eventType === 'event') {
|
||||||
|
this.eventKey = this.val;
|
||||||
|
this.eventStr = '';
|
||||||
|
} else {
|
||||||
|
this.eventStr = this.val;
|
||||||
|
this.eventKey = '';
|
||||||
|
}
|
||||||
|
this.defActive = this.activeData.id;
|
||||||
|
},
|
||||||
|
rm(idx) {
|
||||||
|
this.event.splice(idx, 1);
|
||||||
|
if ((this.activeData && this.defActive === this.activeData.id) || (this.activeBehavior && idx === this.activeBehavior.pid)) {
|
||||||
|
this.destroy();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
save() {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
if (this.activeData) {
|
||||||
|
let str = this.eventKey;
|
||||||
|
if (this.eventType !== 'event') {
|
||||||
|
if (!this.$refs.fn.save()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
str = this.eventStr;
|
||||||
|
}
|
||||||
|
this.activeData.handle = str;
|
||||||
|
this.destroy();
|
||||||
|
resolve();
|
||||||
|
} else if (this.activeBehavior) {
|
||||||
|
const update = (config) => {
|
||||||
|
this.activeBehavior.config = {...config || {}};
|
||||||
|
const behavior = {...this.activeBehavior};
|
||||||
|
const pid = behavior.pid;
|
||||||
|
if (!Object.keys(behavior.config).length) {
|
||||||
|
delete behavior.config;
|
||||||
|
}
|
||||||
|
delete behavior.pid;
|
||||||
|
this.event[pid].behaviors.forEach((item, idx) => {
|
||||||
|
if (item.id === behavior.id) {
|
||||||
|
this.event[pid].behaviors[idx] = behavior;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
if (this.form.rule && this.form.rule.length) {
|
||||||
|
this.form.api.validate().then(() => {
|
||||||
|
update(this.form.formData);
|
||||||
|
this.destroy();
|
||||||
|
resolve();
|
||||||
|
}).catch(() => {
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
update();
|
||||||
|
this.destroy();
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
addBehavior(idx) {
|
||||||
|
this.event[idx].behaviors.push({
|
||||||
|
method: 'openModel',
|
||||||
|
id: this.id++,
|
||||||
|
ignoreError: false,
|
||||||
|
stopPropagation: '',
|
||||||
|
expression: '',
|
||||||
|
})
|
||||||
|
if (!this.activeData && !this.activeBehavior) {
|
||||||
|
this.editBehavior(idx, this.event[idx].behaviors.length - 1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
editBehavior(pid, idx) {
|
||||||
|
this.destroy();
|
||||||
|
this.activeBehavior = deepCopy(this.event[pid].behaviors[idx]);
|
||||||
|
this.activeBehavior.pid = pid;
|
||||||
|
this.defActive = this.activeBehavior.id;
|
||||||
|
this.updateBehaviorForm();
|
||||||
|
},
|
||||||
|
updateBehaviorForm() {
|
||||||
|
let rule = behaviorRules[this.activeBehavior.method];
|
||||||
|
if (is.Function(rule)) {
|
||||||
|
rule = rule(this.designer.setupState);
|
||||||
|
}
|
||||||
|
const loadT = (item) => {
|
||||||
|
if (item.field && !item.title) {
|
||||||
|
item.title = this.t('behavior.' + this.activeBehavior.method + '.props.' + item.field) || this.t('behavior.props.' + item.field) || this.t('props.' + item.field);
|
||||||
|
item.warning = this.t('behavior.' + this.activeBehavior.method + '.warning.' + item.field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rule) {
|
||||||
|
this.form.rule = rule.map(item => {
|
||||||
|
loadT(item);
|
||||||
|
if (item.control) {
|
||||||
|
item.control.forEach(control => {
|
||||||
|
control.rule && control.rule.forEach(item => {
|
||||||
|
loadT(item);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.form.api.setValue(this.activeBehavior.config || {});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.clearBehaviorForm();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clearBehaviorForm() {
|
||||||
|
this.form.rule = [];
|
||||||
|
this.form.formData = {};
|
||||||
|
},
|
||||||
|
rmBehavior(pid, idx) {
|
||||||
|
this.event[pid].behaviors.splice(idx, 1);
|
||||||
|
if (this.activeBehavior && this.defActive === this.activeBehavior.id) {
|
||||||
|
this.destroy();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleSelect(behavior) {
|
||||||
|
if (this.activeBehavior.method === behavior) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.activeBehavior.method = behavior;
|
||||||
|
this.updateBehaviorForm();
|
||||||
|
},
|
||||||
|
destroy() {
|
||||||
|
this.activeBehavior = null;
|
||||||
|
this.activeData = null;
|
||||||
|
this.val = null;
|
||||||
|
this.defActive = null;
|
||||||
|
this.clearBehaviorForm();
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
this.destroy();
|
||||||
|
},
|
||||||
|
submit() {
|
||||||
|
this.save().then(() => {
|
||||||
|
const {on, behaviors, num, hooks} = this.getValue();
|
||||||
|
this.$emit('update:modelValue', on);
|
||||||
|
this.activeRule.$behavior = behaviors;
|
||||||
|
this.activeRule.hook = hooks;
|
||||||
|
this.visible = false;
|
||||||
|
this.eventNum = num;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
beforeCreate() {
|
||||||
|
window.$inject = {
|
||||||
|
$f: {},
|
||||||
|
rule: [],
|
||||||
|
self: {},
|
||||||
|
option: {},
|
||||||
|
inject: {},
|
||||||
|
args: [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
._fd-event .el-button {
|
||||||
|
font-weight: 400;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-event .el-badge {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-menu {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-menu-item {
|
||||||
|
padding: 0 15px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-bottom: 1px dashed var(--fc-line-color-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-menu-item.is-active {
|
||||||
|
border: 1px solid var(--fc-style-color-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-menu-item.is-active ._fd-event-title i {
|
||||||
|
color: var(--fc-style-color-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-event-dialog .el-tabs__header {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-event-select {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: 15px;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-event-select .el-select {
|
||||||
|
width: 240px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-event-con .el-main {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-event-l, ._fd-event-r {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
border: 1px solid var(--fc-line-color-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-event-dropdown .el-dropdown-menu {
|
||||||
|
max-height: 500px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-event-head {
|
||||||
|
display: flex;
|
||||||
|
padding: 5px 15px;
|
||||||
|
border-bottom: 1px solid var(--fc-line-color-3);
|
||||||
|
background: var(--fc-bg-color-3);
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-event-head .el-button.is-link {
|
||||||
|
color: var(--fc-style-color-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-event-r {
|
||||||
|
border-left: 0 none;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-event-r ._fd-event-head {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-event-l > .el-main, ._fd-event-r > .el-main {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex: 1;
|
||||||
|
flex-basis: auto;
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-width: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-event-r > .el-main {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-event-r > .el-main.is-behavior {
|
||||||
|
flex-direction: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-event-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
max-width: 250px;
|
||||||
|
font-size: 14px;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-event-item ._fd-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--fc-text-color-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
._fd-event-l .el-menu-item.is-active ._fd-event-title i {
|
||||||
|
color: var(--fc-style-color-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-event-method {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
width: 225px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: monospace;
|
||||||
|
color: #9D238C;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-event-method ._fd-label {
|
||||||
|
margin-top: 4px;
|
||||||
|
color: var(--fc-text-color-3);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-event-method > span:first-child, ._fd-fn-list-method > span:first-child {
|
||||||
|
color: #9D238C;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-event-method > span:first-child > span, ._fd-fn-list-method > span:first-child > span {
|
||||||
|
color: var(--fc-text-color-1);
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-event-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-event-title .fc-icon {
|
||||||
|
margin-right: 6px;
|
||||||
|
font-size: 18px;
|
||||||
|
color: var(--fc-text-color-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-event-title .el-input {
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-event-title .el-input__wrapper {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-event-con .CodeMirror {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-event-con .CodeMirror-wrap pre.CodeMirror-line {
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-event-behaviors {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-event-behavior {
|
||||||
|
width: 100%;
|
||||||
|
background: var(--fc-bg-color-3);
|
||||||
|
border-radius: 5px 5px 5px 5px;
|
||||||
|
padding: 12px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-event-behavior.is-active {
|
||||||
|
background: var(--fc-style-color-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-event-behavior.is-active ._fd-event-behavior-info, ._fd-event-behavior.is-active ._fd-event-behavior-label {
|
||||||
|
color: #FFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-event-behavior-label {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
color: var(--fc-text-color-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-event-behavior-label > div {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-event-behavior-info {
|
||||||
|
color: var(--fc-text-color-3);
|
||||||
|
margin-left: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-event-behavior-list {
|
||||||
|
height: 100%;
|
||||||
|
padding: 15px;
|
||||||
|
border-right: 1px solid var(--fc-line-color-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-event-behavior-list .el-sub-menu__title, ._fd-event-behavior-list .el-menu-item {
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-event-behavior-list .el-menu {
|
||||||
|
border-right: 0 none;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-event-behavior-list .el-menu-item.is-active {
|
||||||
|
background: var(--fc-style-color-1);
|
||||||
|
color: #FFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-event-behavior-list .el-menu-item, ._fd-event-behavior-list .el-sub-menu__title {
|
||||||
|
border-radius: 6px !important;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-event-con ._fd-event-behavior-con {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-event-con .form-create .form-create .el-form-item {
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-event-con .el-form ._fd-form-item-warning {
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--fc-text-color-3);
|
||||||
|
line-height: 17px;
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-event-behavior-title {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--fc-text-color-1);
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-event-behavior-title > div {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: initial;
|
||||||
|
color: var(--fc-text-color-3);
|
||||||
|
}
|
||||||
|
</style>
|
3609
src/components/FcDesigner.vue
Normal file
3609
src/components/FcDesigner.vue
Normal file
File diff suppressed because it is too large
Load Diff
302
src/components/FetchConfig.vue
Normal file
302
src/components/FetchConfig.vue
Normal file
@ -0,0 +1,302 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_fd-fetch-config">
|
||||||
|
<el-badge type="warning" is-dot :hidden="!configured">
|
||||||
|
<el-button class="_fd-plain-button" plain @click="visible=true" size="small">{{ t('struct.title') }}</el-button>
|
||||||
|
</el-badge>
|
||||||
|
<el-dialog class="_fd-fetch-dialog _fd-config-dialog" v-model="visible" destroy-on-close
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
append-to-body
|
||||||
|
width="980px">
|
||||||
|
<template #header>
|
||||||
|
{{ t('fetch.optionsType.fetch') }}
|
||||||
|
<Warning :tooltip="t('warning.fetch')"></Warning>
|
||||||
|
</template>
|
||||||
|
<el-container class="_fd-fetch-con" style="height: 450px;">
|
||||||
|
<el-tabs model-value="first" class="_fc-tabs" style="width: 100%">
|
||||||
|
<el-tab-pane :label="t('fetch.config')" name="first" style="padding-right: 15px;">
|
||||||
|
<div class="_fd-fetch-info">
|
||||||
|
{{ t('fetch.info') }}
|
||||||
|
</div>
|
||||||
|
<DragForm v-model:api="form.api" v-model="form.formData" :rule="form.rule"
|
||||||
|
:option="form.options">
|
||||||
|
<template #title="scope">
|
||||||
|
<template v-if="scope.rule.warning">
|
||||||
|
<Warning :tooltip="scope.rule.warning">
|
||||||
|
{{ scope.rule.title }}
|
||||||
|
</Warning>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
{{scope.rule.title}}
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</DragForm>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane lazy :label="t('fetch.beforeFetch')" name="second">
|
||||||
|
<template #label>
|
||||||
|
{{ t('fetch.beforeFetch') }}
|
||||||
|
<Warning :tooltip="t('warning.beforeFetch')"></Warning>
|
||||||
|
</template>
|
||||||
|
<FnEditor style="height: 100%;" v-model="form.beforeFetch" name="beforeFetch"
|
||||||
|
:args="['config', 'data']"
|
||||||
|
ref="beforeFetch"></FnEditor>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane lazy name="third">
|
||||||
|
<template #label>
|
||||||
|
{{ t('fetch.parse') }}
|
||||||
|
<Warning :tooltip="t('warning.fetchParse')"></Warning>
|
||||||
|
</template>
|
||||||
|
<FnEditor style="height: 100%;" v-model="form.parse" name="parse"
|
||||||
|
:args="[{name:'res', info: t('fetch.response')}, 'rule', 'api']"
|
||||||
|
ref="parse"></FnEditor>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane lazy :label="t('fetch.onError')" name="fourth">
|
||||||
|
<FnEditor style="height: 100%;" v-model="form.onError" name="onError"
|
||||||
|
:args="['e']"
|
||||||
|
ref="error"></FnEditor>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
</el-container>
|
||||||
|
<template #footer>
|
||||||
|
<div>
|
||||||
|
<el-button size="default" @click="visible=false">{{ t('props.cancel') }}</el-button>
|
||||||
|
<el-button type="primary" size="default" @click="save">{{
|
||||||
|
t('props.ok')
|
||||||
|
}}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {deepCopy} from '@form-create/utils/lib/deepextend';
|
||||||
|
import FnEditor from './FnEditor.vue';
|
||||||
|
import StructEditor from './StructEditor.vue';
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
import {designerForm} from '../utils/form';
|
||||||
|
import errorMessage from '../utils/message';
|
||||||
|
import is from '@form-create/utils/lib/type';
|
||||||
|
import Warning from './Warning.vue';
|
||||||
|
|
||||||
|
const makeRule = (t) => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
field: 'action',
|
||||||
|
title: t('fetch.action'),
|
||||||
|
value: '',
|
||||||
|
validate: [{required: true, message: t('fetch.actionRequired'), trigger: 'blur'}],
|
||||||
|
inject: true,
|
||||||
|
on: {
|
||||||
|
blur({self}, e) {
|
||||||
|
self._start = e.target.selectionStart;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
type: 'VariableConfig',
|
||||||
|
slot: 'suffix',
|
||||||
|
props: {
|
||||||
|
popover: true,
|
||||||
|
},
|
||||||
|
inject: true,
|
||||||
|
on: {
|
||||||
|
confirm({api}, val) {
|
||||||
|
const rule = api.getRule('action');
|
||||||
|
rule.value = rule.value.substring(0, rule._start) + val + rule.value.substring(rule._start);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'radio',
|
||||||
|
field: 'method',
|
||||||
|
title: t('fetch.method'),
|
||||||
|
value: 'GET',
|
||||||
|
options: [
|
||||||
|
{label: 'GET', value: 'GET'},
|
||||||
|
{label: 'POST', value: 'POST'},
|
||||||
|
],
|
||||||
|
$required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'radio',
|
||||||
|
field: 'dataType',
|
||||||
|
title: t('fetch.dataType'),
|
||||||
|
warning: t('warning.fetchDataType'),
|
||||||
|
value: 'json',
|
||||||
|
options: [
|
||||||
|
{label: 'JSON', value: 'json'},
|
||||||
|
{label: 'FormData', value: 'formData'},
|
||||||
|
],
|
||||||
|
$required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'FetchTable',
|
||||||
|
field: 'headers',
|
||||||
|
title: t('fetch.headers'),
|
||||||
|
value: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'FetchTable',
|
||||||
|
field: 'query',
|
||||||
|
title: t('fetch.query'),
|
||||||
|
warning: t('warning.fetchQuery'),
|
||||||
|
value: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'FetchTable',
|
||||||
|
field: 'data',
|
||||||
|
title: t('fetch.data'),
|
||||||
|
warning: t('warning.fetchData'),
|
||||||
|
value: {},
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'FetchConfig',
|
||||||
|
emits: ['update:modelValue'],
|
||||||
|
props: {
|
||||||
|
modelValue: [Object, String],
|
||||||
|
to: String,
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
Warning,
|
||||||
|
DragForm: designerForm.$form(),
|
||||||
|
FnEditor,
|
||||||
|
StructEditor
|
||||||
|
},
|
||||||
|
inject: ['designer'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
visible: false,
|
||||||
|
value: deepCopy(this.modelValue || {}),
|
||||||
|
form: {
|
||||||
|
api: {},
|
||||||
|
formData: {},
|
||||||
|
rule: [],
|
||||||
|
options: {
|
||||||
|
form: {
|
||||||
|
labelWidth: '90px',
|
||||||
|
size: 'default'
|
||||||
|
},
|
||||||
|
submitBtn: false,
|
||||||
|
resetBtn: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
t() {
|
||||||
|
return this.designer.setupState.t;
|
||||||
|
},
|
||||||
|
configured() {
|
||||||
|
return !is.empty(this.modelValue);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
visible(v) {
|
||||||
|
if (v) {
|
||||||
|
this.value = deepCopy(this.modelValue || {});
|
||||||
|
this.active();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
open() {
|
||||||
|
this.visible = true;
|
||||||
|
},
|
||||||
|
active() {
|
||||||
|
const formData = this.value;
|
||||||
|
this.form.rule = formData.type === 'static' ? [] : makeRule(this.t);
|
||||||
|
this.form.formData = {...formData};
|
||||||
|
this.form.label = formData.label;
|
||||||
|
this.form.type = formData.type;
|
||||||
|
this.form.data = formData.data;
|
||||||
|
this.form.dataType = formData.dataType;
|
||||||
|
this.form.parse = formData.parse || '';
|
||||||
|
this.form.beforeFetch = formData.beforeFetch || '';
|
||||||
|
this.form.onError = formData.onError || '';
|
||||||
|
},
|
||||||
|
save() {
|
||||||
|
this.form.api.validate().then(() => {
|
||||||
|
const formData = {...this.form.formData};
|
||||||
|
if ((this.$refs.parse && !this.$refs.parse.save()) || (this.$refs.beforeFetch && !this.$refs.beforeFetch.save()) || (this.$refs.error && !this.$refs.error.save())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
formData.parse = designerForm.parseFn(this.form.parse);
|
||||||
|
formData.beforeFetch = designerForm.parseFn(this.form.beforeFetch);
|
||||||
|
formData.onError = this.form.onError;
|
||||||
|
formData.label = this.form.label;
|
||||||
|
formData.type = this.form.type;
|
||||||
|
formData.to = this.to || 'options';
|
||||||
|
this.$emit('update:modelValue', formData);
|
||||||
|
this.visible = false;
|
||||||
|
}).catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
errorMessage(err[Object.keys(err)[0]][0].message);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.active();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fd-fetch-config, ._fd-fetch-config .el-badge {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-fetch-config .el-button {
|
||||||
|
font-weight: 400;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-fetch-dialog .el-tabs__header {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-fetch-dialog .form-create {
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-fetch-dialog ._fc-tabs {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-fetch-dialog ._fc-tabs .el-tabs__content {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-fetch-info {
|
||||||
|
display: flex;
|
||||||
|
font-size: 12px;
|
||||||
|
position: relative;
|
||||||
|
margin-top: 8px;
|
||||||
|
margin-left: 15px;
|
||||||
|
padding: 8px 13px;
|
||||||
|
line-height: 18px;
|
||||||
|
background: rgba(170, 170, 170, 0.1);
|
||||||
|
border-radius: 6px;
|
||||||
|
color: var(--fc-text-color-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-fetch-con .el-main {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-fetch-con .CodeMirror {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-fetch-con .CodeMirror-wrap pre.CodeMirror-line {
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
169
src/components/FetchTable.vue
Normal file
169
src/components/FetchTable.vue
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_fd-fetch-table">
|
||||||
|
<el-container class="_fd-fetch-table-con" v-if="value.length > 0">
|
||||||
|
<el-header>
|
||||||
|
<div style="width: 40%">{{ t('props.key') }}</div>
|
||||||
|
<div>{{ t('props.value') }}</div>
|
||||||
|
</el-header>
|
||||||
|
<el-main>
|
||||||
|
<template v-for="(item, idx) in value" :key="idx">
|
||||||
|
<div class="_fd-fetch-table-row">
|
||||||
|
<div class="_fd-fetch-table-key">
|
||||||
|
<el-input v-model="item.key" @blur="(e)=>onBlur(item,e)">
|
||||||
|
<template #suffix>
|
||||||
|
<VariableConfig popover
|
||||||
|
@confirm="(val) => onConfirm(item, 'key', val)"></VariableConfig>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</div>
|
||||||
|
<el-input v-model="item.value" @blur="(e)=>onBlur(item,e)">
|
||||||
|
<template #suffix>
|
||||||
|
<VariableConfig popover
|
||||||
|
@confirm="(val) => onConfirm(item, 'value', val)"></VariableConfig>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
<i class="fc-icon icon-delete-circle" @click="rm(idx)"></i>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-main>
|
||||||
|
</el-container>
|
||||||
|
<el-button link type="primary" @click="add">
|
||||||
|
<i class="fc-icon icon-add"></i> {{ t('tableOptions.add') }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
import VariableConfig from './computed/VariableConfig.vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'FetchTable',
|
||||||
|
components: {VariableConfig},
|
||||||
|
inject: ['designer'],
|
||||||
|
emits: ['update:modelValue'],
|
||||||
|
props: {
|
||||||
|
modelValue: Object,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
t() {
|
||||||
|
return this.designer.setupState.t;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
value: [],
|
||||||
|
active: null,
|
||||||
|
start: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onConfirm(item, key, val) {
|
||||||
|
if (item === this.active) {
|
||||||
|
item[key] = (item[key] || '').substring(0, this.start) + val + (item[key] || '').substring(this.start);
|
||||||
|
} else {
|
||||||
|
item[key] += val;
|
||||||
|
this.active = null;
|
||||||
|
this.start = null;
|
||||||
|
}
|
||||||
|
this.submit();
|
||||||
|
},
|
||||||
|
onBlur(item, e) {
|
||||||
|
this.active = item;
|
||||||
|
this.start = e.target.selectionStart;
|
||||||
|
this.submit();
|
||||||
|
},
|
||||||
|
submit() {
|
||||||
|
const value = {};
|
||||||
|
this.value.forEach(item => {
|
||||||
|
if (item.key && item.value) {
|
||||||
|
value[item.key] = item.value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.$emit('update:modelValue', value);
|
||||||
|
},
|
||||||
|
add() {
|
||||||
|
this.value.push({});
|
||||||
|
},
|
||||||
|
rm(idx) {
|
||||||
|
this.value.splice(idx, 1);
|
||||||
|
this.submit();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
const value = [];
|
||||||
|
Object.keys(this.modelValue || {}).forEach(k => {
|
||||||
|
value.push({
|
||||||
|
key: k,
|
||||||
|
value: this.modelValue[k]
|
||||||
|
})
|
||||||
|
});
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fd-fetch-table {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-fetch-table .el-button > span {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-fetch-table-con {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
border: 1px solid var(--fc-line-color-3);
|
||||||
|
border-bottom: 0 none;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-fetch-table-con .el-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 30px;
|
||||||
|
padding-left: 12px;
|
||||||
|
background: var(--fc-bg-color-3);
|
||||||
|
color: var(--fc-text-color-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
._fd-fetch-table-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 34px;
|
||||||
|
padding: 0 10px 4px;
|
||||||
|
border-bottom: 1px solid var(--fc-line-color-3);;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-fetch-table-row > .fc-icon {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
margin-left: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 4px;
|
||||||
|
font-size: 18px;
|
||||||
|
color: var(--fc-text-color-2);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
._fd-fetch-table-row .el-input {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
margin-top: 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-fetch-table-key {
|
||||||
|
margin-right: 15px;
|
||||||
|
width: calc(40% - 20px);
|
||||||
|
}
|
||||||
|
</style>
|
310
src/components/FieldInput.vue
Normal file
310
src/components/FieldInput.vue
Normal file
@ -0,0 +1,310 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_fd-field-input">
|
||||||
|
<i class="fc-icon icon-group" @click.stop="copy"></i>
|
||||||
|
<el-input
|
||||||
|
v-if="!fieldList.length"
|
||||||
|
v-model="value"
|
||||||
|
:readonly="fieldReadonly || disabled"
|
||||||
|
:disabled="fieldReadonly || disabled"
|
||||||
|
@focus="onFocus"
|
||||||
|
@blur="onInput"
|
||||||
|
>
|
||||||
|
<template #append v-if="!fieldReadonly">
|
||||||
|
<i class="fc-icon icon-auto" @click="makeField"></i>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
<el-tree-select
|
||||||
|
v-else
|
||||||
|
v-model="value"
|
||||||
|
:readonly="fieldReadonly || disabled"
|
||||||
|
:disabled="disabled"
|
||||||
|
:allow-create="!fieldReadonly"
|
||||||
|
:filterable="true"
|
||||||
|
:default-first-option="!fieldReadonly"
|
||||||
|
:indent="10"
|
||||||
|
:checkStrictly="isSubform && relationField !== true"
|
||||||
|
popper-class="_fd-field-popper"
|
||||||
|
@focus="onFocus"
|
||||||
|
@change="onInput"
|
||||||
|
@current-change="currentChange"
|
||||||
|
:data="fieldList"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent, nextTick, onUnmounted} from 'vue';
|
||||||
|
import uniqueId from '@form-create/utils/lib/unique';
|
||||||
|
import {copyTextToClipboard, escapeRegExp} from '../utils';
|
||||||
|
import errorMessage from '../utils/message';
|
||||||
|
import is from '@form-create/utils/lib/type';
|
||||||
|
import {deepCopy} from '@form-create/utils/lib/deepextend';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'FieldInput',
|
||||||
|
inject: ['designer'],
|
||||||
|
emits: ['update:modelValue'],
|
||||||
|
props: {
|
||||||
|
modelValue: String,
|
||||||
|
disabled: Boolean,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
fieldList() {
|
||||||
|
if (this.key) {
|
||||||
|
return this.getFieldList();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
subformFieldList() {
|
||||||
|
const fieldList = this.designer.setupState.fieldList || [];
|
||||||
|
const _fieldList = this.fieldLeafSelectable ? fieldList : this.removeLeafNodes(deepCopy(fieldList));
|
||||||
|
if (_fieldList.length) {
|
||||||
|
return _fieldList
|
||||||
|
}
|
||||||
|
return fieldList;
|
||||||
|
},
|
||||||
|
fieldReadonly() {
|
||||||
|
return this.designer.setupState.fieldReadonly;
|
||||||
|
},
|
||||||
|
isSubform() {
|
||||||
|
return this.activeRule && this.activeRule._menu.subForm;
|
||||||
|
},
|
||||||
|
activeRule() {
|
||||||
|
return this.designer.setupState.activeRule;
|
||||||
|
},
|
||||||
|
relationField() {
|
||||||
|
return this.designer.props.config.relationField;
|
||||||
|
},
|
||||||
|
fieldLeafSelectable() {
|
||||||
|
return this.designer.props.config.fieldLeafSelectable !== false;
|
||||||
|
},
|
||||||
|
t() {
|
||||||
|
return this.designer.setupState.t;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
value: this.modelValue || '',
|
||||||
|
oldValue: '',
|
||||||
|
key: 1,
|
||||||
|
activeNode: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
modelValue(n) {
|
||||||
|
this.value = n;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getFieldList() {
|
||||||
|
let fieldList = this.designer.setupState.fieldList || [];
|
||||||
|
if (this.relationField === false) {
|
||||||
|
return fieldList;
|
||||||
|
}
|
||||||
|
if (this.isSubform) {
|
||||||
|
fieldList = this.subformFieldList
|
||||||
|
} else {
|
||||||
|
const rule = this.activeRule;
|
||||||
|
let ctx = rule && rule.__fc__ && rule.__fc__.parent;
|
||||||
|
while (ctx) {
|
||||||
|
if (ctx.rule._menu && ['array', 'object'].indexOf(ctx.rule._menu.subForm) > -1) {
|
||||||
|
const _fieldList = this.findChildrenById(fieldList, ctx.rule.field) || fieldList;
|
||||||
|
if (_fieldList.length) {
|
||||||
|
fieldList = _fieldList
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
ctx = ctx.parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fieldList;
|
||||||
|
},
|
||||||
|
removeLeafNodes(tree) {
|
||||||
|
if (!Array.isArray(tree) || tree.length === 0) {
|
||||||
|
return tree;
|
||||||
|
}
|
||||||
|
|
||||||
|
function recurse(nodes) {
|
||||||
|
return nodes.filter(node => {
|
||||||
|
if (node.children && node.children.length > 0) {
|
||||||
|
node.children = recurse(node.children);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return recurse(tree);
|
||||||
|
},
|
||||||
|
findChildrenById(tree, id) {
|
||||||
|
if (!Array.isArray(tree)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (const node of tree) {
|
||||||
|
if (node.value === id) {
|
||||||
|
return node.children || [];
|
||||||
|
}
|
||||||
|
if (node.children) {
|
||||||
|
const result = this.findChildrenById(node.children, id);
|
||||||
|
if (result !== null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
copy() {
|
||||||
|
copyTextToClipboard(this.modelValue);
|
||||||
|
},
|
||||||
|
getSubChildren() {
|
||||||
|
let subChildren = this.designer.setupState.getSubFormChildren(this.activeRule) || [];
|
||||||
|
subChildren = is.trueArray(subChildren) ? subChildren : this.designer.setupState.children;
|
||||||
|
return subChildren;
|
||||||
|
},
|
||||||
|
getSubFieldChildren() {
|
||||||
|
const subChildren = this.getSubChildren();
|
||||||
|
const list = [];
|
||||||
|
const getRule = (children) => {
|
||||||
|
children && children.forEach(rule => {
|
||||||
|
if (rule && rule._fc_drag_tag && rule.field) {
|
||||||
|
list.push({...rule, children: []});
|
||||||
|
} else if (rule && rule.children) {
|
||||||
|
getRule(rule.children);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
return getRule(subChildren);
|
||||||
|
},
|
||||||
|
checkValue() {
|
||||||
|
const oldField = this.oldValue;
|
||||||
|
const temp = escapeRegExp(oldField);
|
||||||
|
let field = (this.value || '').replace(/[\s\ ]/g, '');
|
||||||
|
if (!field) {
|
||||||
|
errorMessage(this.t('computed.fieldEmpty'));
|
||||||
|
return oldField;
|
||||||
|
} else if (!/^[a-zA-Z]/.test(field)) {
|
||||||
|
errorMessage(this.t('computed.fieldChar'));
|
||||||
|
return oldField;
|
||||||
|
} else if (oldField !== field) {
|
||||||
|
const flag = field.indexOf('.') > -1;
|
||||||
|
if (flag) {
|
||||||
|
field = field.replaceAll('.', '_');
|
||||||
|
}
|
||||||
|
if (this.getSubFieldChildren().filter(v => v.field === field).length > 0) {
|
||||||
|
errorMessage(this.t('computed.fieldExist', {label: field}));
|
||||||
|
return oldField;
|
||||||
|
}
|
||||||
|
// else if (temp) {
|
||||||
|
// const regex = /"_computed"\s*:\s*(\{\s*(?:"[^"]*"\s*:\s*"(?:\\"|[^"])*"(?:,\s*)?)*\})/g;
|
||||||
|
// const subChildren = this.getSubChildren();
|
||||||
|
// const json = JSON.stringify(subChildren).replace(JSON.stringify(this.activeRule), '');
|
||||||
|
// let match;
|
||||||
|
// while ((match = regex.exec(json)) !== null) {
|
||||||
|
// const obj = JSON.parse(match[1]);
|
||||||
|
// let _exec = false;
|
||||||
|
// Object.keys(obj).forEach(k => {
|
||||||
|
// if (!_exec) {
|
||||||
|
// const fieldRag = new RegExp(`(${temp})(?![a-zA-Z0-9_$])`, 'g');
|
||||||
|
// _exec = !!obj[k].match(fieldRag);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// if (_exec) {
|
||||||
|
// errorMessage(this.t('computed.fieldUsed', {label: oldField}));
|
||||||
|
// return oldField;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
if (flag) {
|
||||||
|
return field;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.oldValue = '';
|
||||||
|
return field;
|
||||||
|
},
|
||||||
|
onFocus() {
|
||||||
|
this.oldValue = this.value;
|
||||||
|
},
|
||||||
|
makeField() {
|
||||||
|
this.oldValue = this.value;
|
||||||
|
this.value = uniqueId();
|
||||||
|
this.onInput();
|
||||||
|
},
|
||||||
|
updateRule(node) {
|
||||||
|
const update = {...node.update || {}};
|
||||||
|
if (!update.title) {
|
||||||
|
update.title = node.label;
|
||||||
|
}
|
||||||
|
this.designer.setupState.mergeRule(this.activeRule, update);
|
||||||
|
this.designer.setupState.updateRuleFormData();
|
||||||
|
},
|
||||||
|
onInput() {
|
||||||
|
if (this.value !== this.modelValue) {
|
||||||
|
this.value = this.checkValue();
|
||||||
|
if (this.value !== this.modelValue) {
|
||||||
|
const node = this.activeNode;
|
||||||
|
this.activeNode = null;
|
||||||
|
this.oldValue = this.value;
|
||||||
|
this.$emit('update:modelValue', this.value);
|
||||||
|
if (node) {
|
||||||
|
this.updateRule(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
currentChange(node) {
|
||||||
|
this.activeNode = node;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
const updateKey = () => {
|
||||||
|
nextTick(() => {
|
||||||
|
++this.key;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.designer.setupState.bus.$on('dragEnd', updateKey);
|
||||||
|
onUnmounted(() => {
|
||||||
|
this.designer.setupState.bus.$off('dragEnd', updateKey);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fd-field-input {
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-field-input > .fc-icon {
|
||||||
|
position: absolute;
|
||||||
|
right: 28px;
|
||||||
|
top: 1px;
|
||||||
|
z-index: 3;
|
||||||
|
color: #a8abb2;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-field-input .el-input-group__append {
|
||||||
|
width: 25px;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
color: var(--fc-text-color-3);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-field-popper .el-tree-node__content {
|
||||||
|
padding: 2px 0;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-field-popper .el-select-dropdown__list > .el-select-dropdown__item {
|
||||||
|
padding-left: 15px;
|
||||||
|
border-bottom: 1px solid var(--fc-line-color-3);
|
||||||
|
box-sizing: border-box;
|
||||||
|
height: 26px;
|
||||||
|
}
|
||||||
|
</style>
|
95
src/components/FieldList.vue
Normal file
95
src/components/FieldList.vue
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
<template>
|
||||||
|
<el-tree
|
||||||
|
ref="treeRef"
|
||||||
|
class="_fc-field-tree"
|
||||||
|
:data="field"
|
||||||
|
default-expand-all
|
||||||
|
:expand-on-click-node="false"
|
||||||
|
:indent="10"
|
||||||
|
@nodeClick="nodeClick"
|
||||||
|
>
|
||||||
|
<template #default="{ node, data }">
|
||||||
|
<template v-if="data.rule || data.item">
|
||||||
|
<fcDraggable :group="{name:'default', pull:'clone', put:false}" :sort="false"
|
||||||
|
:list="[{...data, _field: true}]" itemKey="label" class="_fc-field-drag">
|
||||||
|
<template #item>
|
||||||
|
<div class="_fc-field-node">
|
||||||
|
<div class="_fc-field-node-label">
|
||||||
|
<i class="fc-icon" :class="data.icon || 'icon-input'" v-if="node.isLeaf"></i>
|
||||||
|
<i class="fc-icon icon-folder" v-else></i>
|
||||||
|
<span>{{ data.label }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</fcDraggable>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<div class="_fc-field-node">
|
||||||
|
<div class="_fc-field-node-label">
|
||||||
|
<i class="fc-icon" :class="data.icon || 'icon-input'" v-if="node.isLeaf"></i>
|
||||||
|
<i class="fc-icon icon-folder" v-else></i>
|
||||||
|
<span>{{ data.label }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</el-tree>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
import fcDraggable from 'vuedraggable/src/vuedraggable';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'FieldList',
|
||||||
|
inject: ['designer'],
|
||||||
|
props: {
|
||||||
|
field: Array
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
fcDraggable
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
nodeClick(node) {
|
||||||
|
if (node.rule || node.item) {
|
||||||
|
const item = {...node};
|
||||||
|
this.designer.setupState.clickField(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fc-field-tree .el-tree-node__content {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
color: var(--fc-text-color-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-field-tree .el-tree-node__content:hover {
|
||||||
|
background-color: var(--fc-style-bg-color-1);
|
||||||
|
color: var(--fc-style-color-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-field-tree ._fc-field-drag {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-field-tree .fc-icon {
|
||||||
|
margin-right: 5px;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-field-tree .icon-folder {
|
||||||
|
color: var(--fc-style-color-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-field-node-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
</style>
|
310
src/components/FnConfig.vue
Normal file
310
src/components/FnConfig.vue
Normal file
@ -0,0 +1,310 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_fd-fn-list">
|
||||||
|
<el-badge :value="eventNum" type="warning" :hidden="eventNum < 1">
|
||||||
|
<el-button class="_fd-plain-button" plain @click="visible=true" size="small">{{ t('event.title') }}</el-button>
|
||||||
|
</el-badge>
|
||||||
|
<el-dialog class="_fd-fn-list-dialog _fd-config-dialog" :title="t('event.title')" v-model="visible" destroy-on-close
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
append-to-body
|
||||||
|
width="980px">
|
||||||
|
<el-container class="_fd-fn-list-con" style="height: 600px">
|
||||||
|
<el-aside style="width:300px;">
|
||||||
|
<el-container class="_fd-fn-list-l">
|
||||||
|
<el-header class="_fd-fn-list-head" height="40px">
|
||||||
|
<el-text type="primary" size="default">
|
||||||
|
{{ t('event.list') }}
|
||||||
|
</el-text>
|
||||||
|
</el-header>
|
||||||
|
<el-main>
|
||||||
|
<el-menu
|
||||||
|
:default-active="defActive"
|
||||||
|
v-model="activeData">
|
||||||
|
<template v-for="(item, name) in event" :key="name">
|
||||||
|
<el-menu-item :index="name">
|
||||||
|
<div class="_fd-fn-list-method" @click.stop="edit(item)">
|
||||||
|
<span>function<span>{{ name }}</span></span>
|
||||||
|
<span class="_fd-label" v-if="eventInfo[name]">{{ eventInfo[name] }}</span>
|
||||||
|
<span class="_fd-dot" v-if="item.fn"></span>
|
||||||
|
</div>
|
||||||
|
</el-menu-item>
|
||||||
|
</template>
|
||||||
|
</el-menu>
|
||||||
|
</el-main>
|
||||||
|
</el-container>
|
||||||
|
</el-aside>
|
||||||
|
<el-main>
|
||||||
|
<el-container class="_fd-fn-list-r">
|
||||||
|
<el-header class="_fd-fn-list-head" height="40px" v-if="activeData">
|
||||||
|
<el-button size="small" @click="close">{{ t('props.cancel') }}</el-button>
|
||||||
|
<el-button size="small" type="primary" @click="save">{{
|
||||||
|
t('props.save')
|
||||||
|
}}
|
||||||
|
</el-button>
|
||||||
|
</el-header>
|
||||||
|
<el-main v-if="activeData">
|
||||||
|
<FnEditor ref="fn" v-model="eventStr" :name="activeData.item.name"
|
||||||
|
:args="activeData.item.args"/>
|
||||||
|
</el-main>
|
||||||
|
</el-container>
|
||||||
|
</el-main>
|
||||||
|
</el-container>
|
||||||
|
<template #footer>
|
||||||
|
<div>
|
||||||
|
<el-button size="default" @click="visible=false">{{ t('props.cancel') }}</el-button>
|
||||||
|
<el-button type="primary" size="default" @click="submit">{{
|
||||||
|
t('props.ok')
|
||||||
|
}}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import unique from '@form-create/utils/lib/unique';
|
||||||
|
import deepExtend from '@form-create/utils/lib/deepextend';
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
import FnEditor from './FnEditor.vue';
|
||||||
|
|
||||||
|
const PREFIX = '[[FORM-CREATE-PREFIX-';
|
||||||
|
const SUFFIX = '-FORM-CREATE-SUFFIX]]';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'FnConfig',
|
||||||
|
emits: ['update:modelValue'],
|
||||||
|
props: {
|
||||||
|
modelValue: [Object, undefined, null],
|
||||||
|
eventConfig: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inject: ['designer'],
|
||||||
|
components: {
|
||||||
|
FnEditor,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
visible: false,
|
||||||
|
activeData: null,
|
||||||
|
defActive: 'no',
|
||||||
|
event: {},
|
||||||
|
cus: false,
|
||||||
|
eventStr: '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
eventInfo() {
|
||||||
|
const info = {};
|
||||||
|
this.eventConfig.forEach(v => {
|
||||||
|
info[v.name] = v.info;
|
||||||
|
});
|
||||||
|
return info;
|
||||||
|
},
|
||||||
|
t() {
|
||||||
|
return this.designer.setupState.t;
|
||||||
|
},
|
||||||
|
eventNum() {
|
||||||
|
let num = 0;
|
||||||
|
Object.keys(this.modelValue || {}).forEach(k => {
|
||||||
|
if (this.modelValue[k]) {
|
||||||
|
num++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return num;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
visible(v) {
|
||||||
|
this.event = v ? this.loadFN(deepExtend({}, this.modelValue || {})) : {};
|
||||||
|
if (!v) {
|
||||||
|
this.destroy();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getArgs(item) {
|
||||||
|
return item.args.join(', ');
|
||||||
|
},
|
||||||
|
loadFN(e) {
|
||||||
|
const val = {};
|
||||||
|
this.eventConfig.forEach(item => {
|
||||||
|
const k = item.name;
|
||||||
|
const fn = e[k] || '';
|
||||||
|
val[k] = {
|
||||||
|
item, fn
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return val;
|
||||||
|
},
|
||||||
|
parseFN(e) {
|
||||||
|
const on = {};
|
||||||
|
Object.keys(e).forEach(k => {
|
||||||
|
if (e[k].fn) {
|
||||||
|
on[k] = e[k].fn;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return on;
|
||||||
|
},
|
||||||
|
edit(data) {
|
||||||
|
data.key = unique();
|
||||||
|
this.activeData = data;
|
||||||
|
this.eventStr = data.fn || (PREFIX + `function ${data.item.name}(${this.getArgs(data.item)}){}` + SUFFIX);
|
||||||
|
this.defActive = data.item.name;
|
||||||
|
},
|
||||||
|
save() {
|
||||||
|
if (this.$refs.fn.save()) {
|
||||||
|
this.activeData.fn = this.eventStr;
|
||||||
|
this.destroy();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
destroy() {
|
||||||
|
this.activeData = null;
|
||||||
|
this.defActive = 'no';
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
this.destroy();
|
||||||
|
},
|
||||||
|
submit() {
|
||||||
|
if (this.activeData && !this.save()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.$emit('update:modelValue', this.parseFN(this.event));
|
||||||
|
this.visible = false;
|
||||||
|
this.destroy();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fd-fn-list, ._fd-fn-list .el-badge {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-fn-list .el-button {
|
||||||
|
font-weight: 400;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-fn-list-con .el-main {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-fn-list-l, ._fd-fn-list-r {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
border: 1px solid var(--fc-line-color-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-fn-list-head {
|
||||||
|
display: flex;
|
||||||
|
padding: 5px 15px;
|
||||||
|
border-bottom: 1px solid var(--fc-line-color-3);
|
||||||
|
background: var(--fc-bg-color-3);
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-fn-list-head .el-button.is-link {
|
||||||
|
color: var(--fc-style-color-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-fn-list-r {
|
||||||
|
border-left: 0 none;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-fn-list-r ._fd-fn-list-head {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-fn-list-l > .el-main, ._fd-fn-list-r > .el-main {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex: 1;
|
||||||
|
flex-basis: auto;
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-width: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-fn-list-r > .el-main {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-fn-list-l .el-menu {
|
||||||
|
padding: 0 10px 5px;
|
||||||
|
border-right: 0 none;
|
||||||
|
width: 100%;
|
||||||
|
border-top: 0 none;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-fn-list-l .el-menu-item.is-active {
|
||||||
|
background: var(--fc-bg-color-3);
|
||||||
|
color: var(--fc-text-color-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-fn-list-l .el-menu-item {
|
||||||
|
height: auto;
|
||||||
|
line-height: 1em;
|
||||||
|
border: 1px solid var(--fc-line-color-3);
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 0;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-fn-list-method {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 10px 20px 10px 0;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1em;
|
||||||
|
font-family: monospace;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-fn-list-method ._fd-label {
|
||||||
|
margin-top: 4px;
|
||||||
|
color: var(--fc-text-color-3);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-fn-list-method ._fd-dot {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
margin-top: -3px;
|
||||||
|
right: 16px;
|
||||||
|
display: block;
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
background: #00C050;
|
||||||
|
border-radius: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-fn-list-method-info > span:first-child, ._fd-fn-list-method > span:first-child {
|
||||||
|
color: #9D238C;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-fn-list-method-info > span:first-child > span, ._fd-fn-list-method > span:first-child > span {
|
||||||
|
color: var(--fc-text-color-1);
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-fn-list-con .CodeMirror {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-fn-list-con .CodeMirror-wrap pre.CodeMirror-line {
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
252
src/components/FnEditor.vue
Normal file
252
src/components/FnEditor.vue
Normal file
@ -0,0 +1,252 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_fd-fn">
|
||||||
|
<div class="_fd-fn-tip">
|
||||||
|
<div class="_fd-fn-ind"></div>
|
||||||
|
<div class="cm-keyword"><span>function {{ name }}(<template
|
||||||
|
v-for="(item, idx) in argList">{{ idx > 0 ? ', ' : '' }}<template v-if="item.type === 'string'">
|
||||||
|
<span>{{ item.name }}</span>
|
||||||
|
</template><template v-else><el-popover placement="top-start" :width="400" :hide-after="0" trigger="click"
|
||||||
|
:title="item.name"
|
||||||
|
:content="item.info || ''"
|
||||||
|
><template #reference><span class="_fd-fn-arg">{{ item.name }}<i
|
||||||
|
class="fc-icon icon-question"></i></span></template>
|
||||||
|
<template v-if="item.columns">
|
||||||
|
<el-table :data="item.columns" border>
|
||||||
|
<el-table-column width="120" property="label" :label="t('props.field')"/>
|
||||||
|
<el-table-column property="info" :label="t('event.info')"/>
|
||||||
|
<el-table-column width="80" property="type" :label="t('event.type')"/>
|
||||||
|
</el-table>
|
||||||
|
</template>
|
||||||
|
</el-popover>
|
||||||
|
</template>
|
||||||
|
</template>) {</span></div>
|
||||||
|
</div>
|
||||||
|
<div ref="editor" class="_fd-fn-editor"></div>
|
||||||
|
<div class="_fd-fn-tip">
|
||||||
|
<div class="_fd-fn-ind"></div>
|
||||||
|
<div class="cm-keyword">}</div>
|
||||||
|
</div>
|
||||||
|
<el-button v-if="visible && button" type="primary" size="small" @click="save">{{ t('props.save') }}</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import 'codemirror/lib/codemirror.css';
|
||||||
|
import 'codemirror/addon/hint/show-hint.css';
|
||||||
|
import CodeMirror from 'codemirror/lib/codemirror';
|
||||||
|
import 'codemirror/mode/javascript/javascript';
|
||||||
|
import 'codemirror/addon/hint/show-hint';
|
||||||
|
import 'codemirror/addon/hint/javascript-hint';
|
||||||
|
import {defineComponent, markRaw} from 'vue';
|
||||||
|
import {addAutoKeyMap, toJSON} from '../utils';
|
||||||
|
import errorMessage from '../utils/message';
|
||||||
|
|
||||||
|
const PREFIX = '[[FORM-CREATE-PREFIX-';
|
||||||
|
const SUFFIX = '-FORM-CREATE-SUFFIX]]';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'FnEditor',
|
||||||
|
emits: ['update:modelValue', 'change'],
|
||||||
|
props: {
|
||||||
|
modelValue: [String, Function],
|
||||||
|
name: String,
|
||||||
|
args: Array,
|
||||||
|
body: Boolean,
|
||||||
|
button: Boolean,
|
||||||
|
fnx: Boolean,
|
||||||
|
},
|
||||||
|
inject: ['designer'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
editor: null,
|
||||||
|
fn: '',
|
||||||
|
visible: false,
|
||||||
|
value: '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
modelValue(n) {
|
||||||
|
if (n != this.value && (!n || !n.__json || (n.__json && n.__json != this.value))) {
|
||||||
|
this.editor && this.editor.setValue(this.tidyValue());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
t() {
|
||||||
|
return this.designer.setupState.t;
|
||||||
|
},
|
||||||
|
argStr() {
|
||||||
|
return (this.args || []).map(arg => {
|
||||||
|
if (typeof arg === 'string') {
|
||||||
|
return arg;
|
||||||
|
}
|
||||||
|
return arg.name;
|
||||||
|
}).join(', ');
|
||||||
|
},
|
||||||
|
argList() {
|
||||||
|
return this.args.map(arg => {
|
||||||
|
if (typeof arg === 'string') {
|
||||||
|
return {
|
||||||
|
name: arg,
|
||||||
|
type: 'string'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return arg;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.load();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
save() {
|
||||||
|
const str = this.editor.getValue() || '';
|
||||||
|
if (str.trim() === '') {
|
||||||
|
this.fn = '';
|
||||||
|
} else {
|
||||||
|
let fn;
|
||||||
|
try {
|
||||||
|
fn = (new Function('return function (' + this.argStr + '){\n' + str + '\n}'))();
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
errorMessage(this.t('struct.errorMsg'));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this.body) {
|
||||||
|
this.fn = (this.fnx ? '$FNX:' : '') + str;
|
||||||
|
} else {
|
||||||
|
this.fn = PREFIX + fn + SUFFIX;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.submit();
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
submit() {
|
||||||
|
this.$emit('update:modelValue', this.fn);
|
||||||
|
this.$emit('change', this.fn);
|
||||||
|
this.value = this.fn;
|
||||||
|
this.visible = false;
|
||||||
|
},
|
||||||
|
trimString(input) {
|
||||||
|
const firstIndex = input.indexOf('{');
|
||||||
|
const lastIndex = input.lastIndexOf('}');
|
||||||
|
if (firstIndex === -1 || lastIndex === -1 || firstIndex >= lastIndex) {
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
return input.slice(firstIndex + 1, lastIndex).replace(/^\n+|\n+$/g, '');
|
||||||
|
},
|
||||||
|
tidyValue() {
|
||||||
|
let value = this.modelValue || '';
|
||||||
|
if (value.__json) {
|
||||||
|
value = value.__json;
|
||||||
|
}
|
||||||
|
if (this.fnx && typeof value === 'string' && value.indexOf('$FNX:') === 0) {
|
||||||
|
value = value.slice(5);
|
||||||
|
}
|
||||||
|
if (typeof value === 'function') {
|
||||||
|
value = this.trimString(toJSON(value)).trim();
|
||||||
|
} else if (!this.body) {
|
||||||
|
value = this.trimString(value).trim();
|
||||||
|
}
|
||||||
|
this.value = value;
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
load() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
let value = this.tidyValue();
|
||||||
|
this.editor = markRaw(CodeMirror(this.$refs.editor, {
|
||||||
|
lineNumbers: true,
|
||||||
|
mode: {name: 'javascript', globalVars: true},
|
||||||
|
extraKeys: {'Ctrl-Space': 'autocomplete'},
|
||||||
|
line: true,
|
||||||
|
tabSize: 2,
|
||||||
|
lineWrapping: true,
|
||||||
|
value,
|
||||||
|
}));
|
||||||
|
this.editor.on('inputRead', (cm, event) => {
|
||||||
|
if (event.keyCode === 32 && event.ctrlKey) { // 检测 Ctrl + Space 快捷键
|
||||||
|
CodeMirror.showHint(cm, CodeMirror.hint.javascript); // 触发代码提示
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.editor.on('change', () => {
|
||||||
|
this.visible = true;
|
||||||
|
});
|
||||||
|
addAutoKeyMap(this.editor);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
._fd-fn {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-fn .el-button {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 3px;
|
||||||
|
right: 5px;
|
||||||
|
box-shadow: 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 9px 28px 8px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-fn-editor {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
width: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-fn-editor .CodeMirror {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-fn-tip {
|
||||||
|
color: var(--fc-text-color-1);
|
||||||
|
font-family: monospace;
|
||||||
|
direction: ltr;
|
||||||
|
background: var(--fc-bg-color-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-fn-tip .cm-keyword {
|
||||||
|
color: #708;
|
||||||
|
line-height: 24px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-fn-tip .cm-keyword::-webkit-scrollbar {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-fn-ind {
|
||||||
|
background-color: var(--fc-bg-color-3);
|
||||||
|
width: 29px;
|
||||||
|
height: 24px;
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 4px;
|
||||||
|
border-right: 1px solid var(--fc-line-color-2);
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-fn-arg {
|
||||||
|
text-decoration: underline;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-fn-arg i {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--fc-style-color-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
101
src/components/FnInput.vue
Normal file
101
src/components/FnInput.vue
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_fd-fn-input">
|
||||||
|
<el-badge type="warning" is-dot :hidden="!configured">
|
||||||
|
<el-button class="_fd-plain-button" plain @click="visible=true" size="small">
|
||||||
|
<slot>
|
||||||
|
{{t('event.action')}}
|
||||||
|
</slot>
|
||||||
|
</el-button>
|
||||||
|
</el-badge>
|
||||||
|
<el-dialog class="_fd-fn-input-dialog _fd-config-dialog" :title="title || t('struct.title')" v-model="visible"
|
||||||
|
destroy-on-close
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
append-to-body width="800px">
|
||||||
|
<FnEditor ref="editor" v-model="value" :name="name" :args="args" :body="body" :fnx="fnx"></FnEditor>
|
||||||
|
<template #footer>
|
||||||
|
<div>
|
||||||
|
<el-button @click="visible = false" size="default">{{ t('props.cancel') }}</el-button>
|
||||||
|
<el-button type="primary" @click="onOk" size="default">{{ t('props.ok') }}</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import 'codemirror/lib/codemirror.css';
|
||||||
|
import 'codemirror/mode/javascript/javascript';
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
import FnEditor from './FnEditor.vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'FnInput',
|
||||||
|
components: {FnEditor},
|
||||||
|
emits: ['update:modelValue', 'change'],
|
||||||
|
props: {
|
||||||
|
modelValue: [String, Function],
|
||||||
|
name: String,
|
||||||
|
args: Array,
|
||||||
|
title: String,
|
||||||
|
body: Boolean,
|
||||||
|
fnx: Boolean,
|
||||||
|
defaultValue: {
|
||||||
|
require: false
|
||||||
|
},
|
||||||
|
validate: Function,
|
||||||
|
},
|
||||||
|
inject: ['designer'],
|
||||||
|
computed: {
|
||||||
|
t() {
|
||||||
|
return this.designer.setupState.t;
|
||||||
|
},
|
||||||
|
configured() {
|
||||||
|
return !!this.modelValue;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
visible: false,
|
||||||
|
value: this.modelValue
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
modelValue(n){
|
||||||
|
this.value = n;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onOk() {
|
||||||
|
if(this.$refs.editor.save()) {
|
||||||
|
this.$emit('update:modelValue', this.value);
|
||||||
|
this.$emit('change', this.value);
|
||||||
|
this.visible = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fd-fn-input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-fn-input .el-badge {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-fn-input .el-button {
|
||||||
|
font-weight: 400;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-fn-input-dialog .CodeMirror-lint-tooltip {
|
||||||
|
z-index: 2021 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-fn-input-dialog .el-dialog__body {
|
||||||
|
padding: 0px;
|
||||||
|
height: 500px;
|
||||||
|
}
|
||||||
|
</style>
|
151
src/components/FormList.vue
Normal file
151
src/components/FormList.vue
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
<template>
|
||||||
|
<el-tree
|
||||||
|
ref="treeRef"
|
||||||
|
class="_fc-form-tree"
|
||||||
|
:data="list"
|
||||||
|
:indent="5"
|
||||||
|
>
|
||||||
|
<template #default="{ node, data }">
|
||||||
|
<div class="_fc-form-node">
|
||||||
|
<div class="_fc-form-node-label">
|
||||||
|
<i class="fc-icon icon-form" v-if="node.isLeaf"></i>
|
||||||
|
<i class="fc-icon icon-folder" v-else></i>
|
||||||
|
<div>
|
||||||
|
<div>{{ data.label }}</div>
|
||||||
|
<span class="_fc-form-node-info" v-if="data.info">{{ data.info }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<i class="fc-icon icon-edit" v-if="node.isLeaf && !data.disabled" @click.stop="nodeClick(data)"></i>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-tree>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
import {ElLoading} from 'element-plus';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'FormList',
|
||||||
|
inject: ['designer'],
|
||||||
|
props: {
|
||||||
|
list: Array
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
timerId: undefined,
|
||||||
|
load: undefined,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
loadConfig(config, data) {
|
||||||
|
this.designer.setupState.openInputData(false);
|
||||||
|
this.designer.setupState.pageData = [];
|
||||||
|
this.designer.setupState.setOptions(config.options);
|
||||||
|
this.designer.setupState.setRule(config.rule);
|
||||||
|
this.designer.emit('switchForm', data);
|
||||||
|
},
|
||||||
|
starLoad() {
|
||||||
|
if (!this.loading) {
|
||||||
|
this.load = ElLoading.service({
|
||||||
|
target: document.getElementsByClassName('_fc-m-drag')[0],
|
||||||
|
lock: true,
|
||||||
|
});
|
||||||
|
this.loading = true;
|
||||||
|
this.timerId = setTimeout(() => {
|
||||||
|
this.endLoad();
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
endLoad() {
|
||||||
|
this.loading = false;
|
||||||
|
this.load && this.load.close();
|
||||||
|
this.timerId && clearTimeout(this.timerId);
|
||||||
|
this.load = undefined;
|
||||||
|
this.timerId = undefined;
|
||||||
|
},
|
||||||
|
nodeClick(data) {
|
||||||
|
if (this.loading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let config = {
|
||||||
|
rule: data.rule || [],
|
||||||
|
options: data.options || {}
|
||||||
|
}
|
||||||
|
if (data.load) {
|
||||||
|
const value = data.load(data);
|
||||||
|
if (value && value.then) {
|
||||||
|
this.starLoad();
|
||||||
|
value.then(res => {
|
||||||
|
if (res.rule) {
|
||||||
|
config.rule = res.rule;
|
||||||
|
}
|
||||||
|
if (res.options) {
|
||||||
|
config.options = res.options;
|
||||||
|
}
|
||||||
|
this.loadConfig(config, data);
|
||||||
|
this.endLoad();
|
||||||
|
}).catch(e => {
|
||||||
|
this.endLoad();
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
} else if (value) {
|
||||||
|
if (value.rule) {
|
||||||
|
config.rule = value.rule;
|
||||||
|
}
|
||||||
|
if (value.options) {
|
||||||
|
config.options = value.options;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.loadConfig(config, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fc-l ._fc-form-tree .el-tree-node__content {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
color: var(--fc-text-color-1);
|
||||||
|
height: auto;
|
||||||
|
padding: 5px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-form-tree .el-tree-node__content:hover {
|
||||||
|
background-color: var(--fc-style-bg-color-1);
|
||||||
|
color: var(--fc-style-color-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-form-tree .fc-icon {
|
||||||
|
margin-right: 5px;
|
||||||
|
font-size: 18px;
|
||||||
|
color: var(--fc-style-color-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-form-tree .icon-folder {
|
||||||
|
color: #FFBA00;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-form-node {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-direction: row;
|
||||||
|
padding-right: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-form-node-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
white-space: normal;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-form-node-info {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--fc-text-color-2);
|
||||||
|
}
|
||||||
|
</style>
|
383
src/components/GlobalClassConfig.vue
Normal file
383
src/components/GlobalClassConfig.vue
Normal file
@ -0,0 +1,383 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_fd-gcc">
|
||||||
|
<el-badge :value="eventNum" type="warning" :hidden="eventNum < 1">
|
||||||
|
<el-button class="_fd-plain-button" plain @click="open" size="small">{{ t('class.title') }}</el-button>
|
||||||
|
</el-badge>
|
||||||
|
<el-dialog class="_fd-gcc-dialog _fd-config-dialog" v-model="visible" destroy-on-close
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
append-to-body
|
||||||
|
width="700px">
|
||||||
|
<template #header>
|
||||||
|
{{ t('form.globalClass') }}
|
||||||
|
<Warning :tooltip="t('warning.globalClass')"></Warning>
|
||||||
|
</template>
|
||||||
|
<el-container class="_fd-gcc-con" style="height: 600px">
|
||||||
|
<el-aside style="width:255px;">
|
||||||
|
<el-container class="_fd-gcc-l">
|
||||||
|
<el-header class="_fd-gcc-head" height="40px">
|
||||||
|
<el-button link type="primary" size="default" @click="cusEvent">
|
||||||
|
{{ t('class.create') }}
|
||||||
|
</el-button>
|
||||||
|
</el-header>
|
||||||
|
<el-main>
|
||||||
|
<el-menu>
|
||||||
|
<el-menu-item :class="{'is-active':activeStyle, '_fd-gcc-default': true}">
|
||||||
|
<div class="_fd-gcc-title" @click.stop="changeStyle">
|
||||||
|
<div class="_fd-gcc-method">
|
||||||
|
<span class="_fd-label">{{ t('form.globalClass') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-menu-item>
|
||||||
|
<template v-for="(item, key) in value">
|
||||||
|
<el-menu-item :class="{'is-active':key === activeIdx}">
|
||||||
|
<div class="_fd-gcc-title" @click.stop="active(key)">
|
||||||
|
<div class="_fd-gcc-method">
|
||||||
|
<span>.{{ key }}</span>
|
||||||
|
<span class="_fd-label" v-if="item.label">{{ item.label }}</span>
|
||||||
|
</div>
|
||||||
|
<i class="fc-icon icon-delete" v-if="item.deletable !== false" @click.stop="rm(key)"></i>
|
||||||
|
</div>
|
||||||
|
</el-menu-item>
|
||||||
|
</template>
|
||||||
|
<el-menu-item v-if="cus" style="padding-left: 10px;">
|
||||||
|
<div class="_fd-gcc-title" @click.stop>
|
||||||
|
<el-input type="text" v-model="cusValue" size="default"
|
||||||
|
@keydown.enter="addCus"
|
||||||
|
:placeholder="t('class.placeholder')">
|
||||||
|
</el-input>
|
||||||
|
<div>
|
||||||
|
<i class="fc-icon icon-add" @click.stop="addCus"></i>
|
||||||
|
<i class="fc-icon icon-delete" @click.stop="closeCus"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-menu-item>
|
||||||
|
</el-menu>
|
||||||
|
</el-main>
|
||||||
|
</el-container>
|
||||||
|
</el-aside>
|
||||||
|
<el-main>
|
||||||
|
<el-container class="_fd-gcc-r">
|
||||||
|
<el-header class="_fd-gcc-head" height="40px" v-if="activeIdx || activeStyle">
|
||||||
|
<el-button size="small" @click="close">{{ t('props.cancel') }}</el-button>
|
||||||
|
<el-button size="small" type="primary" @click="save">{{
|
||||||
|
t('props.save')
|
||||||
|
}}
|
||||||
|
</el-button>
|
||||||
|
</el-header>
|
||||||
|
<el-main v-if="activeIdx || activeStyle" :key="activeIdx"
|
||||||
|
:class="activeStyle ? '_fd-gcc-style' : ''">
|
||||||
|
<template v-if="activeStyle">
|
||||||
|
<StyleEditor ref="editor" v-model="content"></StyleEditor>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<el-form size="small">
|
||||||
|
<StyleConfig v-model="handle"/>
|
||||||
|
</el-form>
|
||||||
|
</template>
|
||||||
|
</el-main>
|
||||||
|
</el-container>
|
||||||
|
</el-main>
|
||||||
|
</el-container>
|
||||||
|
<template #footer>
|
||||||
|
<div>
|
||||||
|
<el-button size="default" @click="visible=false">{{ t('props.cancel') }}</el-button>
|
||||||
|
<el-button type="primary" size="default" @click="submit">{{
|
||||||
|
t('props.ok')
|
||||||
|
}}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import unique from '@form-create/utils/lib/unique';
|
||||||
|
import {deepCopy} from '@form-create/utils/lib/deepextend';
|
||||||
|
import {defineComponent, markRaw} from 'vue';
|
||||||
|
import {getInjectArg} from '../utils';
|
||||||
|
import StyleConfig from './style/StyleConfig.vue';
|
||||||
|
import toLine from '@form-create/utils/lib/toline';
|
||||||
|
import StyleEditor from './style/StyleEditor.vue';
|
||||||
|
import Warning from './Warning.vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'GlobalClassConfig',
|
||||||
|
emits: ['update:modelValue'],
|
||||||
|
props: {
|
||||||
|
modelValue: Object,
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
Warning,
|
||||||
|
StyleEditor,
|
||||||
|
StyleConfig,
|
||||||
|
},
|
||||||
|
inject: ['designer'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
visible: false,
|
||||||
|
activeIdx: '',
|
||||||
|
value: {},
|
||||||
|
cus: false,
|
||||||
|
cusValue: '',
|
||||||
|
handle: '',
|
||||||
|
styleEl: null,
|
||||||
|
content: '',
|
||||||
|
activeStyle: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
t() {
|
||||||
|
return this.designer.setupState.t;
|
||||||
|
},
|
||||||
|
eventNum() {
|
||||||
|
return Object.keys(this.modelValue || {}).length;
|
||||||
|
},
|
||||||
|
fnArgs() {
|
||||||
|
return [getInjectArg(this.t)];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
visible(v) {
|
||||||
|
if (v) {
|
||||||
|
this.activeIdx = '';
|
||||||
|
this.value = deepCopy(this.modelValue || {});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modelValue() {
|
||||||
|
this.updateGlobalStyle();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
open() {
|
||||||
|
this.visible = true;
|
||||||
|
},
|
||||||
|
changeStyle() {
|
||||||
|
this.content = this.designer.setupState.formOptions.style || '#_demo1{\n display:flex;\n}\n\n._demo2{\n display:flex;\n}';
|
||||||
|
this.activeStyle = true;
|
||||||
|
this.activeIdx = '';
|
||||||
|
},
|
||||||
|
active(idx) {
|
||||||
|
this.activeStyle = false;
|
||||||
|
if (this.activeIdx !== idx) {
|
||||||
|
this.handle = this.value[idx].style || '';
|
||||||
|
this.activeIdx = idx;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
addCus() {
|
||||||
|
const label = this.cusValue && this.cusValue.trim();
|
||||||
|
if (label) {
|
||||||
|
const key = 'cls_' + unique();
|
||||||
|
this.value[key] = {
|
||||||
|
label,
|
||||||
|
style: {},
|
||||||
|
};
|
||||||
|
this.active(key);
|
||||||
|
this.closeCus();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
closeCus() {
|
||||||
|
this.cus = false;
|
||||||
|
this.cusValue = '';
|
||||||
|
},
|
||||||
|
cusEvent() {
|
||||||
|
this.cus = true;
|
||||||
|
},
|
||||||
|
save() {
|
||||||
|
if (this.activeStyle) {
|
||||||
|
this.$refs.editor.save();
|
||||||
|
this.designer.setupState.formOptions.style = this.content;
|
||||||
|
this.activeStyle = false;
|
||||||
|
} else {
|
||||||
|
this.value[this.activeIdx].style = this.handle;
|
||||||
|
this.activeIdx = '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rm(key) {
|
||||||
|
delete this.value[key];
|
||||||
|
if (key === this.activeIdx) {
|
||||||
|
this.activeIdx = '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
this.activeIdx = '';
|
||||||
|
},
|
||||||
|
submit() {
|
||||||
|
if (this.activeIdx || this.activeStyle) {
|
||||||
|
this.save();
|
||||||
|
}
|
||||||
|
this.$emit('update:modelValue', {...this.value});
|
||||||
|
this.visible = false;
|
||||||
|
},
|
||||||
|
updateGlobalStyle() {
|
||||||
|
let content = '';
|
||||||
|
const globalClass = this.modelValue || {};
|
||||||
|
Object.keys(globalClass).forEach(k => {
|
||||||
|
let subCss = '';
|
||||||
|
globalClass[k].style && Object.keys(globalClass[k].style).forEach(key => {
|
||||||
|
subCss += toLine(key) + ':' + globalClass[k].style[key] + ';';
|
||||||
|
});
|
||||||
|
if (globalClass[k].content) {
|
||||||
|
subCss += globalClass[k].content + ';';
|
||||||
|
}
|
||||||
|
if (subCss) {
|
||||||
|
content += `.${k}{${subCss}}`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (content) {
|
||||||
|
this.styleEl.innerHTML = content;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.styleEl = markRaw(document.createElement('style'));
|
||||||
|
this.styleEl.type = 'text/css';
|
||||||
|
document.head.appendChild(this.styleEl);
|
||||||
|
this.updateGlobalStyle();
|
||||||
|
},
|
||||||
|
unmounted() {
|
||||||
|
document.head.removeChild(this.styleEl);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fd-gcc, ._fd-gcc .el-badge {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gcc .el-button {
|
||||||
|
font-weight: 400;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gcc-con .el-main {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gcc-l, ._fd-gcc-r {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
border: 1px solid var(--fc-line-color-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gcc-head {
|
||||||
|
display: flex;
|
||||||
|
padding: 5px 15px;
|
||||||
|
border-bottom: 1px solid var(--fc-line-color-3);
|
||||||
|
background: var(--fc-bg-color-3);
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gcc-head .el-button.is-link {
|
||||||
|
color: var(--fc-style-color-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gcc-r {
|
||||||
|
border-left: 0 none;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gcc-r ._fd-gcc-head {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gcc-l > .el-main, ._fd-gcc-r > .el-main {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex: 1;
|
||||||
|
flex-basis: auto;
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-width: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gcc-r > .el-main {
|
||||||
|
padding: 20px;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gcc-r > .el-main._fd-gcc-style {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gcc-r .el-form-item {
|
||||||
|
margin-bottom: 10px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gcc-l .el-menu {
|
||||||
|
padding: 0 10px 5px;
|
||||||
|
border-right: 0 none;
|
||||||
|
width: 100%;
|
||||||
|
border-top: 0 none;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gcc-l .el-menu-item.is-active {
|
||||||
|
background: var(--fc-bg-color-3);
|
||||||
|
color: var(--fc-style-color-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gcc-l .el-menu-item {
|
||||||
|
height: auto;
|
||||||
|
line-height: 1em;
|
||||||
|
border: 1px solid var(--fc-line-color-3);
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 0;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gcc-default.is-active ._fd-label {
|
||||||
|
color: var(--fc-style-color-1);
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gcc-method {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
width: 175px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: monospace;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
color: #923B76;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gcc-method ._fd-label {
|
||||||
|
color: var(--fc-text-color-3);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gcc-method span + ._fd-label {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gcc-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gcc-title .el-input {
|
||||||
|
width: 160px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gcc-title .fc-icon {
|
||||||
|
margin-right: 6px;
|
||||||
|
font-size: 18px;
|
||||||
|
color: var(--fc-text-color-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gcc-title .el-input__wrapper {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gcc-con .el-menu-item.is-active i {
|
||||||
|
color: var(--fc-text-color-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
93
src/components/GlobalClassSelect.vue
Normal file
93
src/components/GlobalClassSelect.vue
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_fd-gcs">
|
||||||
|
<el-select v-model="value"
|
||||||
|
multiple
|
||||||
|
filterable
|
||||||
|
allow-create
|
||||||
|
default-first-option
|
||||||
|
:reserve-keyword="false"
|
||||||
|
clearable @change="input">
|
||||||
|
<el-option
|
||||||
|
v-for="item in options"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<div class="_fd-gcs-handle">
|
||||||
|
<div class="_fc-manage-text" @click="openConfig"><i
|
||||||
|
class="fc-icon icon-setting"/></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'GlobalClassSelect',
|
||||||
|
emits: ['update:modelValue'],
|
||||||
|
props: {
|
||||||
|
modelValue: [Array, String],
|
||||||
|
to: String,
|
||||||
|
},
|
||||||
|
inject: ['designer'],
|
||||||
|
computed: {
|
||||||
|
t() {
|
||||||
|
return this.designer.setupState.t;
|
||||||
|
},
|
||||||
|
options() {
|
||||||
|
return Object.keys((this.designer.setupState.formOptions.globalClass || {})).map(k => {
|
||||||
|
return {label: this.designer.setupState.formOptions.globalClass[k].label, value: k}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
modelValue() {
|
||||||
|
this.tidyValue();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
value: [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
tidyValue() {
|
||||||
|
this.value = Array.isArray(this.modelValue) ? this.modelValue : (this.modelValue || '').split(' ').filter(v => !!v);
|
||||||
|
},
|
||||||
|
openConfig() {
|
||||||
|
this.designer.setupState.openGlobalClassDialog();
|
||||||
|
},
|
||||||
|
input() {
|
||||||
|
this.$emit('update:modelValue', this.value.join(' '));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.tidyValue();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fd-gcs {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gcs .el-select {
|
||||||
|
width: 190px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gcs-handle {
|
||||||
|
display: inline-flex;
|
||||||
|
height: 14px;
|
||||||
|
line-height: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gcs-handle ._fc-manage-text {
|
||||||
|
border-left: 1px solid var(--fc-line-color-3);
|
||||||
|
padding-left: 4px;
|
||||||
|
}
|
||||||
|
</style>
|
325
src/components/GlobalEventConfig.vue
Normal file
325
src/components/GlobalEventConfig.vue
Normal file
@ -0,0 +1,325 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_fd-gec">
|
||||||
|
<el-badge :value="eventNum" type="warning" :hidden="eventNum < 1">
|
||||||
|
<el-button class="_fd-plain-button" plain @click="open" size="small">{{ t('event.title') }}</el-button>
|
||||||
|
</el-badge>
|
||||||
|
<el-dialog class="_fd-gec-dialog _fd-config-dialog" v-model="visible" destroy-on-close
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
append-to-body
|
||||||
|
width="980px">
|
||||||
|
<template #header>
|
||||||
|
{{ t('form.globalEvent') }}<Warning :tooltip="t('warning.globalEvent')"></Warning>
|
||||||
|
</template>
|
||||||
|
<el-container class="_fd-gec-con" style="height: 600px">
|
||||||
|
<el-aside style="width:300px;">
|
||||||
|
<el-container class="_fd-gec-l">
|
||||||
|
<el-header class="_fd-gec-head" height="40px">
|
||||||
|
<el-button link type="primary" size="default" @click="cusEvent">
|
||||||
|
{{ t('event.create') }}
|
||||||
|
</el-button>
|
||||||
|
</el-header>
|
||||||
|
<el-main>
|
||||||
|
<el-menu>
|
||||||
|
<template v-for="(item, key) in event">
|
||||||
|
<el-menu-item :class="{'is-active':key === activeIdx}">
|
||||||
|
<div class="_fd-gec-title" @click.stop="active(key)">
|
||||||
|
<div class="_fd-gec-method">
|
||||||
|
<span>{{ key }}</span>
|
||||||
|
<span class="_fd-label" v-if="item.label">{{ item.label }}</span>
|
||||||
|
</div>
|
||||||
|
<i class="fc-icon icon-delete" v-if="item.deletable !== false" @click.stop="rm(key)"></i>
|
||||||
|
</div>
|
||||||
|
</el-menu-item>
|
||||||
|
</template>
|
||||||
|
<el-menu-item v-if="cus" style="padding-left: 10px;">
|
||||||
|
<div class="_fd-gec-title" @click.stop>
|
||||||
|
<el-input type="text" v-model="cusValue" size="default"
|
||||||
|
@keydown.enter="addCus"
|
||||||
|
:placeholder="t('event.placeholder')">
|
||||||
|
</el-input>
|
||||||
|
<div>
|
||||||
|
<i class="fc-icon icon-add" @click.stop="addCus"></i>
|
||||||
|
<i class="fc-icon icon-delete" @click.stop="closeCus"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-menu-item>
|
||||||
|
</el-menu>
|
||||||
|
</el-main>
|
||||||
|
</el-container>
|
||||||
|
</el-aside>
|
||||||
|
<el-main>
|
||||||
|
<el-container class="_fd-gec-r">
|
||||||
|
<el-header class="_fd-gec-head" height="40px" v-if="activeIdx">
|
||||||
|
<el-button size="small" @click="close">{{ t('props.cancel') }}</el-button>
|
||||||
|
<el-button size="small" type="primary" @click="save">{{
|
||||||
|
t('props.save')
|
||||||
|
}}
|
||||||
|
</el-button>
|
||||||
|
</el-header>
|
||||||
|
<el-main v-if="activeIdx" :key="activeIdx">
|
||||||
|
<FnEditor v-model="handle" name="handle" :args="fnArgs"
|
||||||
|
ref="data"></FnEditor>
|
||||||
|
</el-main>
|
||||||
|
</el-container>
|
||||||
|
</el-main>
|
||||||
|
</el-container>
|
||||||
|
<template #footer>
|
||||||
|
<div>
|
||||||
|
<el-button size="default" @click="visible=false">{{ t('props.cancel') }}</el-button>
|
||||||
|
<el-button type="primary" size="default" @click="submit">{{
|
||||||
|
t('props.ok')
|
||||||
|
}}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import unique from '@form-create/utils/lib/unique';
|
||||||
|
import {deepCopy} from '@form-create/utils/lib/deepextend';
|
||||||
|
import FnEditor from './FnEditor.vue';
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
import {getInjectArg} from '../utils';
|
||||||
|
import Warning from './Warning.vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'GlobalEventConfig',
|
||||||
|
emits: ['update:modelValue'],
|
||||||
|
props: {
|
||||||
|
modelValue: Object,
|
||||||
|
eventName: Array,
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
Warning,
|
||||||
|
FnEditor,
|
||||||
|
},
|
||||||
|
inject: ['designer'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
visible: false,
|
||||||
|
activeIdx: '',
|
||||||
|
event: {},
|
||||||
|
cus: false,
|
||||||
|
cusValue: '',
|
||||||
|
handle: '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
t() {
|
||||||
|
return this.designer.setupState.t;
|
||||||
|
},
|
||||||
|
eventNum() {
|
||||||
|
return Object.keys(this.modelValue || {}).length;
|
||||||
|
},
|
||||||
|
fnArgs() {
|
||||||
|
return [getInjectArg(this.t)];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
visible(v) {
|
||||||
|
if (v) {
|
||||||
|
this.activeIdx = '';
|
||||||
|
this.event = deepCopy(this.modelValue || {});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
open() {
|
||||||
|
this.visible = true;
|
||||||
|
},
|
||||||
|
active(idx) {
|
||||||
|
if (this.activeIdx !== idx) {
|
||||||
|
this.handle = this.event[idx].handle || '';
|
||||||
|
this.activeIdx = idx;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
addCus() {
|
||||||
|
const label = this.cusValue && this.cusValue.trim();
|
||||||
|
if (label) {
|
||||||
|
const key = 'event_' + unique();
|
||||||
|
this.event[key] = {
|
||||||
|
label,
|
||||||
|
handle: '',
|
||||||
|
};
|
||||||
|
this.active(key);
|
||||||
|
this.closeCus();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
closeCus() {
|
||||||
|
this.cus = false;
|
||||||
|
this.cusValue = '';
|
||||||
|
},
|
||||||
|
cusEvent() {
|
||||||
|
this.cus = true;
|
||||||
|
},
|
||||||
|
save() {
|
||||||
|
if (!this.$refs.data.save()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.event[this.activeIdx].handle = this.handle;
|
||||||
|
this.activeIdx = '';
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
rm(key) {
|
||||||
|
delete this.event[key];
|
||||||
|
if (key === this.activeIdx) {
|
||||||
|
this.activeIdx = '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
this.activeIdx = '';
|
||||||
|
},
|
||||||
|
submit() {
|
||||||
|
if (this.activeIdx && !this.save()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.$emit('update:modelValue', {...this.event});
|
||||||
|
this.visible = false;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
beforeCreate() {
|
||||||
|
window.$inject = {
|
||||||
|
$f: {},
|
||||||
|
rule: [],
|
||||||
|
self: {},
|
||||||
|
option: {},
|
||||||
|
inject: {},
|
||||||
|
args: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fd-gec, ._fd-gec .el-badge {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gec .el-button {
|
||||||
|
font-weight: 400;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gec-con .el-main {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gec-l, ._fd-gec-r {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
border: 1px solid var(--fc-line-color-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gec-head {
|
||||||
|
display: flex;
|
||||||
|
padding: 5px 15px;
|
||||||
|
border-bottom: 1px solid var(--fc-line-color-3);
|
||||||
|
background: var(--fc-bg-color-3);
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gec-head .el-button.is-link {
|
||||||
|
color: var(--fc-style-color-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gec-r {
|
||||||
|
border-left: 0 none;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gec-r ._fd-gec-head {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gec-l > .el-main, ._fd-gec-r > .el-main {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex: 1;
|
||||||
|
flex-basis: auto;
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-width: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gec-r > .el-main {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gec-l .el-menu {
|
||||||
|
padding: 0 10px 5px;
|
||||||
|
border-right: 0 none;
|
||||||
|
width: 100%;
|
||||||
|
border-top: 0 none;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gec-l .el-menu-item.is-active {
|
||||||
|
background: var(--fc-bg-color-3);
|
||||||
|
color: var(--fc-text-color-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gec-l .el-menu-item {
|
||||||
|
height: auto;
|
||||||
|
line-height: 1em;
|
||||||
|
border: 1px solid var(--fc-line-color-3);
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 0;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gec-method {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
width: 225px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: monospace;
|
||||||
|
color: #9D238C;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gec-method ._fd-label {
|
||||||
|
margin-top: 4px;
|
||||||
|
color: var(--fc-text-color-3);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gec-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gec-title .el-input {
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gec-title .fc-icon {
|
||||||
|
margin-right: 6px;
|
||||||
|
font-size: 18px;
|
||||||
|
color: var(--fc-text-color-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gec-title .el-input__wrapper {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gec-con .el-menu-item.is-active i {
|
||||||
|
color: var(--fc-text-color-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gec-con .CodeMirror {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gec-con .CodeMirror-wrap pre.CodeMirror-line {
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
528
src/components/GlobalFetchConfig.vue
Normal file
528
src/components/GlobalFetchConfig.vue
Normal file
@ -0,0 +1,528 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_fd-gfc">
|
||||||
|
<el-badge :value="dataNum" type="warning" :hidden="dataNum < 1">
|
||||||
|
<el-button class="_fd-plain-button" plain @click="open" size="small">{{ t('fetch.title') }}</el-button>
|
||||||
|
</el-badge>
|
||||||
|
<el-dialog class="_fd-gfc-dialog _fd-config-dialog" v-model="visible" destroy-on-close
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
append-to-body
|
||||||
|
width="980px">
|
||||||
|
<template #header>
|
||||||
|
{{ t('form.globalFetch') }}
|
||||||
|
<Warning :tooltip="t('warning.globalFetch')"></Warning>
|
||||||
|
</template>
|
||||||
|
<el-container class="_fd-gfc-con" style="height: 600px">
|
||||||
|
<el-aside style="width:300px;">
|
||||||
|
<el-container class="_fd-gfc-l">
|
||||||
|
<el-header class="_fd-gfc-head" height="40px">
|
||||||
|
<el-dropdown trigger="click" size="default">
|
||||||
|
<el-button link type="primary" size="default">
|
||||||
|
{{ t('fetch.create') }}<i class="fc-icon icon-down" style="font-size: 14px;"></i>
|
||||||
|
</el-button>
|
||||||
|
<template #dropdown>
|
||||||
|
<el-dropdown-menu>
|
||||||
|
<el-dropdown-item v-for="(label, key) in types" :key="key"
|
||||||
|
@click="cusEvent(key)">
|
||||||
|
<div>{{ label }}</div>
|
||||||
|
</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</template>
|
||||||
|
</el-dropdown>
|
||||||
|
</el-header>
|
||||||
|
<el-main>
|
||||||
|
<el-menu>
|
||||||
|
<template v-for="(item, key) in list">
|
||||||
|
<el-menu-item :class="{'is-active':key === activeIdx}">
|
||||||
|
<div class="_fd-gfc-title" @click.stop="active(key)">
|
||||||
|
<div class="_fd-gfc-method">
|
||||||
|
<span>{{ key }}</span>
|
||||||
|
<span class="_fd-label" v-if="item.label">{{ item.label }}</span>
|
||||||
|
</div>
|
||||||
|
<i class="fc-icon icon-delete" v-if="item.deletable !== false" @click.stop="rm(key)"></i>
|
||||||
|
</div>
|
||||||
|
</el-menu-item>
|
||||||
|
</template>
|
||||||
|
<el-menu-item v-if="cus" style="padding-left: 10px;">
|
||||||
|
<div class="_fd-gfc-title" @click.stop>
|
||||||
|
<el-input type="text" v-model="cusValue" size="default"
|
||||||
|
@keydown.enter="addCus"
|
||||||
|
:placeholder="t('fetch.placeholder')">
|
||||||
|
</el-input>
|
||||||
|
<div>
|
||||||
|
<i class="fc-icon icon-add" @click.stop="addCus"></i>
|
||||||
|
<i class="fc-icon icon-delete" @click.stop="closeCus"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-menu-item>
|
||||||
|
</el-menu>
|
||||||
|
</el-main>
|
||||||
|
</el-container>
|
||||||
|
</el-aside>
|
||||||
|
<el-main>
|
||||||
|
<el-container class="_fd-gfc-r">
|
||||||
|
<el-header class="_fd-gfc-head" height="40px" v-if="activeIdx">
|
||||||
|
<el-button size="small" @click="close">{{ t('props.cancel') }}</el-button>
|
||||||
|
<el-button size="small" type="primary" @click="save">{{
|
||||||
|
t('props.save')
|
||||||
|
}}
|
||||||
|
</el-button>
|
||||||
|
</el-header>
|
||||||
|
<el-main v-if="activeIdx" :key="activeIdx">
|
||||||
|
<template v-if="list[activeIdx].type === 'fetch'">
|
||||||
|
<el-tabs model-value="first" class="_fc-tabs" style="width: 100%">
|
||||||
|
<el-tab-pane :label="t('fetch.config')" name="first" style="padding-right: 15px;">
|
||||||
|
<div class="_fd-gfc-info">
|
||||||
|
{{ t('fetch.info') }}
|
||||||
|
</div>
|
||||||
|
<DragForm v-model:api="form.api" v-model="form.formData" :rule="form.rule"
|
||||||
|
:option="form.options">
|
||||||
|
<template #title="scope">
|
||||||
|
<template v-if="scope.rule.warning">
|
||||||
|
<Warning :tooltip="scope.rule.warning">
|
||||||
|
{{ scope.rule.title }}
|
||||||
|
</Warning>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
{{ scope.rule.title }}
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</DragForm>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane lazy :label="t('fetch.beforeFetch')" name="second">
|
||||||
|
<template #label>
|
||||||
|
{{ t('fetch.beforeFetch') }}
|
||||||
|
<Warning :tooltip="t('warning.beforeFetch')"></Warning>
|
||||||
|
</template>
|
||||||
|
<FnEditor style="height: 100%;" v-model="form.beforeFetch" name="beforeFetch"
|
||||||
|
:args="['config', 'data']"
|
||||||
|
ref="beforeFetch"></FnEditor>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane lazy name="third">
|
||||||
|
<template #label>
|
||||||
|
{{ t('fetch.parse') }}
|
||||||
|
<Warning :tooltip="t('warning.fetchParse')"></Warning>
|
||||||
|
</template>
|
||||||
|
<FnEditor style="height: 100%;" v-model="form.parse" name="parse"
|
||||||
|
:args="[{name:'res', info: t('fetch.response')}, 'rule', 'api']"
|
||||||
|
ref="parse"></FnEditor>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane lazy :label="t('fetch.onError')" name="fourth">
|
||||||
|
<FnEditor style="height: 100%;" v-model="form.onError" name="onError"
|
||||||
|
:args="['e']"
|
||||||
|
ref="error"></FnEditor>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<StructEditor v-model="form.data" ref="data"></StructEditor>
|
||||||
|
</template>
|
||||||
|
</el-main>
|
||||||
|
</el-container>
|
||||||
|
</el-main>
|
||||||
|
</el-container>
|
||||||
|
<template #footer>
|
||||||
|
<div>
|
||||||
|
<el-button size="default" @click="visible=false">{{ t('props.cancel') }}</el-button>
|
||||||
|
<el-button type="primary" size="default" @click="submit">{{
|
||||||
|
t('props.ok')
|
||||||
|
}}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import unique from '@form-create/utils/lib/unique';
|
||||||
|
import {deepCopy} from '@form-create/utils/lib/deepextend';
|
||||||
|
import FnEditor from './FnEditor.vue';
|
||||||
|
import StructEditor from './StructEditor.vue';
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
import {designerForm} from '../utils/form';
|
||||||
|
import errorMessage from '../utils/message';
|
||||||
|
import VariableConfig from './computed/VariableConfig.vue';
|
||||||
|
import Warning from './Warning.vue';
|
||||||
|
|
||||||
|
const makeRule = (t) => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
field: 'action',
|
||||||
|
title: t('fetch.action'),
|
||||||
|
value: '',
|
||||||
|
validate: [{required: true, message: t('fetch.actionRequired'), trigger: 'blur'}],
|
||||||
|
inject: true,
|
||||||
|
on: {
|
||||||
|
blur({self}, e) {
|
||||||
|
self._start = e.target.selectionStart;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
type: 'VariableConfig',
|
||||||
|
slot: 'suffix',
|
||||||
|
props: {
|
||||||
|
popover: true,
|
||||||
|
},
|
||||||
|
inject: true,
|
||||||
|
on: {
|
||||||
|
confirm({api}, val) {
|
||||||
|
const rule = api.getRule('action');
|
||||||
|
rule.value = rule.value.substring(0, rule._start) + val + rule.value.substring(rule._start);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'radio',
|
||||||
|
field: 'method',
|
||||||
|
title: t('fetch.method'),
|
||||||
|
value: 'GET',
|
||||||
|
options: [
|
||||||
|
{label: 'GET', value: 'GET'},
|
||||||
|
{label: 'POST', value: 'POST'},
|
||||||
|
],
|
||||||
|
$required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'radio',
|
||||||
|
field: 'dataType',
|
||||||
|
title: t('fetch.dataType'),
|
||||||
|
warning: t('warning.fetchDataType'),
|
||||||
|
value: 'json',
|
||||||
|
options: [
|
||||||
|
{label: 'JSON', value: 'json'},
|
||||||
|
{label: 'FormData', value: 'formData'},
|
||||||
|
],
|
||||||
|
$required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'FetchTable',
|
||||||
|
field: 'headers',
|
||||||
|
title: t('fetch.headers'),
|
||||||
|
value: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'FetchTable',
|
||||||
|
field: 'query',
|
||||||
|
title: t('fetch.query'),
|
||||||
|
warning: t('warning.fetchQuery'),
|
||||||
|
value: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'FetchTable',
|
||||||
|
field: 'data',
|
||||||
|
title: t('fetch.data'),
|
||||||
|
warning: t('warning.fetchData'),
|
||||||
|
value: {},
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'GlobalFetchConfig',
|
||||||
|
emits: ['update:modelValue'],
|
||||||
|
props: {
|
||||||
|
modelValue: Object,
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
Warning,
|
||||||
|
VariableConfig,
|
||||||
|
DragForm: designerForm.$form(),
|
||||||
|
FnEditor,
|
||||||
|
StructEditor
|
||||||
|
},
|
||||||
|
inject: ['designer'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
visible: false,
|
||||||
|
activeIdx: '',
|
||||||
|
list: {},
|
||||||
|
cus: false,
|
||||||
|
cusValue: '',
|
||||||
|
form: {
|
||||||
|
api: {},
|
||||||
|
formData: {},
|
||||||
|
rule: [],
|
||||||
|
options: {
|
||||||
|
form: {
|
||||||
|
labelWidth: '90px',
|
||||||
|
size: 'small'
|
||||||
|
},
|
||||||
|
submitBtn: false,
|
||||||
|
resetBtn: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
t() {
|
||||||
|
return this.designer.setupState.t;
|
||||||
|
},
|
||||||
|
types() {
|
||||||
|
return {fetch: this.t('fetch.remote'), static: this.t('fetch.static')}
|
||||||
|
},
|
||||||
|
dataNum() {
|
||||||
|
return Object.keys(this.modelValue || {}).length;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
visible(v) {
|
||||||
|
if (v) {
|
||||||
|
this.list = deepCopy(this.modelValue || {});
|
||||||
|
this.activeIdx = '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
open() {
|
||||||
|
this.visible = true;
|
||||||
|
},
|
||||||
|
active(key) {
|
||||||
|
if (this.activeIdx !== key) {
|
||||||
|
const formData = this.list[key];
|
||||||
|
this.form.rule = formData.type === 'static' ? [] : makeRule(this.t);
|
||||||
|
this.form.formData = {...formData};
|
||||||
|
this.form.label = formData.label;
|
||||||
|
this.form.type = formData.type;
|
||||||
|
this.form.data = formData.data;
|
||||||
|
this.form.dataType = formData.dataType;
|
||||||
|
this.form.parse = formData.parse || '';
|
||||||
|
this.form.beforeFetch = formData.beforeFetch || '';
|
||||||
|
this.form.onError = formData.onError || '';
|
||||||
|
this.activeIdx = key;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
addCus() {
|
||||||
|
const label = this.cusValue && this.cusValue.trim();
|
||||||
|
if (label) {
|
||||||
|
const key = 'data_' + unique();
|
||||||
|
this.list[key] = {
|
||||||
|
label,
|
||||||
|
type: this.cus,
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
|
this.active(key);
|
||||||
|
this.closeCus();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
closeCus() {
|
||||||
|
this.cus = false;
|
||||||
|
this.cusValue = '';
|
||||||
|
},
|
||||||
|
cusEvent(type) {
|
||||||
|
this.cus = type;
|
||||||
|
},
|
||||||
|
saveData() {
|
||||||
|
if (!this.$refs.data.save()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.list[this.activeIdx].data = this.form.data || [];
|
||||||
|
this.activeIdx = '';
|
||||||
|
},
|
||||||
|
save() {
|
||||||
|
if (this.list[this.activeIdx].type === 'static') {
|
||||||
|
return this.saveData();
|
||||||
|
}
|
||||||
|
this.form.api.validate().then(() => {
|
||||||
|
const formData = {...this.form.formData};
|
||||||
|
if ((this.$refs.parse && !this.$refs.parse.save()) || (this.$refs.beforeFetch && !this.$refs.beforeFetch.save()) || (this.$refs.error && !this.$refs.error.save())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
formData.parse = designerForm.parseFn(this.form.parse);
|
||||||
|
formData.beforeFetch = designerForm.parseFn(this.form.beforeFetch);
|
||||||
|
formData.onError = this.form.onError;
|
||||||
|
formData.label = this.form.label;
|
||||||
|
formData.type = this.form.type;
|
||||||
|
this.list[this.activeIdx] = formData;
|
||||||
|
this.activeIdx = '';
|
||||||
|
}).catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
errorMessage(err[Object.keys(err)[0]][0].message);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
rm(key) {
|
||||||
|
delete this.list[key];
|
||||||
|
if (key === this.activeIdx) {
|
||||||
|
this.activeIdx = '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
this.activeIdx = '';
|
||||||
|
},
|
||||||
|
submit() {
|
||||||
|
if (this.activeIdx) {
|
||||||
|
return errorMessage(this.t('event.saveMsg'));
|
||||||
|
}
|
||||||
|
this.$emit('update:modelValue', {...this.list});
|
||||||
|
this.visible = false;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fd-gfc, ._fd-gfc .el-badge {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gfc .el-button {
|
||||||
|
font-weight: 400;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gfc-dialog .el-tabs__header {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gfc-dialog .form-create {
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gfc-dialog ._fc-tabs {
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gfc-dialog ._fc-tabs .el-tabs__content {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gfc-info {
|
||||||
|
display: flex;
|
||||||
|
font-size: 12px;
|
||||||
|
position: relative;
|
||||||
|
margin-top: 8px;
|
||||||
|
margin-left: 15px;
|
||||||
|
padding: 8px 13px;
|
||||||
|
line-height: 18px;
|
||||||
|
background: rgba(170, 170, 170, 0.1);
|
||||||
|
border-radius: 6px;
|
||||||
|
color: var(--fc-text-color-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
._fd-gfc-con .el-main {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gfc-l, ._fd-gfc-r {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
border: 1px solid var(--fc-line-color-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gfc-head {
|
||||||
|
display: flex;
|
||||||
|
padding: 5px 15px;
|
||||||
|
border-bottom: 1px solid var(--fc-line-color-3);
|
||||||
|
background: var(--fc-bg-color-3);
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gfc-head .el-button.is-link {
|
||||||
|
color: var(--fc-style-color-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gfc-r {
|
||||||
|
border-left: 0 none;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gfc-r ._fd-gfc-head {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gfc-l > .el-main, ._fd-gfc-r > .el-main {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex: 1;
|
||||||
|
flex-basis: auto;
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-width: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gfc-r > .el-main {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gfc-l .el-menu {
|
||||||
|
padding: 0 10px 5px;
|
||||||
|
border-right: 0 none;
|
||||||
|
width: 100%;
|
||||||
|
border-top: 0 none;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gfc-l .el-menu-item.is-active {
|
||||||
|
background: var(--fc-bg-color-3);
|
||||||
|
color: var(--fc-text-color-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gfc-l .el-menu-item {
|
||||||
|
height: auto;
|
||||||
|
line-height: 1em;
|
||||||
|
border: 1px solid var(--fc-line-color-3);
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 0;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gfc-method {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: monospace;
|
||||||
|
color: #702C71;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gfc-method ._fd-label {
|
||||||
|
margin-top: 4px;
|
||||||
|
color: var(--fc-text-color-3);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gfc-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gfc-title .el-input {
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gfc-title .fc-icon {
|
||||||
|
margin-right: 6px;
|
||||||
|
font-size: 18px;
|
||||||
|
color: var(--fc-text-color-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gfc-title .el-input__wrapper {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gfc-title .el-menu-item.is-active i {
|
||||||
|
color: var(--fc-text-color-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gfc-con .CodeMirror {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gfc-con .CodeMirror-wrap pre.CodeMirror-line {
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
111
src/components/GlobalFetchSelect.vue
Normal file
111
src/components/GlobalFetchSelect.vue
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_fd-gfs">
|
||||||
|
<el-select v-model="value" clearable filterable @change="input">
|
||||||
|
<el-option
|
||||||
|
v-for="item in options"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<div class="_fd-gfs-handle">
|
||||||
|
<i @click="refresh" class="fc-icon icon-refresh" :class="{disabled: !value, '_fc-loading': this.loading}"
|
||||||
|
title="reload"/>
|
||||||
|
<div class="_fc-manage-text" @click="openConfig"><i
|
||||||
|
class="fc-icon icon-setting"/></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'GlobalFetchSelect',
|
||||||
|
emits: ['update:modelValue'],
|
||||||
|
props: {
|
||||||
|
modelValue: [Object, String],
|
||||||
|
to: String,
|
||||||
|
},
|
||||||
|
inject: ['designer'],
|
||||||
|
computed: {
|
||||||
|
t() {
|
||||||
|
return this.designer.setupState.t;
|
||||||
|
},
|
||||||
|
options() {
|
||||||
|
return Object.keys((this.designer.setupState.formOptions.globalData || {})).map(k => {
|
||||||
|
return {label: this.designer.setupState.formOptions.globalData[k].label, value: k}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
modelValue() {
|
||||||
|
this.value = (this.modelValue || {}).key || '';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
value: (this.modelValue || {}).key || '',
|
||||||
|
uni: 1,
|
||||||
|
loading: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
refresh() {
|
||||||
|
if (!this.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.uni = this.uni === 1 ? 0 : 1;
|
||||||
|
this.input();
|
||||||
|
this.loading = true;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.loading = false;
|
||||||
|
}, 1000);
|
||||||
|
},
|
||||||
|
openConfig() {
|
||||||
|
this.designer.setupState.openGlobalFetchDialog();
|
||||||
|
},
|
||||||
|
input() {
|
||||||
|
const value = typeof this.modelValue === 'object' ? {...this.modelValue} : {};
|
||||||
|
value.to = this.to || 'options';
|
||||||
|
value.key = this.value;
|
||||||
|
value._uni = this.uni;
|
||||||
|
this.$emit('update:modelValue', value);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fd-gfs {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gfs .el-select {
|
||||||
|
width: 190px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gfs-handle {
|
||||||
|
display: inline-flex;
|
||||||
|
height: 14px;
|
||||||
|
line-height: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gfs-handle .fc-icon {
|
||||||
|
cursor: pointer;
|
||||||
|
margin-left: 4px;
|
||||||
|
color: var(--fc-style-color-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gfs-handle .icon-refresh.disabled {
|
||||||
|
color: #a9abb2;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gfs-handle ._fc-manage-text {
|
||||||
|
border-left: 1px solid var(--fc-line-color-3);
|
||||||
|
padding-left: 4px;
|
||||||
|
}
|
||||||
|
</style>
|
302
src/components/GlobalVariableConfig.vue
Normal file
302
src/components/GlobalVariableConfig.vue
Normal file
@ -0,0 +1,302 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_fd-gvc">
|
||||||
|
<el-badge :value="eventNum" type="warning" :hidden="eventNum < 1">
|
||||||
|
<el-button class="_fd-plain-button" plain @click="open" size="small">{{t('computed.variable.btn')}}</el-button>
|
||||||
|
</el-badge>
|
||||||
|
<el-dialog class="_fd-gvc-dialog _fd-config-dialog" v-model="visible" destroy-on-close
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
append-to-body
|
||||||
|
width="980px">
|
||||||
|
<template #header>
|
||||||
|
{{ t('computed.variable.title') }}
|
||||||
|
<Warning :tooltip="t('warning.globalVariable')"></Warning>
|
||||||
|
</template>
|
||||||
|
<el-container class="_fd-gvc-con" style="height: 600px">
|
||||||
|
<el-aside style="width:255px;">
|
||||||
|
<el-container class="_fd-gvc-l">
|
||||||
|
<el-header class="_fd-gvc-head" height="40px">
|
||||||
|
<el-button link type="primary" size="default" @click="cusEvent">
|
||||||
|
{{ t('computed.variable.create') }}
|
||||||
|
</el-button>
|
||||||
|
</el-header>
|
||||||
|
<el-main>
|
||||||
|
<el-menu>
|
||||||
|
<template v-for="(item, key) in value">
|
||||||
|
<el-menu-item :class="{'is-active':key === activeIdx}">
|
||||||
|
<div class="_fd-gvc-title" @click.stop="active(key)">
|
||||||
|
<div class="_fd-gvc-method">
|
||||||
|
<span>{{ key }}</span>
|
||||||
|
<span class="_fd-label" v-if="item.label">{{ item.label }}</span>
|
||||||
|
</div>
|
||||||
|
<i class="fc-icon icon-delete" v-if="item.deletable !== false" @click.stop="rm(key)"></i>
|
||||||
|
</div>
|
||||||
|
</el-menu-item>
|
||||||
|
</template>
|
||||||
|
<el-menu-item v-if="cus" style="padding-left: 10px;">
|
||||||
|
<div class="_fd-gvc-title" @click.stop>
|
||||||
|
<el-input type="text" v-model="cusValue" size="default"
|
||||||
|
@keydown.enter="addCus"
|
||||||
|
:placeholder="t('computed.variable.placeholder')">
|
||||||
|
</el-input>
|
||||||
|
<div>
|
||||||
|
<i class="fc-icon icon-add" @click.stop="addCus"></i>
|
||||||
|
<i class="fc-icon icon-delete" @click.stop="closeCus"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-menu-item>
|
||||||
|
</el-menu>
|
||||||
|
</el-main>
|
||||||
|
</el-container>
|
||||||
|
</el-aside>
|
||||||
|
<el-main>
|
||||||
|
<el-container class="_fd-gvc-r">
|
||||||
|
<el-header class="_fd-gvc-head" height="40px" v-if="activeIdx">
|
||||||
|
<el-button size="small" @click="close">{{ t('props.cancel') }}</el-button>
|
||||||
|
<el-button size="small" type="primary" @click="save">{{
|
||||||
|
t('props.save')
|
||||||
|
}}
|
||||||
|
</el-button>
|
||||||
|
</el-header>
|
||||||
|
<el-main v-if="activeIdx" :key="activeIdx">
|
||||||
|
<FnEditor ref="editor" v-model="handle" name="handle" :args="['get', 'api']"></FnEditor>
|
||||||
|
</el-main>
|
||||||
|
</el-container>
|
||||||
|
</el-main>
|
||||||
|
</el-container>
|
||||||
|
<template #footer>
|
||||||
|
<div>
|
||||||
|
<el-button size="default" @click="visible=false">{{ t('props.cancel') }}</el-button>
|
||||||
|
<el-button type="primary" size="default" @click="submit">{{
|
||||||
|
t('props.ok')
|
||||||
|
}}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import unique from '@form-create/utils/lib/unique';
|
||||||
|
import {deepCopy} from '@form-create/utils/lib/deepextend';
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
import FnEditor from './FnEditor.vue';
|
||||||
|
import Warning from './Warning.vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'GlobalVariableConfig',
|
||||||
|
emits: ['update:modelValue'],
|
||||||
|
props: {
|
||||||
|
modelValue: Object,
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
Warning,
|
||||||
|
FnEditor,
|
||||||
|
},
|
||||||
|
inject: ['designer'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
visible: false,
|
||||||
|
activeIdx: '',
|
||||||
|
value: {},
|
||||||
|
cus: false,
|
||||||
|
cusValue: '',
|
||||||
|
handle: '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
t() {
|
||||||
|
return this.designer.setupState.t;
|
||||||
|
},
|
||||||
|
eventNum() {
|
||||||
|
return Object.keys(this.modelValue || {}).length;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
visible(v) {
|
||||||
|
if (v) {
|
||||||
|
this.activeIdx = '';
|
||||||
|
this.value = deepCopy(this.modelValue || {});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
open() {
|
||||||
|
this.visible = true;
|
||||||
|
},
|
||||||
|
active(idx) {
|
||||||
|
if (this.activeIdx !== idx) {
|
||||||
|
this.handle = this.value[idx].handle || '';
|
||||||
|
this.activeIdx = idx;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
addCus() {
|
||||||
|
const label = this.cusValue && this.cusValue.trim();
|
||||||
|
if (label) {
|
||||||
|
const key = 'var_' + unique();
|
||||||
|
this.value[key] = {
|
||||||
|
label,
|
||||||
|
handle: '',
|
||||||
|
};
|
||||||
|
this.active(key);
|
||||||
|
this.closeCus();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
closeCus() {
|
||||||
|
this.cus = false;
|
||||||
|
this.cusValue = '';
|
||||||
|
},
|
||||||
|
cusEvent() {
|
||||||
|
this.cus = true;
|
||||||
|
},
|
||||||
|
save() {
|
||||||
|
if (!this.$refs.editor.save()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.value[this.activeIdx].handle = this.handle;
|
||||||
|
this.activeIdx = '';
|
||||||
|
},
|
||||||
|
rm(key) {
|
||||||
|
delete this.value[key];
|
||||||
|
if (key === this.activeIdx) {
|
||||||
|
this.activeIdx = '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
this.activeIdx = '';
|
||||||
|
},
|
||||||
|
submit() {
|
||||||
|
if (this.activeIdx && !this.save()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.$emit('update:modelValue', {...this.value});
|
||||||
|
this.visible = false;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fd-gvc, ._fd-gvc .el-badge {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gvc .el-button {
|
||||||
|
font-weight: 400;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gvc-con .el-main {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gvc-l, ._fd-gvc-r {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
border: 1px solid var(--fc-line-color-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gvc-head {
|
||||||
|
display: flex;
|
||||||
|
padding: 5px 15px;
|
||||||
|
border-bottom: 1px solid var(--fc-line-color-3);
|
||||||
|
background: var(--fc-bg-color-3);
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gvc-head .el-button.is-link {
|
||||||
|
color: var(--fc-style-color-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gvc-r {
|
||||||
|
border-left: 0 none;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gvc-r ._fd-gvc-head {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gvc-l > .el-main, ._fd-gvc-r > .el-main {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex: 1;
|
||||||
|
flex-basis: auto;
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-width: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gvc-r > .el-main {
|
||||||
|
padding: 0;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gvc-l .el-menu {
|
||||||
|
padding: 0 10px 5px;
|
||||||
|
border-right: 0 none;
|
||||||
|
width: 100%;
|
||||||
|
border-top: 0 none;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gvc-l .el-menu-item.is-active {
|
||||||
|
background: var(--fc-bg-color-3);
|
||||||
|
color: var(--fc-text-color-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gvc-l .el-menu-item {
|
||||||
|
height: auto;
|
||||||
|
line-height: 1em;
|
||||||
|
border: 1px solid var(--fc-line-color-3);
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 0;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gvc-method {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
width: 175px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: monospace;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
color: #923B76;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gvc-method ._fd-label {
|
||||||
|
margin-top: 4px;
|
||||||
|
color: var(--fc-text-color-3);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gvc-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gvc-title .el-input {
|
||||||
|
width: 160px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gvc-title .fc-icon {
|
||||||
|
margin-right: 6px;
|
||||||
|
font-size: 18px;
|
||||||
|
color: var(--fc-text-color-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gvc-title .el-input__wrapper {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-gvc-con .el-menu-item.is-active i {
|
||||||
|
color: var(--fc-text-color-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
66
src/components/HideConfig.vue
Normal file
66
src/components/HideConfig.vue
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_fd-hide-config" :class="{disabled: !!disabled, active: modelValue === activeValue}" @click="onInput">
|
||||||
|
<template v-if="modelValue === activeValue">
|
||||||
|
<i class="fc-icon icon-eye"></i> {{ t('props.show') }}
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<i class="fc-icon icon-eye-close"></i> {{ t('props.hide') }}
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'HideSwitch',
|
||||||
|
props: {
|
||||||
|
modelValue: [String, Boolean, Number],
|
||||||
|
activeValue: {
|
||||||
|
type: [String, Boolean, Number],
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
inactiveValue: {
|
||||||
|
type: [String, Boolean, Number],
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
disabled: Boolean,
|
||||||
|
},
|
||||||
|
events: ['update:modelValue'],
|
||||||
|
inject: ['designer'],
|
||||||
|
computed: {
|
||||||
|
t() {
|
||||||
|
return this.designer.setupState.t;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onInput() {
|
||||||
|
if (!this.disabled) {
|
||||||
|
this.$emit('update:modelValue', this.modelValue !== this.activeValue ? this.activeValue : this.inactiveValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fd-hide-config {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--fc-text-color-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-hide-config .fc-icon {
|
||||||
|
margin-right: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-hide-config.active {
|
||||||
|
color: var(--fc-style-color-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-hide-config.disabled {
|
||||||
|
color: var(--fc-text-color-3);
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
124
src/components/HtmlEditor.vue
Normal file
124
src/components/HtmlEditor.vue
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_fd-html-editor">
|
||||||
|
<el-button class="_fd-plain-button" plain @click="visible=true">{{ title || t('struct.title') }}</el-button>
|
||||||
|
<el-dialog class="_fd-html-editor-con" :title="title || t('struct.title')" v-model="visible"
|
||||||
|
:close-on-click-modal="false" append-to-body width="800px">
|
||||||
|
<div ref="editor" v-if="visible"></div>
|
||||||
|
<template #footer>
|
||||||
|
<div>
|
||||||
|
<el-button @click="visible = false" size="default">{{ t('props.cancel') }}</el-button>
|
||||||
|
<el-button type="primary" @click="onOk" size="default">{{ t('props.ok') }}</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import 'codemirror/lib/codemirror.css';
|
||||||
|
import CodeMirror from 'codemirror/lib/codemirror';
|
||||||
|
import {defineComponent, markRaw} from 'vue';
|
||||||
|
import errorMessage from '../utils/message';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'HtmlEditor',
|
||||||
|
emits: ['update:modelValue'],
|
||||||
|
props: {
|
||||||
|
modelValue: String,
|
||||||
|
title: String,
|
||||||
|
text: Boolean,
|
||||||
|
defaultValue: {
|
||||||
|
require: false
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inject: ['designer'],
|
||||||
|
computed: {
|
||||||
|
t() {
|
||||||
|
return this.designer.setupState.t;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
editor: null,
|
||||||
|
visible: false,
|
||||||
|
oldVal: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
modelValue() {
|
||||||
|
this.load();
|
||||||
|
},
|
||||||
|
visible(n) {
|
||||||
|
if (n) {
|
||||||
|
this.load();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
validateXML(xmlString) {
|
||||||
|
const parser = new DOMParser();
|
||||||
|
const xmlDoc = parser.parseFromString(xmlString, 'application/xml');
|
||||||
|
const parseErrors = xmlDoc.getElementsByTagName('parsererror');
|
||||||
|
if (parseErrors.length > 0) {
|
||||||
|
return parseErrors[0].innerText.split('\n')[0] ?? '';
|
||||||
|
} else {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
load() {
|
||||||
|
this.oldVal = this.modelValue;
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.editor = markRaw(CodeMirror(this.$refs.editor, {
|
||||||
|
lineNumbers: true,
|
||||||
|
mode: 'xml',
|
||||||
|
lint: true,
|
||||||
|
line: true,
|
||||||
|
tabSize: 2,
|
||||||
|
lineWrapping: true,
|
||||||
|
value: this.modelValue || ''
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onOk() {
|
||||||
|
const str = this.editor.getValue();
|
||||||
|
if (!this.text && this.validateXML(str)) {
|
||||||
|
errorMessage(this.t('struct.errorMsg'));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.visible = false;
|
||||||
|
if (str !== this.oldVal) {
|
||||||
|
this.$emit('update:modelValue', str);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fd-html-editor {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-html-editor > .el-button {
|
||||||
|
font-weight: 400;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-html-editor-con .CodeMirror {
|
||||||
|
height: 450px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-html-editor-con .CodeMirror-line {
|
||||||
|
line-height: 16px !important;
|
||||||
|
font-size: 13px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-html-editor-con .CodeMirror-lint-tooltip {
|
||||||
|
z-index: 2021 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-html-editor-con .el-dialog__body {
|
||||||
|
padding: 0px 20px;
|
||||||
|
}
|
||||||
|
</style>
|
39
src/components/Id.vue
Normal file
39
src/components/Id.vue
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<template>
|
||||||
|
<el-input :modelValue="designer ? ('' + (prefix || '') + preview) : modelValue" readonly disabled></el-input>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
import SnowflakeId from 'snowflake-id';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'FcId',
|
||||||
|
props: ['modelValue', 'prefix'],
|
||||||
|
emits: ['update:modelValue'],
|
||||||
|
inject: {
|
||||||
|
designer: {
|
||||||
|
default: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
preview: '7379787000000000'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
modelValue: {
|
||||||
|
handler: function (val) {
|
||||||
|
if (!val) {
|
||||||
|
const snowflake = new SnowflakeId({
|
||||||
|
mid: 42,
|
||||||
|
offset: (2025 - 1970) * 31536000 * 1000
|
||||||
|
});
|
||||||
|
this.$emit('update:modelValue', '' + (this.prefix || '') + snowflake.generate());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
49
src/components/InlineForm.vue
Normal file
49
src/components/InlineForm.vue
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_fc-line-form">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'fcInlineForm',
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fc-line-form {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: wrap;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-create-m ._fc-line-form {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-create ._fc-line-form .el-col-24, .form-create ._fc-line-form ._fd-drag-tool, .form-create ._fc-line-form ._fd-drag-item, .form-create ._fc-line-form ._fc-line-form {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
max-width: 100%;
|
||||||
|
flex: initial;
|
||||||
|
width: auto !important;
|
||||||
|
flex: unset !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-m-con .form-create ._fc-line-form > .el-col-24 {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-line-form .el-form-item {
|
||||||
|
display: inline-flex;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-line-form .el-select, ._fc-line-form .el-slider {
|
||||||
|
width: 220px;
|
||||||
|
}
|
||||||
|
</style>
|
94
src/components/JsonPreview.vue
Normal file
94
src/components/JsonPreview.vue
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
<template>
|
||||||
|
<el-container class="_fc-json-preview">
|
||||||
|
<el-header height="40px" class="_fc-l-tabs">
|
||||||
|
<div class="_fc-l-tab"
|
||||||
|
:class="{active: active==='rule'}"
|
||||||
|
@click="active='rule'"> {{ t('designer.json') }}
|
||||||
|
</div>
|
||||||
|
<div class="_fc-l-tab"
|
||||||
|
:class="{active: active==='options'}"
|
||||||
|
@click="active='options'"> {{ t('designer.form') }}
|
||||||
|
</div>
|
||||||
|
</el-header>
|
||||||
|
<el-main style="padding: 8px;">
|
||||||
|
<StructEditor ref="editor" v-model="value" @blur="handleBlur" @focus="handleFocus" format
|
||||||
|
style="height:100%;"></StructEditor>
|
||||||
|
</el-main>
|
||||||
|
</el-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
import StructEditor from './StructEditor.vue';
|
||||||
|
import {designerForm} from '../utils/form';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'JsonPreview',
|
||||||
|
components: {StructEditor},
|
||||||
|
inject: ['designer'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
active: 'rule',
|
||||||
|
value: this.designer.setupState.getRule(),
|
||||||
|
oldValue: '',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
active() {
|
||||||
|
this.updateValue();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
change() {
|
||||||
|
if (this.active === 'rule') {
|
||||||
|
return this.designer.setupState.children;
|
||||||
|
} else {
|
||||||
|
return this.designer.setupState.formOptions;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
t() {
|
||||||
|
return this.designer.setupState.t;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateValue() {
|
||||||
|
if (this.active === 'rule') {
|
||||||
|
this.value = this.designer.setupState.getRule();
|
||||||
|
} else {
|
||||||
|
this.value = this.designer.setupState.getOptions();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleFocus() {
|
||||||
|
this.oldValue = designerForm.toJson(this.value);
|
||||||
|
},
|
||||||
|
handleBlur() {
|
||||||
|
let str;
|
||||||
|
if (this.$refs.editor.save() && (str = designerForm.toJson(this.value)) !== this.oldValue) {
|
||||||
|
if (this.active === 'rule') {
|
||||||
|
this.designer.setupState.setRule(str);
|
||||||
|
} else {
|
||||||
|
this.designer.setupState.setOptions(this.value || {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$watch(() => this.change, () => {
|
||||||
|
this.updateValue();
|
||||||
|
}, {deep: true});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fc-json-preview {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
color: var(--fc-text-color-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-json-preview .CodeMirror {
|
||||||
|
height: 100%;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
</style>
|
172
src/components/PageInput.vue
Normal file
172
src/components/PageInput.vue
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_fd-page-input">
|
||||||
|
<template v-for="(page, index) in pageData" :key="page.main ? page.main.name : ''">
|
||||||
|
<div class="_fd-page-item" :class="{active: page === activePage}" @click="$emit('change', index)">
|
||||||
|
<div>
|
||||||
|
<div class="_fd-page-label">
|
||||||
|
<span>{{ getPageLabel(page) }}</span>
|
||||||
|
<i class="fc-icon icon-yes" v-if="page === activePage"></i>
|
||||||
|
</div>
|
||||||
|
<div class="_fd-page-id" v-if="page.main">
|
||||||
|
ID:{{ page.main.name }} <i @click.stop="copy(page.main.name)" class="fc-icon icon-group"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="_fd-page-btns" v-if="!page.default">
|
||||||
|
<div class="_fd-page-copy" @click.stop="$emit('copy', index)">
|
||||||
|
<i class="fc-icon icon-copy"></i>
|
||||||
|
</div>
|
||||||
|
<div class="_fd-page-del" @click.stop="$emit('delete', index)">
|
||||||
|
<i class="fc-icon icon-delete"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-dropdown size="default" trigger="click">
|
||||||
|
<el-button link type="primary">
|
||||||
|
{{ t('designer.addPage') }}<i class="fc-icon icon-down"></i>
|
||||||
|
</el-button>
|
||||||
|
<template #dropdown>
|
||||||
|
<el-dropdown-menu>
|
||||||
|
<el-dropdown-item v-for="item in containerList" :key="item.name"
|
||||||
|
@click="$emit('add', item.name)">
|
||||||
|
{{ getPageName(item) }}
|
||||||
|
</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</template>
|
||||||
|
</el-dropdown>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
import {copyTextToClipboard, deepGet} from '../utils';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'PageInput',
|
||||||
|
inject: ['designer'],
|
||||||
|
emits: ['add', 'delete', 'change'],
|
||||||
|
computed: {
|
||||||
|
activePage() {
|
||||||
|
return this.designer.setupState.activePage;
|
||||||
|
},
|
||||||
|
pageData() {
|
||||||
|
return this.designer.setupState.pageData;
|
||||||
|
},
|
||||||
|
t() {
|
||||||
|
return this.designer.setupState.t;
|
||||||
|
},
|
||||||
|
containerList() {
|
||||||
|
const dragRuleList = this.designer.setupState.dragRuleList;
|
||||||
|
return Object.keys(dragRuleList).map(k => {
|
||||||
|
if (dragRuleList[k].container) {
|
||||||
|
return dragRuleList[k];
|
||||||
|
}
|
||||||
|
}).filter(item => !!item);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
copy(name) {
|
||||||
|
copyTextToClipboard(name);
|
||||||
|
},
|
||||||
|
getPageName(item) {
|
||||||
|
return this.t('com.' + item.name + '.name') || item.label
|
||||||
|
},
|
||||||
|
getPageLabel(page) {
|
||||||
|
return page.default ? this.t('designer.main') : (deepGet(page.main, page.config.labelField, '') || this.getPageName(page.main._menu));
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fd-page-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
padding: 12px 0;
|
||||||
|
margin: 0 12px;
|
||||||
|
height: 30px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
border-bottom: 1px solid var(--fc-line-color-3);
|
||||||
|
box-sizing: content-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-page-item.active ._fd-page-label {
|
||||||
|
color: var(--fc-style-color-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-page-btns{
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-page-label {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--fc-text-color-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-page-label .fc-icon {
|
||||||
|
margin-left: 5px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-page-id {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
color: var(--fc-text-color-3);
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-page-id .fc-icon {
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-page-id .fc-icon:hover {
|
||||||
|
color: var(--fc-style-color-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-page-input .el-button {
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-left: 12px;
|
||||||
|
margin-top: 12px;
|
||||||
|
color: var(--fc-style-color-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-page-input .el-button .fc-icon {
|
||||||
|
margin-left: 5px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-page-del {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
margin-left: 4px;
|
||||||
|
border-radius: 25px;
|
||||||
|
background-color: var(--fc-style-bg-color-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-page-del .fc-icon {
|
||||||
|
color: var(--fc-style-color-3);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-page-copy {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 25px;
|
||||||
|
background-color: var(--fc-style-bg-color-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-page-copy .fc-icon {
|
||||||
|
color: var(--fc-style-color-1);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
</style>
|
398
src/components/PrintForm.vue
Normal file
398
src/components/PrintForm.vue
Normal file
@ -0,0 +1,398 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_fd-print">
|
||||||
|
<el-tooltip
|
||||||
|
effect="dark"
|
||||||
|
:content="t('designer.print.title')"
|
||||||
|
placement="top"
|
||||||
|
:hide-after="0"
|
||||||
|
>
|
||||||
|
<i class="fc-icon icon-print" @click="open"></i>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-dialog class="_fd-print-dialog _fd-config-dialog" v-model="visible" destroy-on-close
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
:title="t('designer.print.title')"
|
||||||
|
append-to-body
|
||||||
|
width="1080px">
|
||||||
|
<el-container class="_fd-print-con" style="height: 600px">
|
||||||
|
<el-aside style="width:255px;">
|
||||||
|
<el-container class="_fd-print-l">
|
||||||
|
<el-header class="_fd-print-head" height="40px">
|
||||||
|
{{ t('designer.print.config') }}
|
||||||
|
</el-header>
|
||||||
|
<el-main>
|
||||||
|
<el-form label-position="top" size="small">
|
||||||
|
<el-form-item :label="t('props.mode')">
|
||||||
|
<el-radio-group v-model="formData.type">
|
||||||
|
<el-radio-button value="form">{{ t('form.formMode') }}</el-radio-button>
|
||||||
|
<el-radio-button value="read">{{ t('form.previewMode') }}</el-radio-button>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="t('props.style')">
|
||||||
|
<el-radio-group v-model="formData.style">
|
||||||
|
<el-radio-button value="default">{{
|
||||||
|
t('designer.print.defaultStyle')
|
||||||
|
}}
|
||||||
|
</el-radio-button>
|
||||||
|
<el-radio-button value="word">{{
|
||||||
|
t('designer.print.wordStyle')
|
||||||
|
}}
|
||||||
|
</el-radio-button>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="t('style.width')">
|
||||||
|
<el-input-number :min="300" controls-position="right"
|
||||||
|
v-model="formData.width"></el-input-number>
|
||||||
|
</el-form-item>
|
||||||
|
<template v-for="item in padding" :key="item">
|
||||||
|
<el-form-item :label="t('designer.print.' + item)">
|
||||||
|
<el-input-number :min="0" controls-position="right"
|
||||||
|
v-model="formData[item]"></el-input-number>
|
||||||
|
</el-form-item>
|
||||||
|
</template>
|
||||||
|
</el-form>
|
||||||
|
</el-main>
|
||||||
|
</el-container>
|
||||||
|
</el-aside>
|
||||||
|
<el-main>
|
||||||
|
<el-container class="_fd-print-r">
|
||||||
|
<el-main>
|
||||||
|
<ViewForm class="_fd-print-form" :class="{'_fd-print-form-word': formData.style === 'word'}"
|
||||||
|
ref="form" :rule="rule" :option="options"
|
||||||
|
v-if="visible"
|
||||||
|
:style="{width: formData.width > 0 ? (formData.width + 'px') : '100%'}">
|
||||||
|
<template v-for="(_, name) in $slots" #[name]="scope">
|
||||||
|
<slot :name="name" v-bind="scope ?? {}"/>
|
||||||
|
</template>
|
||||||
|
</ViewForm>
|
||||||
|
</el-main>
|
||||||
|
</el-container>
|
||||||
|
</el-main>
|
||||||
|
</el-container>
|
||||||
|
<template #footer>
|
||||||
|
<div>
|
||||||
|
<el-button size="default" @click="visible=false">{{ t('props.cancel') }}</el-button>
|
||||||
|
<el-button size="default" @click="print(true)">{{
|
||||||
|
t('designer.print.export')
|
||||||
|
}}</el-button>
|
||||||
|
<el-button type="primary" size="default" @click="print(false)" :loading="printing">{{ t('props.print') }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent, markRaw} from 'vue';
|
||||||
|
import Warning from './Warning.vue';
|
||||||
|
import viewForm from '../utils/form';
|
||||||
|
import loadjs from '../utils/loadjs/loadjs';
|
||||||
|
import SizeInput from './style/SizeInput.vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'PrintForm',
|
||||||
|
components: {
|
||||||
|
SizeInput,
|
||||||
|
Warning,
|
||||||
|
ViewForm: viewForm.$form(),
|
||||||
|
},
|
||||||
|
inject: ['designer'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
visible: false,
|
||||||
|
printing: false,
|
||||||
|
frame: null,
|
||||||
|
rule: [],
|
||||||
|
options: {},
|
||||||
|
padding: ['top', 'bottom', 'left', 'right'],
|
||||||
|
formData: {
|
||||||
|
type: 'form',
|
||||||
|
style: 'default',
|
||||||
|
left: 20,
|
||||||
|
right: 20,
|
||||||
|
top: 20,
|
||||||
|
bottom: 20,
|
||||||
|
width: 780,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
t() {
|
||||||
|
return this.designer.setupState.t;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
visible(v) {
|
||||||
|
if (v) {
|
||||||
|
this.rule = viewForm.parseJson(this.designer.setupState.getPreviewRule());
|
||||||
|
this.options = viewForm.parseJson(this.designer.setupState.getOptionsJson());
|
||||||
|
this.options.submitBtn = false;
|
||||||
|
this.options.resetBtn = false;
|
||||||
|
} else {
|
||||||
|
this.printing = false;
|
||||||
|
if (this.frame) {
|
||||||
|
document.body.removeChild(this.frame);
|
||||||
|
this.frame = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'formData.type': function (n) {
|
||||||
|
this.options.preview = n === 'read';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
open() {
|
||||||
|
this.visible = true;
|
||||||
|
},
|
||||||
|
disableImageSmoothing(ctx) {
|
||||||
|
ctx.imageSmoothingEnabled = false;
|
||||||
|
ctx.mozImageSmoothingEnabled = false;
|
||||||
|
ctx.webkitImageSmoothingEnabled = false;
|
||||||
|
ctx.msImageSmoothingEnabled = false;
|
||||||
|
},
|
||||||
|
print(flag) {
|
||||||
|
this.printing = true;
|
||||||
|
loadjs.ready(['html2canvas', 'jspdf'], () => {
|
||||||
|
window.html2canvas(this.$refs.form.$el, {
|
||||||
|
allowTaint: true,
|
||||||
|
useCORS: true,
|
||||||
|
}).then((canvas) => {
|
||||||
|
const pdf = new window.jspdf.jsPDF({
|
||||||
|
orientation: 'p',
|
||||||
|
unit: 'pt',
|
||||||
|
format: 'a4',
|
||||||
|
});
|
||||||
|
|
||||||
|
this.disableImageSmoothing(canvas.getContext('2d'));
|
||||||
|
|
||||||
|
const {
|
||||||
|
left: marginLeft,
|
||||||
|
right: marginRight,
|
||||||
|
top: marginTop,
|
||||||
|
bottom: marginBottom,
|
||||||
|
} = this.formData;
|
||||||
|
|
||||||
|
const pageWidth = pdf.internal.pageSize.getWidth();
|
||||||
|
const pageHeight = pdf.internal.pageSize.getHeight();
|
||||||
|
const contentWidth = pageWidth - marginLeft - marginRight;
|
||||||
|
const contentHeight = pageHeight - marginTop - marginBottom;
|
||||||
|
|
||||||
|
const scaledHeight = (canvas.height * contentWidth) / canvas.width;
|
||||||
|
|
||||||
|
if (scaledHeight <= contentHeight) {
|
||||||
|
pdf.addImage(
|
||||||
|
canvas.toDataURL('image/jpeg'),
|
||||||
|
'JPEG',
|
||||||
|
marginLeft,
|
||||||
|
marginTop,
|
||||||
|
contentWidth,
|
||||||
|
scaledHeight
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
let remainingHeight = scaledHeight;
|
||||||
|
let page = 0;
|
||||||
|
const clipHeight = canvas.width * contentHeight / contentWidth;
|
||||||
|
|
||||||
|
while (remainingHeight > 0) {
|
||||||
|
const tempCanvas = document.createElement('canvas');
|
||||||
|
const tempCtx = tempCanvas.getContext('2d');
|
||||||
|
this.disableImageSmoothing(tempCtx);
|
||||||
|
|
||||||
|
const offsetY = page * clipHeight;
|
||||||
|
const actualClipHeight = Math.min(clipHeight, canvas.height - offsetY);
|
||||||
|
|
||||||
|
tempCanvas.width = canvas.width;
|
||||||
|
tempCanvas.height = actualClipHeight;
|
||||||
|
|
||||||
|
tempCtx.drawImage(
|
||||||
|
canvas,
|
||||||
|
0, offsetY, canvas.width, actualClipHeight,
|
||||||
|
0, 0, canvas.width, actualClipHeight
|
||||||
|
);
|
||||||
|
|
||||||
|
const imageHeight = (actualClipHeight / canvas.height) * scaledHeight;
|
||||||
|
|
||||||
|
pdf.addImage(
|
||||||
|
tempCanvas.toDataURL('image/jpeg'),
|
||||||
|
'JPEG',
|
||||||
|
marginLeft,
|
||||||
|
marginTop,
|
||||||
|
contentWidth,
|
||||||
|
imageHeight
|
||||||
|
);
|
||||||
|
|
||||||
|
remainingHeight -= contentHeight;
|
||||||
|
if (remainingHeight > 0) {
|
||||||
|
pdf.addPage();
|
||||||
|
page++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (flag) {
|
||||||
|
this.printing = false;
|
||||||
|
window.open(URL.createObjectURL(pdf.output('blob')));
|
||||||
|
} else {
|
||||||
|
this.printPdf(pdf);
|
||||||
|
}
|
||||||
|
}).catch((e) => {
|
||||||
|
this.printing = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
printPdf(pdf) {
|
||||||
|
if (!this.frame) {
|
||||||
|
const frame = markRaw(document.createElement('iframe'));
|
||||||
|
frame.style.width = '0';
|
||||||
|
frame.style.position = 'absolute';
|
||||||
|
frame.style.height = '0';
|
||||||
|
frame.style.border = 'none';
|
||||||
|
frame.onload = function () {
|
||||||
|
setTimeout(() => {
|
||||||
|
frame.contentWindow.print();
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
document.body.appendChild(frame);
|
||||||
|
this.frame = frame;
|
||||||
|
}
|
||||||
|
this.frame.src = URL.createObjectURL(pdf.output('blob'));
|
||||||
|
this.printing = false;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
if (window.html2canvas) {
|
||||||
|
loadjs.done('html2canvas');
|
||||||
|
} else if (!loadjs.isDefined('html2canvas')) {
|
||||||
|
loadjs.loadNpm('html2canvas@1.4.1/dist/html2canvas.min.js', 'html2canvas');
|
||||||
|
}
|
||||||
|
if (window.jspdf) {
|
||||||
|
loadjs.done('jspdf');
|
||||||
|
} else if (!loadjs.isDefined('jspdf')) {
|
||||||
|
loadjs.loadNpm('jspdf@3.0.1/dist/jspdf.umd.js', 'jspdf');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fd-print .el-button {
|
||||||
|
font-weight: 400;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-print-con .el-main {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-print-l, ._fd-print-r {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
border: 1px solid var(--fc-line-color-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-print-head {
|
||||||
|
display: flex;
|
||||||
|
padding: 5px 15px;
|
||||||
|
border-bottom: 1px solid var(--fc-line-color-3);
|
||||||
|
background: var(--fc-bg-color-3);
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-print-head .el-button.is-link {
|
||||||
|
color: var(--fc-style-color-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-print-r {
|
||||||
|
border-left: 0 none;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-print-r ._fd-print-head {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-print-l > .el-main, ._fd-print-r > .el-main {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
flex-basis: auto;
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-width: 0;
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-print-l .el-form .el-radio-group, ._fd-print-l .el-form .el-radio-button__inner {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-print-l .el-form .el-radio-button {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-print-r > .el-main {
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 20px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-print-form {
|
||||||
|
padding: 2px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-print-form .el-input__wrapper, ._fd-print-form .el-textarea__inner, ._fd-print-form .el-select__wrapper {
|
||||||
|
box-shadow: none !important;
|
||||||
|
border: 1px solid var(--el-input-border-color, var(--el-border-color));
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-print-form .el-select__placeholder {
|
||||||
|
position: unset !important;
|
||||||
|
top: unset !important;
|
||||||
|
transform: unset !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-print-form .is-disabled .el-input__wrapper {
|
||||||
|
background-color: unset !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-print-form .is-disabled .el-input__inner {
|
||||||
|
color: unset !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-print-form-word .el-input__wrapper, ._fd-print-form-word .el-textarea__inner, ._fd-print-form-word .el-select__wrapper {
|
||||||
|
border: none !important;
|
||||||
|
border-bottom: 1px solid var(--el-input-border-color, var(--el-border-color)) !important;
|
||||||
|
border-color: inherit !important;
|
||||||
|
border-radius: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-print-form-word .el-input-number__decrease, ._fd-print-form-word .el-input-number__increase {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-print-form-word ._fc-read-view {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 1.5em;
|
||||||
|
padding: 0 4px;
|
||||||
|
line-height: 1.5em;
|
||||||
|
border-bottom: 1px solid var(--el-input-border-color, var(--el-border-color)) !important;
|
||||||
|
border-color: inherit !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-print-page-line {
|
||||||
|
position: absolute;
|
||||||
|
border-bottom: 1px dashed var(--fc-line-color-3);
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 1px;
|
||||||
|
color: var(--fc-text-color-3);
|
||||||
|
padding-left: 4px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 2em;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
62
src/components/PromptInput.vue
Normal file
62
src/components/PromptInput.vue
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
<template>
|
||||||
|
<el-input :size="size" v-model="value" @blur="onInput" clearable class="_fd-list-input">
|
||||||
|
<template #append>
|
||||||
|
<el-dropdown size="default" trigger="click" :popper-class="popperClass">
|
||||||
|
<i class="fc-icon icon-setting"></i>
|
||||||
|
<template #dropdown>
|
||||||
|
<el-dropdown-menu>
|
||||||
|
<el-dropdown-item v-for="item in options" :key="item.value" @click="setValue(item.value)">
|
||||||
|
{{ item.label }}
|
||||||
|
</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</template>
|
||||||
|
</el-dropdown>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'PromptInput',
|
||||||
|
emits: ['update:modelValue', 'change'],
|
||||||
|
props: {
|
||||||
|
size: String,
|
||||||
|
modelValue: String,
|
||||||
|
popperClass: String,
|
||||||
|
options: Array,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
value: this.modelValue || '',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
setValue(val) {
|
||||||
|
this.value = val;
|
||||||
|
this.onInput();
|
||||||
|
},
|
||||||
|
onInput() {
|
||||||
|
this.$emit('update:modelValue', this.value);
|
||||||
|
this.$emit('change', this.value);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fd-list-input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-list-input .el-input-group__append {
|
||||||
|
padding: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-list-input .fc-icon {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
80
src/components/PropsInput.vue
Normal file
80
src/components/PropsInput.vue
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
<template>
|
||||||
|
<Struct class="_fd-props-input" :modelValue="props" @update:modelValue="onInput" :title="t('designer.customProps')">
|
||||||
|
<el-tooltip
|
||||||
|
effect="dark"
|
||||||
|
:content="t('designer.customProps')"
|
||||||
|
placement="top"
|
||||||
|
:hide-after="0"
|
||||||
|
>
|
||||||
|
<i class="fc-icon icon-edit"></i>
|
||||||
|
</el-tooltip>
|
||||||
|
</Struct>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
import Struct from './Struct.vue';
|
||||||
|
import extend from '@form-create/utils/lib/extend';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'PropsInput',
|
||||||
|
components: {Struct},
|
||||||
|
inject: ['designer'],
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
t() {
|
||||||
|
return this.designer.setupState.t;
|
||||||
|
},
|
||||||
|
activeRule() {
|
||||||
|
return this.designer.setupState.activeRule;
|
||||||
|
},
|
||||||
|
props() {
|
||||||
|
const propsKeys = this.activeRule._fc_store?.props_keys || [];
|
||||||
|
const props = {};
|
||||||
|
propsKeys.forEach(k => {
|
||||||
|
if (this.activeRule.props && this.activeRule.props[k] != null) {
|
||||||
|
props[k] = this.activeRule.props[k];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return props;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onInput(props) {
|
||||||
|
if (!this.activeRule.props) {
|
||||||
|
this.activeRule.props = {};
|
||||||
|
}
|
||||||
|
if (!this.activeRule._fc_store) {
|
||||||
|
this.activeRule._fc_store = {};
|
||||||
|
}
|
||||||
|
Object.keys(this.props).forEach(k => {
|
||||||
|
if ((props || {})[k] == null) {
|
||||||
|
delete this.activeRule.props[k];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
extend(this.activeRule.props, props || {});
|
||||||
|
const keys = Object.keys(props || {});
|
||||||
|
if (keys.length) {
|
||||||
|
this.activeRule._fc_store.props_keys = keys;
|
||||||
|
} else {
|
||||||
|
delete this.activeRule._fc_store.props_keys;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fd-props-input {
|
||||||
|
flex: 1;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-props-input .fc-icon {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
120
src/components/QuickLayout.vue
Normal file
120
src/components/QuickLayout.vue
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_fd-quick-layout">
|
||||||
|
<el-popover ref="pop" placement="bottom" :width="240" :hide-after="0" trigger="click">
|
||||||
|
<template #reference>
|
||||||
|
<div>
|
||||||
|
<el-tooltip
|
||||||
|
effect="dark"
|
||||||
|
:content="t('designer.layout')"
|
||||||
|
placement="top"
|
||||||
|
:hide-after="0"
|
||||||
|
>
|
||||||
|
<i class="fc-icon icon-layout"></i>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="_fd-quick-layout-content">
|
||||||
|
<template v-for="(item,idx) in layout">
|
||||||
|
<div @click="change(idx)">
|
||||||
|
<i class="fc-icon" :class="'icon-column' + (idx + 1)"></i>
|
||||||
|
<span>{{ item.label }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</el-popover>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'QuickLayout',
|
||||||
|
inject: ['designer'],
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
t() {
|
||||||
|
return this.designer.setupState.t;
|
||||||
|
},
|
||||||
|
layout() {
|
||||||
|
return [{
|
||||||
|
span: 24,
|
||||||
|
label: this.t('designer.col1')
|
||||||
|
}, {
|
||||||
|
span: 12,
|
||||||
|
label: this.t('designer.col2')
|
||||||
|
}, {
|
||||||
|
span: 8,
|
||||||
|
label: this.t('designer.col3')
|
||||||
|
}, {
|
||||||
|
span: 6,
|
||||||
|
label: this.t('designer.col4')
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
check(rule) {
|
||||||
|
if (rule?._menu?.subForm) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let ctx = rule.__fc__.parent;
|
||||||
|
while (ctx) {
|
||||||
|
if (ctx.rule?._menu?.menu === 'layout' ||ctx.rule?._menu?.subForm) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ctx = ctx.parent;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
change(idx) {
|
||||||
|
const models = this.designer.setupState.dragForm.api.model();
|
||||||
|
const span = this.layout[idx].span;
|
||||||
|
let flag = false;
|
||||||
|
Object.keys(models).forEach(key => {
|
||||||
|
const rules = Array.isArray(models[key]) ? models[key] : [models[key]];
|
||||||
|
rules.forEach(rule => {
|
||||||
|
if (this.check(rule)) {
|
||||||
|
if (!rule.col) {
|
||||||
|
rule.col = {};
|
||||||
|
}
|
||||||
|
flag = flag || rule.col.span !== span;
|
||||||
|
rule.col.span = span;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
this.$refs.pop.hide();
|
||||||
|
if (flag) {
|
||||||
|
this.designer.setupState.addOperationRecord();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fd-quick-layout-content {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
width: 100%;
|
||||||
|
grid-column-gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-quick-layout-content > div {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-quick-layout-content > div:hover {
|
||||||
|
color: var(--fc-style-color-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-quick-layout-content i {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
</style>
|
75
src/components/Required.vue
Normal file
75
src/components/Required.vue
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_fd-required">
|
||||||
|
<el-switch v-model="required"></el-switch>
|
||||||
|
<LanguageInput v-model="requiredMsg" v-if="required"
|
||||||
|
:placeholder="t('validate.requiredPlaceholder')"></LanguageInput>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import is from '@form-create/utils/lib/type';
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
import LanguageInput from './language/LanguageInput.vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'Required',
|
||||||
|
components: {LanguageInput},
|
||||||
|
emits: ['update:modelValue'],
|
||||||
|
props: {
|
||||||
|
modelValue: {}
|
||||||
|
},
|
||||||
|
inject: ['designer'],
|
||||||
|
watch: {
|
||||||
|
required() {
|
||||||
|
this.update();
|
||||||
|
},
|
||||||
|
requiredMsg() {
|
||||||
|
this.update();
|
||||||
|
},
|
||||||
|
modelValue(n) {
|
||||||
|
const flag = is.String(n);
|
||||||
|
this.required = n === undefined ? false : (flag ? true : !!n);
|
||||||
|
this.requiredMsg = flag ? n : '';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
t() {
|
||||||
|
return this.designer.setupState.t;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
const flag = is.String(this.modelValue);
|
||||||
|
return {
|
||||||
|
required: this.modelValue === undefined ? false : (flag ? true : !!this.modelValue),
|
||||||
|
requiredMsg: flag ? this.modelValue : ''
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
update() {
|
||||||
|
let val;
|
||||||
|
if (this.required === false) {
|
||||||
|
val = false;
|
||||||
|
} else {
|
||||||
|
val = this.requiredMsg || true;
|
||||||
|
}
|
||||||
|
this.$emit('update:modelValue', val);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fd-required {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-required .el-input {
|
||||||
|
margin-left: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-required .el-switch {
|
||||||
|
height: 28px;
|
||||||
|
}
|
||||||
|
</style>
|
25
src/components/Row.vue
Normal file
25
src/components/Row.vue
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<template>
|
||||||
|
<el-col :span="24">
|
||||||
|
<div class="_fd-row el-row" :class="{'_fc-child-empty' : !$slots.default}" v-bind="$attrs">
|
||||||
|
<slot name="default"></slot>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'fcRow',
|
||||||
|
mounted() {
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fd-row {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
135
src/components/RuleSelect.vue
Normal file
135
src/components/RuleSelect.vue
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
<template>
|
||||||
|
<el-tree-select class="_fd-rule-select" :modelValue="modelValue" @update:modelValue="input" :size="size"
|
||||||
|
:multiple="multiple" checkStrictly
|
||||||
|
:showCheckbox="multiple" :data="tree">
|
||||||
|
<template #default="{ data }">
|
||||||
|
<template v-if="data.value === '___subform'">
|
||||||
|
<div class="_fd-rule-select-node">
|
||||||
|
<div>{{ data.label }}</div>
|
||||||
|
<span>{{ t('props.subform') }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
{{ data.label }}
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</el-tree-select>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'RuleSelect',
|
||||||
|
inject: ['designer'],
|
||||||
|
emits: ['update:modelValue', 'change'],
|
||||||
|
props: {
|
||||||
|
modelValue: [String, Number, Array],
|
||||||
|
onlyField: Boolean,
|
||||||
|
valueType: String,
|
||||||
|
size: String,
|
||||||
|
multiple: Boolean,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
activeRule() {
|
||||||
|
return this.designer.setupState.activeRule;
|
||||||
|
},
|
||||||
|
t() {
|
||||||
|
return this.designer.setupState.t;
|
||||||
|
},
|
||||||
|
tree() {
|
||||||
|
const activePage = this.designer.setupState.activePage;
|
||||||
|
let tree = [];
|
||||||
|
if (activePage.default) {
|
||||||
|
tree = this.getFields(this.designer.setupState.treeInfo);
|
||||||
|
} else {
|
||||||
|
tree = this.getFields(activePage.main.field && activePage.main === this.activeRule ? this.designer.setupState.treeInfo : this.designer.setupState.treeInfo[0].children);
|
||||||
|
}
|
||||||
|
let ctx = this.activeRule?.__fc__.parent;
|
||||||
|
while (ctx) {
|
||||||
|
if (ctx.rule === activePage.main) {
|
||||||
|
ctx = undefined;
|
||||||
|
} else if (ctx.rule._menu && ['array', 'object', 'scope'].indexOf(ctx.rule._menu.subForm) > -1) {
|
||||||
|
const subTree = this.getFields(this.designer.setupState.findTree(ctx.rule._fc_id))
|
||||||
|
if (subTree.length) {
|
||||||
|
tree.unshift({
|
||||||
|
value: '___subform',
|
||||||
|
disabled: true,
|
||||||
|
label: ctx.refRule?.__$title?.value || ctx.rule.title || ctx.rule._menu.label,
|
||||||
|
children: subTree
|
||||||
|
})
|
||||||
|
}
|
||||||
|
ctx = undefined;
|
||||||
|
} else {
|
||||||
|
ctx = ctx.parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tree;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getFields(children, parent = []) {
|
||||||
|
const fields = [];
|
||||||
|
children.forEach(({rule, children}) => {
|
||||||
|
const temp = [...parent];
|
||||||
|
if (rule.field) {
|
||||||
|
temp.push(rule);
|
||||||
|
}
|
||||||
|
const childrenFields = ['array', 'scope'].indexOf(rule._menu.subForm) > -1 ? [] : this.getFields(children || [], temp);
|
||||||
|
if (!this.onlyField || this.onlyField && rule.field) {
|
||||||
|
const item = {
|
||||||
|
value: parent.length ? (parent.map(item => item[this.valueType || '_fc_id']).join('.') + '.' + rule[this.valueType || '_fc_id']) : rule[this.valueType || '_fc_id'],
|
||||||
|
label: (rule?.__fc__?.refRule?.__$title?.value || rule.title || '').trim() || (rule.props && rule.props.label) || this.t('com.' + (rule._menu && rule._menu.name) + '.name') || (rule._menu && rule._menu.label) || rule.type,
|
||||||
|
rule,
|
||||||
|
parent,
|
||||||
|
};
|
||||||
|
if (childrenFields.length) {
|
||||||
|
item.children = childrenFields;
|
||||||
|
}
|
||||||
|
fields.push(item);
|
||||||
|
} else {
|
||||||
|
fields.push(...childrenFields)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return fields;
|
||||||
|
},
|
||||||
|
input(value) {
|
||||||
|
this.$emit('update:modelValue', value);
|
||||||
|
this.$emit('change', value);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.el-tree._fd-rule-select {
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-rule-select .el-tree-node:has(._fd-rule-select-node) {
|
||||||
|
border-bottom: 1px solid var(--fc-line-color-3);
|
||||||
|
border-bottom-style: dashed;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-rule-select .el-tree-node:has(._fd-rule-select-node) > .el-tree-node__content > .el-checkbox {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-rule-select .el-tree-node:has(._fd-rule-select-node) > .el-tree-node__content > .el-select-dropdown__item {
|
||||||
|
padding-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-rule-select-node {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-rule-select-node > div {
|
||||||
|
color: #61affe;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-rule-select-node > span {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
</style>
|
144
src/components/SignaturePad.vue
Normal file
144
src/components/SignaturePad.vue
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_fc-signature">
|
||||||
|
<template v-if="modelValue">
|
||||||
|
<div class="_fc-signature-preview">
|
||||||
|
<i class="fc-icon icon-delete2" @click="remove"></i>
|
||||||
|
<img :src="modelValue" alt="signature">
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<div class="_fc-signature-btn" @click="visible = true">
|
||||||
|
<i class="fc-icon icon-edit2"></i> {{ formCreateInject.t('signaturePadTip') || '点击添加手写签名' }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-dialog class="_fc-signature-dialog" :title="formCreateInject.t('signaturePadTitle') || '请在虚线框内书写'"
|
||||||
|
v-model="visible"
|
||||||
|
destroy-on-close
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
append-to-body width="640px">
|
||||||
|
<canvas class="_fc-signature-pad" ref="pad" width="600px" height="270px"></canvas>
|
||||||
|
<template #footer>
|
||||||
|
<div>
|
||||||
|
<el-button size="default" @click="clear()">{{ formCreateInject.t('reset') || '重置' }}</el-button>
|
||||||
|
<el-button type="primary" :disabled="isEmpty" @click="submit" size="default">{{ formCreateInject.t('ok') || '确定' }}</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent, markRaw} from 'vue';
|
||||||
|
import SignaturePad from 'signature_pad';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'SignaturePad',
|
||||||
|
emits: ['update:modelValue', 'change', 'remove'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
visible: false,
|
||||||
|
isEmpty: true,
|
||||||
|
signaturePad: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
modelValue: String,
|
||||||
|
penColor: String,
|
||||||
|
formCreateInject: Object,
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
visible(val) {
|
||||||
|
if (val) {
|
||||||
|
this.isEmpty = true;
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.signaturePad = markRaw(new SignaturePad(this.$refs.pad, {
|
||||||
|
penColor: this.penColor,
|
||||||
|
}));
|
||||||
|
this.signaturePad.addEventListener('endStroke', () => {
|
||||||
|
this.isEmpty = this.signaturePad.isEmpty();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.signaturePad.off();
|
||||||
|
this.signaturePad = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
clear() {
|
||||||
|
this.signaturePad.clear();
|
||||||
|
this.isEmpty = true;
|
||||||
|
},
|
||||||
|
submit() {
|
||||||
|
const res = this.signaturePad.toDataURL();
|
||||||
|
this.updateValue(res);
|
||||||
|
this.visible = false;
|
||||||
|
},
|
||||||
|
updateValue(val) {
|
||||||
|
this.$emit('update:modelValue', val);
|
||||||
|
this.$emit('change', val);
|
||||||
|
},
|
||||||
|
remove() {
|
||||||
|
this.updateValue('');
|
||||||
|
this.$emit('remove');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
._fc-signature {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-signature-btn, ._fc-signature-preview {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 160px;
|
||||||
|
height: 88px;
|
||||||
|
line-height: 88px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgb(201, 204, 216);
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px dashed rgb(212, 215, 224);
|
||||||
|
text-align: center;
|
||||||
|
background: rgb(255, 255, 255);
|
||||||
|
position: relative;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-signature-btn {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-signature-preview > img {
|
||||||
|
display: inline-block;
|
||||||
|
height: 88px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-signature-preview .icon-delete2 {
|
||||||
|
position: absolute;
|
||||||
|
top: 9px;
|
||||||
|
right: 9px;
|
||||||
|
display: inline-block;
|
||||||
|
line-height: 14px;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-signature-btn i {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-signature-dialog .el-dialog__body {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-signature-pad {
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px dashed #D4D7E0;
|
||||||
|
background-image: linear-gradient(#FFFFFF 14px, transparent 0), linear-gradient(90deg, #FFFFFF 14px, #D4D7E0 0);
|
||||||
|
background-size: 15px 15px;
|
||||||
|
}
|
||||||
|
</style>
|
310
src/components/SlotsConfig.vue
Normal file
310
src/components/SlotsConfig.vue
Normal file
@ -0,0 +1,310 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_fd-slots-config">
|
||||||
|
<template v-for="(item, key) in easySlots">
|
||||||
|
<ConfigItem :label="item.label">
|
||||||
|
<el-input size="small" v-model="item.value" clearable @blur="onChange">
|
||||||
|
<template #prepend v-if="!item.only">
|
||||||
|
<el-select size="small" v-model="item.type" @change="changeType(item)">
|
||||||
|
<template v-for="name in type">
|
||||||
|
<el-option :label="t('props.' + name)" :value="name"/>
|
||||||
|
</template>
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
<template #append v-if="item.type === 'icon'">
|
||||||
|
<el-popover :ref="key" placement="bottom" popper-class="_fd-slots-config-pop" :width="400"
|
||||||
|
trigger="click">
|
||||||
|
<div class="_fd-slots-icons">
|
||||||
|
<template v-for="name in icons">
|
||||||
|
<div class="_fd-slots-icon" @click="changeIcon(item, name, key)">
|
||||||
|
<i class="fc-icon iconfont" :class="name"></i>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<template #reference>
|
||||||
|
<i class="fc-icon icon-menu"></i>
|
||||||
|
</template>
|
||||||
|
</el-popover>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</ConfigItem>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
import uniqueId from '@form-create/utils/lib/unique';
|
||||||
|
import ConfigItem from './style/ConfigItem.vue';
|
||||||
|
import {uniqueArray} from '../utils';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'SlotsConfig',
|
||||||
|
inject: ['designer'],
|
||||||
|
components: {ConfigItem},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
type: ['icon', 'text'],
|
||||||
|
easySlots: {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
t() {
|
||||||
|
return this.designer.setupState.t;
|
||||||
|
},
|
||||||
|
slots() {
|
||||||
|
const menu = this.designer.setupState?.activeRule?._menu || {};
|
||||||
|
const slots = menu.easySlots || [];
|
||||||
|
return slots.map(slot => {
|
||||||
|
if (typeof slot === 'string') {
|
||||||
|
return {
|
||||||
|
value: slot,
|
||||||
|
label: this.t('com.' + menu.name + '.slots.' + slot) || this.t('slots.' + slot) || this.t('props.' + slot) || slot,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const item = {...slot};
|
||||||
|
if (!item.label) {
|
||||||
|
item.label = this.t('com.' + menu.name + '.slots.' + slot.value) || this.t('slots.' + slot.value) || this.t('props.' + slot.value) || slot.value;
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
},
|
||||||
|
modelValue() {
|
||||||
|
return this.designer.setupState?.activeRule?.$easySlots || {};
|
||||||
|
},
|
||||||
|
icons() {
|
||||||
|
return uniqueArray([
|
||||||
|
...this.designer.setupState.getConfig('icons', []),
|
||||||
|
'icon-layout',
|
||||||
|
'icon-column1',
|
||||||
|
'icon-column2',
|
||||||
|
'icon-column3',
|
||||||
|
'icon-column4',
|
||||||
|
'icon-tab',
|
||||||
|
'icon-config-event',
|
||||||
|
'icon-step-form',
|
||||||
|
'icon-slider',
|
||||||
|
'icon-dialog',
|
||||||
|
'icon-justify-spacearound',
|
||||||
|
'icon-upload',
|
||||||
|
'icon-copy',
|
||||||
|
'icon-time-range',
|
||||||
|
'icon-task-add',
|
||||||
|
'icon-justify-spacebetween',
|
||||||
|
'icon-import',
|
||||||
|
'icon-config-base',
|
||||||
|
'icon-alignitems-stretch',
|
||||||
|
'icon-alignitems-flexend',
|
||||||
|
'icon-check',
|
||||||
|
'icon-auto',
|
||||||
|
'icon-calendar',
|
||||||
|
'icon-config-style',
|
||||||
|
'icon-config-advanced',
|
||||||
|
'icon-config-props',
|
||||||
|
'icon-delete-circle2',
|
||||||
|
'icon-delete-circle',
|
||||||
|
'icon-delete',
|
||||||
|
'icon-direction-rowreverse',
|
||||||
|
'icon-display-flex',
|
||||||
|
'icon-drag',
|
||||||
|
'icon-display-block',
|
||||||
|
'icon-data',
|
||||||
|
'icon-edit2',
|
||||||
|
'icon-edit',
|
||||||
|
'icon-add-col',
|
||||||
|
'icon-display-inlineblock',
|
||||||
|
'icon-config-validate',
|
||||||
|
'icon-down',
|
||||||
|
'icon-display-inline',
|
||||||
|
'icon-eye',
|
||||||
|
'icon-eye-close',
|
||||||
|
'icon-preview',
|
||||||
|
'icon-flex-nowrap',
|
||||||
|
'icon-folder',
|
||||||
|
'icon-form-circle',
|
||||||
|
'icon-flex-wrap',
|
||||||
|
'icon-form',
|
||||||
|
'icon-form-item',
|
||||||
|
'icon-icon',
|
||||||
|
'icon-image',
|
||||||
|
'icon-justify-flexstart',
|
||||||
|
'icon-justify-center',
|
||||||
|
'icon-justify-stretch',
|
||||||
|
'icon-link2',
|
||||||
|
'icon-minus',
|
||||||
|
'icon-menu2',
|
||||||
|
'icon-more',
|
||||||
|
'icon-menu',
|
||||||
|
'icon-language',
|
||||||
|
'icon-pad',
|
||||||
|
'icon-mobile',
|
||||||
|
'icon-page-max',
|
||||||
|
'icon-move',
|
||||||
|
'icon-page-min',
|
||||||
|
'icon-pre-step',
|
||||||
|
'icon-pc',
|
||||||
|
'icon-page',
|
||||||
|
'icon-refresh',
|
||||||
|
'icon-radius',
|
||||||
|
'icon-save-filled',
|
||||||
|
'icon-question',
|
||||||
|
'icon-scroll',
|
||||||
|
'icon-script',
|
||||||
|
'icon-setting',
|
||||||
|
'icon-save',
|
||||||
|
'icon-shadow',
|
||||||
|
'icon-variable',
|
||||||
|
'icon-yes',
|
||||||
|
'icon-shadow-inset',
|
||||||
|
'icon-date',
|
||||||
|
'icon-date-range',
|
||||||
|
'icon-collapse',
|
||||||
|
'icon-switch',
|
||||||
|
'icon-subform',
|
||||||
|
'icon-tree-select',
|
||||||
|
'icon-value',
|
||||||
|
'icon-alert',
|
||||||
|
'icon-card',
|
||||||
|
'icon-checkbox',
|
||||||
|
'icon-cascader',
|
||||||
|
'icon-button',
|
||||||
|
'icon-data-table',
|
||||||
|
'icon-group',
|
||||||
|
'icon-divider',
|
||||||
|
'icon-flex',
|
||||||
|
'icon-descriptions',
|
||||||
|
'icon-html',
|
||||||
|
'icon-editor',
|
||||||
|
'icon-input',
|
||||||
|
'icon-link',
|
||||||
|
'icon-password',
|
||||||
|
'icon-radio',
|
||||||
|
'icon-row',
|
||||||
|
'icon-inline',
|
||||||
|
'icon-rate',
|
||||||
|
'icon-color',
|
||||||
|
'icon-select',
|
||||||
|
'icon-json',
|
||||||
|
'icon-number',
|
||||||
|
'icon-space',
|
||||||
|
'icon-table-form',
|
||||||
|
'icon-table-form2',
|
||||||
|
'icon-time',
|
||||||
|
'icon-span',
|
||||||
|
'icon-textarea',
|
||||||
|
'icon-tooltip',
|
||||||
|
'icon-slot',
|
||||||
|
'icon-transfer',
|
||||||
|
'icon-tag',
|
||||||
|
'icon-watermark',
|
||||||
|
'icon-tree',
|
||||||
|
'icon-table',
|
||||||
|
'icon-add-child',
|
||||||
|
'icon-add2',
|
||||||
|
'icon-add',
|
||||||
|
'icon-alignitems-baseline',
|
||||||
|
'icon-add-circle',
|
||||||
|
'icon-alignitems-center'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
modelValue: {
|
||||||
|
handler: function (val) {
|
||||||
|
const easySlots = {};
|
||||||
|
this.slots.forEach(({value, label, type}) => {
|
||||||
|
if (val[value]) {
|
||||||
|
easySlots[value] = {...val[value]};
|
||||||
|
} else if (this.easySlots[value]) {
|
||||||
|
easySlots[value] = {
|
||||||
|
type: this.easySlots[value].type,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
easySlots[value] = {
|
||||||
|
type: type || 'icon',
|
||||||
|
value: '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
easySlots[value].only = type;
|
||||||
|
easySlots[value].label = label;
|
||||||
|
})
|
||||||
|
this.easySlots = easySlots;
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
changeIcon(item, icon, key) {
|
||||||
|
item.value = icon;
|
||||||
|
this.onChange();
|
||||||
|
this.$refs[key][0].hide();
|
||||||
|
},
|
||||||
|
changeType(item) {
|
||||||
|
if (item.value) {
|
||||||
|
item.value = '';
|
||||||
|
this.onChange();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onChange() {
|
||||||
|
if (this.designer.setupState?.activeRule) {
|
||||||
|
const easySlots = {};
|
||||||
|
Object.keys(this.easySlots).forEach(key => {
|
||||||
|
if (this.easySlots[key].value) {
|
||||||
|
easySlots[key] = {...this.easySlots[key]};
|
||||||
|
delete easySlots[key].label;
|
||||||
|
delete easySlots[key].only;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (Object.keys(easySlots).length === 0) {
|
||||||
|
delete this.designer.setupState.activeRule.$easySlots;
|
||||||
|
} else {
|
||||||
|
this.designer.setupState.activeRule.$easySlots = easySlots;
|
||||||
|
}
|
||||||
|
this.designer.setupState.activeRule.key = uniqueId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fd-slots-config .el-input {
|
||||||
|
width: 170px;
|
||||||
|
min-width: 170px;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-slots-config .el-select {
|
||||||
|
width: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-slots-config .el-select input, ._fd-slots-config .fc-icon {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-slots-config .el-input-group__append {
|
||||||
|
padding: 0 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-slots-icons {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(13, 1fr);
|
||||||
|
width: 100%;
|
||||||
|
grid-gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-slots-icon {
|
||||||
|
text-align: center;
|
||||||
|
color: var(--fc-text-color-1);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-slots-config-pop {
|
||||||
|
max-height: 320px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
</style>
|
59
src/components/SpanInput.vue
Normal file
59
src/components/SpanInput.vue
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<template>
|
||||||
|
<el-radio-group :modelValue="modelValue" class="_fd-span-input">
|
||||||
|
<el-radio-button :value="item.value" :label="item.value" v-for="item in layout" :key="item.value"
|
||||||
|
@click="onInput(item.value)">
|
||||||
|
{{ item.value === 24 ? t('form.row') : item.label }}
|
||||||
|
</el-radio-button>
|
||||||
|
</el-radio-group>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'SpanInput',
|
||||||
|
props: {
|
||||||
|
modelValue: [Number, String],
|
||||||
|
},
|
||||||
|
inject: ['designer'],
|
||||||
|
computed: {
|
||||||
|
t() {
|
||||||
|
return this.designer.setupState.t;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
layout: [
|
||||||
|
{label: '1/4', value: 6},
|
||||||
|
{label: '1/3', value: 8},
|
||||||
|
{label: '1/2', value: 12},
|
||||||
|
{label: '2/3', value: 16},
|
||||||
|
{label: '3/4', value: 18},
|
||||||
|
{label: '整行', value: 24},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onInput(span) {
|
||||||
|
this.$emit('update:modelValue', span === this.modelValue ? '' : span);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fd-span-input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-span-input .el-radio-button__inner {
|
||||||
|
width: 100%;
|
||||||
|
padding: 4px;
|
||||||
|
line-height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-span-input .el-radio-button {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
</style>
|
141
src/components/Struct.vue
Normal file
141
src/components/Struct.vue
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_fd-struct">
|
||||||
|
<el-badge type="warning" is-dot :hidden="!configured">
|
||||||
|
<div @click="visible=true">
|
||||||
|
<slot>
|
||||||
|
<el-button class="_fd-plain-button" plain size="small">
|
||||||
|
{{ title || t('struct.title') }}
|
||||||
|
</el-button>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
</el-badge>
|
||||||
|
<el-dialog class="_fd-struct-dialog _fd-config-dialog" :title="title || t('struct.title')" v-model="visible"
|
||||||
|
destroy-on-close
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
append-to-body width="800px">
|
||||||
|
<div ref="editor" v-if="visible"></div>
|
||||||
|
<template #footer>
|
||||||
|
<div>
|
||||||
|
<el-button @click="visible = false" size="default">{{ t('props.cancel') }}</el-button>
|
||||||
|
<el-button type="primary" @click="onOk" size="default">{{ t('props.ok') }}</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import 'codemirror/lib/codemirror.css';
|
||||||
|
import CodeMirror from 'codemirror/lib/codemirror';
|
||||||
|
import 'codemirror/mode/javascript/javascript';
|
||||||
|
import {deepParseFn, toJSON} from '../utils/index';
|
||||||
|
import {deepCopy} from '@form-create/utils/lib/deepextend';
|
||||||
|
import {defineComponent, markRaw} from 'vue';
|
||||||
|
import is from '@form-create/utils/lib/type';
|
||||||
|
import errorMessage from '../utils/message';
|
||||||
|
import beautify from 'js-beautify';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'Struct',
|
||||||
|
emits: ['update:modelValue'],
|
||||||
|
props: {
|
||||||
|
modelValue: [Object, Array, Function],
|
||||||
|
title: String,
|
||||||
|
defaultValue: {
|
||||||
|
require: false
|
||||||
|
},
|
||||||
|
validate: Function,
|
||||||
|
},
|
||||||
|
inject: ['designer'],
|
||||||
|
computed: {
|
||||||
|
t() {
|
||||||
|
return this.designer.setupState.t;
|
||||||
|
},
|
||||||
|
configured() {
|
||||||
|
return !is.empty(this.modelValue) && Object.keys(this.modelValue).length > 0;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
editor: null,
|
||||||
|
visible: false,
|
||||||
|
oldVal: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
modelValue() {
|
||||||
|
this.load();
|
||||||
|
},
|
||||||
|
visible(n) {
|
||||||
|
if (n) {
|
||||||
|
this.load();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
load() {
|
||||||
|
const val = toJSON(deepParseFn(this.modelValue ? deepCopy(this.modelValue) : this.defaultValue));
|
||||||
|
this.oldVal = val;
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.editor = markRaw(CodeMirror(this.$refs.editor, {
|
||||||
|
lineNumbers: true,
|
||||||
|
mode: 'javascript',
|
||||||
|
lint: true,
|
||||||
|
line: true,
|
||||||
|
tabSize: 2,
|
||||||
|
lineWrapping: true,
|
||||||
|
value: val ? beautify.js(val, {
|
||||||
|
indent_size: '2',
|
||||||
|
indent_char: ' ',
|
||||||
|
max_preserve_newlines: '5',
|
||||||
|
indent_scripts: 'separate',
|
||||||
|
}) : '',
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onOk() {
|
||||||
|
const str = (this.editor.getValue() || '').trim();
|
||||||
|
let val;
|
||||||
|
try {
|
||||||
|
val = (new Function('return ' + str))();
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
errorMessage(this.t('struct.errorMsg'));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this.validate && false === this.validate(val)) {
|
||||||
|
errorMessage(this.t('struct.errorMsg'));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.visible = false;
|
||||||
|
if (toJSON(val, null, 2) !== this.oldVal) {
|
||||||
|
this.$emit('update:modelValue', val);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fd-struct {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-struct .el-badge {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-struct .el-button {
|
||||||
|
font-weight: 400;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-struct-dialog .CodeMirror {
|
||||||
|
height: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-struct-dialog .el-dialog__body {
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
</style>
|
121
src/components/StructEditor.vue
Normal file
121
src/components/StructEditor.vue
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_fd-struct-editor">
|
||||||
|
<div ref="editor"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import 'codemirror/lib/codemirror.css';
|
||||||
|
import CodeMirror from 'codemirror/lib/codemirror';
|
||||||
|
import 'codemirror/mode/javascript/javascript';
|
||||||
|
import {toJSON} from '../utils/index';
|
||||||
|
import {defineComponent, markRaw} from 'vue';
|
||||||
|
import errorMessage from '../utils/message';
|
||||||
|
import {designerForm} from '../utils/form';
|
||||||
|
import beautify from 'js-beautify';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'StructEditor',
|
||||||
|
props: {
|
||||||
|
modelValue: [Object, Array, Function],
|
||||||
|
format: Boolean,
|
||||||
|
defaultValue: {
|
||||||
|
require: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ['blur', 'focus', 'update:modelValue'],
|
||||||
|
inject: ['designer'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
editor: null,
|
||||||
|
visible: false,
|
||||||
|
err: false,
|
||||||
|
oldVal: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
t() {
|
||||||
|
return this.designer.setupState.t;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
modelValue(n) {
|
||||||
|
if (this.editor) {
|
||||||
|
const val = n ? this.toJson(n) : '';
|
||||||
|
this.oldVal = val;
|
||||||
|
const scrollInfo = this.editor.getScrollInfo();
|
||||||
|
const scrollTop = scrollInfo.top;
|
||||||
|
this.editor.setValue(val);
|
||||||
|
this.editor.scrollTo(0, scrollTop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.load();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
toJson(val) {
|
||||||
|
return this.format ? designerForm.toJson(val, 2) : toJSON(val);
|
||||||
|
},
|
||||||
|
load() {
|
||||||
|
const val = this.modelValue ? this.toJson(this.modelValue) : '';
|
||||||
|
this.oldVal = val;
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.editor = markRaw(CodeMirror(this.$refs.editor, {
|
||||||
|
lineNumbers: true,
|
||||||
|
mode: 'javascript',
|
||||||
|
lint: true,
|
||||||
|
line: true,
|
||||||
|
tabSize: 2,
|
||||||
|
lineWrapping: true,
|
||||||
|
value: val ? beautify.js(val, {
|
||||||
|
indent_size: '2',
|
||||||
|
indent_char: ' ',
|
||||||
|
max_preserve_newlines: '5',
|
||||||
|
indent_scripts: 'separate',
|
||||||
|
}) : '',
|
||||||
|
}));
|
||||||
|
this.editor.on('blur', () => {
|
||||||
|
this.$emit('blur');
|
||||||
|
});
|
||||||
|
this.editor.on('focus', () => {
|
||||||
|
this.$emit('focus');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
save() {
|
||||||
|
const str = (this.editor.getValue() || '').trim();
|
||||||
|
let val;
|
||||||
|
try {
|
||||||
|
val = (new Function('return ' + str))();
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
errorMessage(this.t('struct.errorMsg'));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this.validate && false === this.validate(val)) {
|
||||||
|
this.err = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.visible = false;
|
||||||
|
if (this.toJson(val) !== this.oldVal) {
|
||||||
|
this.$emit('update:modelValue', val);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fd-struct-editor {
|
||||||
|
flex: 1;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-struct-editor > div {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
90
src/components/SubList.vue
Normal file
90
src/components/SubList.vue
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_fc-sublist">
|
||||||
|
<p class="_fc-r-title">
|
||||||
|
<span>{{ t('designer.sublist') }}</span>
|
||||||
|
<i class="fc-icon icon-add-circle"
|
||||||
|
@click="toolHandle(activeRule ,'addChild')"></i>
|
||||||
|
</p>
|
||||||
|
<fcDraggable :group="{name:'sub', pull:'clone', put:false}" :sort="true"
|
||||||
|
handle=".icon-drag" direction="vertical" :animation="0"
|
||||||
|
itemKey="_fc_id"
|
||||||
|
@end="end"
|
||||||
|
:list="activeRuleChildren">
|
||||||
|
<template #item="{element,index}">
|
||||||
|
<ConfigItem>
|
||||||
|
<template #label>
|
||||||
|
<i class="fc-icon icon-drag"></i>
|
||||||
|
<span>{{
|
||||||
|
(t('com.' + (element._menu.name) + '.name') || activeRule._menu.label) + ' ' + (index + 1)
|
||||||
|
}}</span>
|
||||||
|
</template>
|
||||||
|
<i class="fc-icon icon-copy" @click="toolHandle(element ,'copy')"></i>
|
||||||
|
<i class="fc-icon icon-delete" @click="toolHandle(element ,'delete')"></i>
|
||||||
|
<template #append v-if="activeRule._menu.subRender">
|
||||||
|
<VNode
|
||||||
|
:fn="()=>subRender(activeRule._menu.subRender, activeRule, element)"></VNode>
|
||||||
|
</template>
|
||||||
|
</ConfigItem>
|
||||||
|
</template>
|
||||||
|
</fcDraggable>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
import ConfigItem from './style/ConfigItem.vue';
|
||||||
|
import VNode from './VNode.vue';
|
||||||
|
import fcDraggable from 'vuedraggable/src/vuedraggable';
|
||||||
|
import uniqueId from '@form-create/utils/lib/unique';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'SubList',
|
||||||
|
components: {fcDraggable, VNode, ConfigItem},
|
||||||
|
inject: ['designer'],
|
||||||
|
computed: {
|
||||||
|
t() {
|
||||||
|
return this.designer.setupState.t;
|
||||||
|
},
|
||||||
|
activeRuleChildren() {
|
||||||
|
return this.designer.setupState.activeRuleChildren;
|
||||||
|
},
|
||||||
|
activeRule() {
|
||||||
|
return this.designer.setupState.activeRule;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
toolHandle(...args) {
|
||||||
|
this.designer.setupState.toolHandle(...args);
|
||||||
|
},
|
||||||
|
subRender(...args) {
|
||||||
|
return this.designer.setupState.subRender(...args);
|
||||||
|
},
|
||||||
|
end({oldIndex, newIndex}) {
|
||||||
|
if (oldIndex === newIndex) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const rule = this.activeRule.children.splice(oldIndex, 1);
|
||||||
|
this.activeRule.children.splice(newIndex, 0, rule[0]);
|
||||||
|
this.activeRule.key = uniqueId();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
._fc-sublist ._fc-r-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-sublist .fc-icon {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-sublist ._fd-config-item + ._fd-config-item {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
</style>
|
208
src/components/TableOptions.vue
Normal file
208
src/components/TableOptions.vue
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_td-table-opt">
|
||||||
|
<el-table
|
||||||
|
:data="value"
|
||||||
|
:key="checked ? '2': '1'"
|
||||||
|
border
|
||||||
|
:size="size || 'small'"
|
||||||
|
style="width: 100%">
|
||||||
|
<template v-for="(col,idx) in overColumn" :key="col.label + idx">
|
||||||
|
<el-table-column :label="col.label">
|
||||||
|
<template #default="scope">
|
||||||
|
<template v-if="col.value">
|
||||||
|
<ValueInput :size="size || 'small'" :modelValue="scope.row[col.key]"
|
||||||
|
@update:modelValue="(n)=>(scope.row[col.key] = n)"
|
||||||
|
@blur="onInput(scope.row)" @change-type="onInput(scope.row)"></ValueInput>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<el-input :size="size || 'small'" :modelValue="scope.row[col.key]"
|
||||||
|
@update:modelValue="(n)=>(scope.row[col.key] = n)"
|
||||||
|
@blur="onInput(scope.row)"></el-input>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</template>
|
||||||
|
<el-table-column width="35" align="center" fixed="right">
|
||||||
|
<template #default="scope">
|
||||||
|
<i class="fc-icon icon-delete" @click="del(scope.$index)"></i>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<div class="_td-table-opt-handle">
|
||||||
|
<el-button link type="primary" @click="add" v-if="!max || max > value.length">
|
||||||
|
<i class="fc-icon icon-add"></i> {{ t('tableOptions.add') }}
|
||||||
|
</el-button>
|
||||||
|
<el-checkbox v-model="checked" :label="t('tableOptions.keyValue')" v-if="keyValue"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
import {copy} from '@form-create/utils/lib/extend';
|
||||||
|
import ValueInput from './computed/ValueInput.vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'TableOptions',
|
||||||
|
components: {ValueInput},
|
||||||
|
emits: ['update:modelValue', 'change'],
|
||||||
|
props: {
|
||||||
|
modelValue: [Array, Object],
|
||||||
|
column: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [{label: 'label', key: 'label'}, {label: 'value', key: 'value'}]
|
||||||
|
},
|
||||||
|
valueType: String,
|
||||||
|
keyValue: String,
|
||||||
|
max: Number,
|
||||||
|
size: String,
|
||||||
|
},
|
||||||
|
inject: ['designer'],
|
||||||
|
watch: {
|
||||||
|
modelValue() {
|
||||||
|
this.value = this.tidyModelValue();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
t() {
|
||||||
|
return this.designer.setupState.t;
|
||||||
|
},
|
||||||
|
overColumn() {
|
||||||
|
let column = this.column;
|
||||||
|
if (this.checked) {
|
||||||
|
for (let i = 0; i < column.length; i++) {
|
||||||
|
if (column[i].key === this.keyValue) {
|
||||||
|
return [column[i]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return column;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
value: this.tidyModelValue(),
|
||||||
|
checked: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
if (this.keyValue) {
|
||||||
|
this.checked = this.isChecked();
|
||||||
|
this.$watch('checked', (n) => {
|
||||||
|
n && this.input();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
isChecked() {
|
||||||
|
for (let i = 0; i < this.value.length; i++) {
|
||||||
|
const item = this.value[i];
|
||||||
|
const keys = Object.keys(item);
|
||||||
|
const value = item[this.keyValue];
|
||||||
|
for (let i = 0; i < keys.length; i++) {
|
||||||
|
if (value !== item[keys[i]]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
tidyModelValue() {
|
||||||
|
const modelValue = this.modelValue;
|
||||||
|
if (this.valueType === 'string') {
|
||||||
|
return (modelValue || []).map(value => {
|
||||||
|
return {value: '' + value}
|
||||||
|
})
|
||||||
|
} else if (this.valueType === 'object') {
|
||||||
|
return Object.keys((modelValue || {})).map(label => {
|
||||||
|
return {label, value: modelValue[label]}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return [...modelValue || []].map(v => {
|
||||||
|
return copy(v);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tidyValue() {
|
||||||
|
if (this.valueType === 'object') {
|
||||||
|
const obj = {};
|
||||||
|
this.value.forEach(v => {
|
||||||
|
if (v.label && v.value) {
|
||||||
|
obj[v.label] = v.value;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return obj;
|
||||||
|
} else {
|
||||||
|
return this.value.map(v => {
|
||||||
|
if (this.valueType === 'string') {
|
||||||
|
return v.value;
|
||||||
|
}
|
||||||
|
if (this.checked) {
|
||||||
|
const value = v[this.keyValue];
|
||||||
|
return this.column.reduce((item, col) => {
|
||||||
|
item[col.key] = value;
|
||||||
|
return item;
|
||||||
|
}, {});
|
||||||
|
} else {
|
||||||
|
return {...v}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onInput(item) {
|
||||||
|
if (this.column.length === 1 && '' === item[this.column[0].key]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const flag = this.column.every(v => {
|
||||||
|
if (v.required === false) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (['object', 'string'].indexOf(this.valueType) > -1) {
|
||||||
|
return item[v.key] !== undefined && item[v.key] !== '' && item[v.key] !== null;
|
||||||
|
}
|
||||||
|
return item[v.key] !== undefined;
|
||||||
|
})
|
||||||
|
if (flag) {
|
||||||
|
this.input();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
input() {
|
||||||
|
const value = this.tidyValue();
|
||||||
|
this.$emit('update:modelValue', value);
|
||||||
|
this.$emit('change', value);
|
||||||
|
},
|
||||||
|
add() {
|
||||||
|
this.value.push(this.column.reduce((initial, v) => {
|
||||||
|
initial[v.key] = '';
|
||||||
|
return initial;
|
||||||
|
}, {}));
|
||||||
|
},
|
||||||
|
del(idx) {
|
||||||
|
this.value.splice(idx, 1);
|
||||||
|
this.input();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._td-table-opt {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._td-table-opt .icon-delete {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
._td-table-opt .el-table {
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
._td-table-opt-handle {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding-right: 5px;
|
||||||
|
}
|
||||||
|
</style>
|
138
src/components/ToolsBar.vue
Normal file
138
src/components/ToolsBar.vue
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_fc-r-tools-bar" v-if="tools.length > 2">
|
||||||
|
<div class="_fc-r-tools">
|
||||||
|
<template v-for="item in tools" :key="item.icon">
|
||||||
|
<el-tooltip
|
||||||
|
effect="dark"
|
||||||
|
:content="item.label"
|
||||||
|
placement="bottom"
|
||||||
|
persistent
|
||||||
|
:hide-after="0"
|
||||||
|
>
|
||||||
|
<div class="_fc-r-tool" @click="onClick(item.icon)">
|
||||||
|
<i class="fc-icon" :class="`icon-config-${item.icon}`"></i>
|
||||||
|
</div>
|
||||||
|
</el-tooltip>
|
||||||
|
</template>
|
||||||
|
<div class="_fc-r-tools-close _fc-r-tool" @click="clearActiveRule">
|
||||||
|
<i class="fc-icon icon-add2"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'ToolsBar',
|
||||||
|
inject: ['designer'],
|
||||||
|
computed: {
|
||||||
|
t() {
|
||||||
|
return this.designer.setupState.t;
|
||||||
|
},
|
||||||
|
tools() {
|
||||||
|
const vm = this.designer.setupState;
|
||||||
|
const tools = [];
|
||||||
|
if (!vm.activeRule && !vm.customForm.config) {
|
||||||
|
return tools;
|
||||||
|
}
|
||||||
|
if (vm.baseForm.isShow) {
|
||||||
|
tools.push({
|
||||||
|
label: this.t('designer.rule'),
|
||||||
|
icon: 'base'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (vm.propsForm.isShow || (vm.customForm.isShow && vm.customForm.propsShow)) {
|
||||||
|
tools.push({
|
||||||
|
label: this.t('designer.props'),
|
||||||
|
icon: 'props'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (vm.advancedForm.isShow) {
|
||||||
|
tools.push({
|
||||||
|
label: this.t('designer.advanced'),
|
||||||
|
icon: 'advanced'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (vm.styleForm.isShow) {
|
||||||
|
tools.push({
|
||||||
|
label: this.t('designer.style'),
|
||||||
|
icon: 'style'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (vm.eventShow) {
|
||||||
|
tools.push({
|
||||||
|
label: this.t('designer.event'),
|
||||||
|
icon: 'event'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (vm.validateForm.isShow) {
|
||||||
|
tools.push({
|
||||||
|
label: this.t('designer.validate'),
|
||||||
|
icon: 'validate'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return tools;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onClick(icon) {
|
||||||
|
document.querySelector(`#_fd-config-${icon}`).scrollIntoView({
|
||||||
|
block: 'start',
|
||||||
|
inline: 'nearest',
|
||||||
|
behavior: 'smooth'
|
||||||
|
})
|
||||||
|
},
|
||||||
|
clearActiveRule() {
|
||||||
|
this.designer.setupState.clearActiveRule();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
._fc-r-tools-bar {
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-r-tools-close {
|
||||||
|
position: absolute;
|
||||||
|
right: 5px;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
color: var(--fc-text-color-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-r-tools {
|
||||||
|
display: flex;
|
||||||
|
border-top: 1px solid var(--fc-line-color-3);
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: 0 10px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-r-tool {
|
||||||
|
display: flex;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-r-tool:hover {
|
||||||
|
color: var(--fc-style-color-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-r-tool .fc-icon {
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-r-tools-close .fc-icon {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
</style>
|
226
src/components/TreeOptions.vue
Normal file
226
src/components/TreeOptions.vue
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_fd-tree-opt">
|
||||||
|
<el-tree
|
||||||
|
:data="value"
|
||||||
|
node-key="index"
|
||||||
|
:key="checked ? '1' : '2'"
|
||||||
|
:indent="5"
|
||||||
|
:expand-on-click-node="false">
|
||||||
|
<template #default="{ node, data }">
|
||||||
|
<div class="_fd-tree-opt-node">
|
||||||
|
<template v-if="!checked">
|
||||||
|
<el-input class="_fd-tree-opt-first" v-model="data[overColumns.label]"
|
||||||
|
@blur="change"/>
|
||||||
|
<ValueInput class="_fd-tree-opt-last" v-model="data[overColumns.value]" @blur="change"
|
||||||
|
@change-type="change">
|
||||||
|
<template #append>
|
||||||
|
<div class="_fd-tree-opt-btn" @click="add(node, data)">
|
||||||
|
<i class="fc-icon icon-add"></i>
|
||||||
|
</div>
|
||||||
|
<div class="_fd-tree-opt-btn" @click="append(data)">
|
||||||
|
<i class="fc-icon icon-add-child"></i>
|
||||||
|
</div>
|
||||||
|
<div class="_fd-tree-opt-btn _fd-tree-opt-danger" @click="remove(node, data)">
|
||||||
|
<i class="fc-icon icon-delete"></i>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</ValueInput>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<el-input class="_fd-tree-opt-last _label" v-model="data[keyValue]" @blur="change">
|
||||||
|
<template #append>
|
||||||
|
<div class="_fd-tree-opt-btn" @click="add(node, data)">
|
||||||
|
<i class="fc-icon icon-add"></i>
|
||||||
|
</div>
|
||||||
|
<div class="_fd-tree-opt-btn" @click="append(data)">
|
||||||
|
<i class="fc-icon icon-add-child"></i>
|
||||||
|
</div>
|
||||||
|
<div class="_fd-tree-opt-btn _fd-tree-opt-danger" @click="remove(node, data)">
|
||||||
|
<i class="fc-icon icon-delete"></i>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-tree>
|
||||||
|
<el-checkbox v-if="keyValue" v-model="checked" :label="t('tableOptions.keyValue')"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
import {deepCopy} from '@form-create/utils/lib/deepextend';
|
||||||
|
import ValueInput from './computed/ValueInput.vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'TreeOptions',
|
||||||
|
components: {ValueInput},
|
||||||
|
emits: ['update:modelValue'],
|
||||||
|
props: {
|
||||||
|
modelValue: Array,
|
||||||
|
columns: Object,
|
||||||
|
keyValue: String,
|
||||||
|
},
|
||||||
|
inject: ['designer'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
value: [...deepCopy(this.modelValue || [])],
|
||||||
|
checked: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
t() {
|
||||||
|
return this.designer.setupState.t;
|
||||||
|
},
|
||||||
|
overColumns() {
|
||||||
|
if (!this.columns) {
|
||||||
|
return {
|
||||||
|
label: 'label',
|
||||||
|
value: 'value',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
label: this.columns.label || 'label',
|
||||||
|
value: this.columns.value || 'value',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
if (!this.value.length) {
|
||||||
|
this.value = [{}]
|
||||||
|
}
|
||||||
|
if (this.keyValue) {
|
||||||
|
this.checked = this.isChecked();
|
||||||
|
this.$watch('checked', (n) => {
|
||||||
|
n && this.change();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
isChecked() {
|
||||||
|
const deepCheck = (list) => {
|
||||||
|
for (let i = 0; i < list.length; i++) {
|
||||||
|
const item = list[i];
|
||||||
|
if (item[this.overColumns.label] !== item[this.overColumns.value]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (item.children && !deepCheck(item.children)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return deepCheck(this.modelValue || []);
|
||||||
|
},
|
||||||
|
tidyValue() {
|
||||||
|
const deepTidy = (list) => {
|
||||||
|
let tmp = [];
|
||||||
|
list.map(v => {
|
||||||
|
const val = v[this.keyValue];
|
||||||
|
const item = {
|
||||||
|
[this.overColumns.label]: val,
|
||||||
|
[this.overColumns.value]: val,
|
||||||
|
};
|
||||||
|
tmp.push(item)
|
||||||
|
if (v.children) {
|
||||||
|
item.children = deepTidy(v.children);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return tmp;
|
||||||
|
}
|
||||||
|
if (this.checked && this.keyValue) {
|
||||||
|
return deepTidy(this.value);
|
||||||
|
} else {
|
||||||
|
return deepCopy(this.value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
change() {
|
||||||
|
this.$emit('update:modelValue', this.tidyValue());
|
||||||
|
},
|
||||||
|
add(node) {
|
||||||
|
const parent = node.parent;
|
||||||
|
const children = parent.data.children || parent.data;
|
||||||
|
children.push({});
|
||||||
|
},
|
||||||
|
append(data) {
|
||||||
|
if (!data.children) {
|
||||||
|
data.children = [];
|
||||||
|
}
|
||||||
|
data.children.push({});
|
||||||
|
},
|
||||||
|
remove(node, data) {
|
||||||
|
const parent = node.parent;
|
||||||
|
if (parent.data.children) {
|
||||||
|
parent.data.children.splice(parent.data.children.indexOf(data), 1);
|
||||||
|
if (!parent.data.children.length) {
|
||||||
|
delete parent.data.children;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
parent.data.splice(parent.data.indexOf(data), 1);
|
||||||
|
}
|
||||||
|
this.change();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fd-tree-opt ._fd-tree-opt-btn {
|
||||||
|
height: 19px;
|
||||||
|
width: 18px;
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 20px;
|
||||||
|
padding-bottom: 1px;
|
||||||
|
float: left;
|
||||||
|
cursor: pointer;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: var(--fc-style-color-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-tree-opt-node {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-tree-opt-first {
|
||||||
|
width: 60px;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-tree-opt-last {
|
||||||
|
width: 165px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-tree-opt-last._label {
|
||||||
|
width: 175px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-tree-opt-last._label .el-input-group__append {
|
||||||
|
width: 65px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-tree-opt ._fd-tree-opt-danger {
|
||||||
|
background-color: var(--fc-style-color-3);
|
||||||
|
border-radius: 0 2px 2px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-tree-opt .el-tree-node__content {
|
||||||
|
margin-bottom: 3px;
|
||||||
|
height: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-tree-opt .el-input__inner {
|
||||||
|
border-right: 0 none;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-tree-opt .el-input-group__append {
|
||||||
|
width: 90px;
|
||||||
|
padding-right: 2px;
|
||||||
|
padding-left: 1px;
|
||||||
|
background: var(--fc-bg-color-1);
|
||||||
|
}
|
||||||
|
</style>
|
140
src/components/TypeSelect.vue
Normal file
140
src/components/TypeSelect.vue
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
<template>
|
||||||
|
<el-dropdown class="_fd-type-select" trigger="click" size="default" popper-class="_fd-type-select-pop"
|
||||||
|
:disabled="!menus.length" @command="handleCommand">
|
||||||
|
<el-tag type="success" effect="plain" disable-transitions>
|
||||||
|
<template v-if="activeRule">
|
||||||
|
{{ t('com.' + (activeRule._menu.name) + '.name') || activeRule._menu.label }} <i
|
||||||
|
class="fc-icon icon-down" v-if="menus.length"></i>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
{{
|
||||||
|
t('com.' + (customForm.config.name) + '.name') || customForm.config.label || customForm.config.name
|
||||||
|
}}
|
||||||
|
</template>
|
||||||
|
</el-tag>
|
||||||
|
<template #dropdown>
|
||||||
|
<el-dropdown-menu>
|
||||||
|
<el-dropdown-item :command="item" v-for="item in menus" :key="item.name">
|
||||||
|
<div><i class="fc-icon" :class="item.icon || 'icon-input'"></i>{{ t('com.' + (item.name) + '.name') || item.label }}</div>
|
||||||
|
</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</template>
|
||||||
|
</el-dropdown>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'TypeSelect',
|
||||||
|
inject: ['designer'],
|
||||||
|
computed: {
|
||||||
|
t() {
|
||||||
|
return this.designer.setupState.t;
|
||||||
|
},
|
||||||
|
activeRule() {
|
||||||
|
return this.designer.setupState.activeRule;
|
||||||
|
},
|
||||||
|
customForm() {
|
||||||
|
return this.designer.setupState.customForm;
|
||||||
|
},
|
||||||
|
menus() {
|
||||||
|
let menus = [];
|
||||||
|
const designer = this.designer.setupState;
|
||||||
|
if (this.activeRule) {
|
||||||
|
const name = this.activeRule._menu.name;
|
||||||
|
const switchConfig = designer.getConfig('switchType', []);
|
||||||
|
if (switchConfig === false) {
|
||||||
|
return menus;
|
||||||
|
}
|
||||||
|
let switchs = [];
|
||||||
|
switchConfig.forEach(lst => {
|
||||||
|
if (lst.indexOf(name) > -1) {
|
||||||
|
switchs.push(...lst);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
switchs = switchs.filter((key, idx) => {
|
||||||
|
return key !== name && switchs.indexOf(key) === idx;
|
||||||
|
});
|
||||||
|
if (switchs.length) {
|
||||||
|
designer.menuList.forEach(item => {
|
||||||
|
item.list.forEach(menu => {
|
||||||
|
if (switchs.indexOf(menu.name) > -1) {
|
||||||
|
menus.push(menu);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
designer.menuList.forEach(item => {
|
||||||
|
if (item.name === this.activeRule._menu.menu) {
|
||||||
|
item.list.forEach(menu => {
|
||||||
|
if (menu.name !== name) {
|
||||||
|
menus.push(menu);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return menus.filter(menu => this.designer.setupState.hiddenItem.indexOf(menu.name) === -1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleCommand(item) {
|
||||||
|
let activeRule = this.activeRule;
|
||||||
|
let rule = this.activeRule;
|
||||||
|
if (!rule._menu.inside) {
|
||||||
|
rule = rule.__fc__.parent.rule;
|
||||||
|
}
|
||||||
|
const children = rule.__fc__.parent.rule.children;
|
||||||
|
const replaceRule = this.designer.setupState.makeRule(item);
|
||||||
|
let newRule = replaceRule;
|
||||||
|
if (replaceRule.type === 'DragTool') {
|
||||||
|
newRule = replaceRule.children[0];
|
||||||
|
}
|
||||||
|
if (newRule.field && activeRule.field) {
|
||||||
|
['title', 'info', 'field', 'validate', 'computed', 'control', '$required', 'style'].forEach(k => {
|
||||||
|
newRule[k] = activeRule[k];
|
||||||
|
});
|
||||||
|
} else if (activeRule?.computed?.hidden) {
|
||||||
|
newRule.computed = {hidden: activeRule.computed.hidden}
|
||||||
|
}
|
||||||
|
if (activeRule.name) {
|
||||||
|
newRule.name = activeRule.name;
|
||||||
|
}
|
||||||
|
['name', 'wrap', 'class', 'id', 'control', 'on'].forEach(k => {
|
||||||
|
if (activeRule[k]) {
|
||||||
|
newRule[k] = activeRule[k];
|
||||||
|
}
|
||||||
|
})
|
||||||
|
children.splice(children.indexOf(rule), 1, replaceRule);
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.designer.setupState.triggerActive(newRule);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fd-type-select {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-type-select.is-disabled {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-type-select .fc-icon {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-type-select-pop {
|
||||||
|
max-height: 500px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-type-select-pop .fc-icon {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
</style>
|
19
src/components/VNode.vue
Normal file
19
src/components/VNode.vue
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<script>
|
||||||
|
import {defineComponent, Fragment, h} from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'VNode',
|
||||||
|
props: {
|
||||||
|
fn: Function
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
const vnode = this.fn();
|
||||||
|
if (Array.isArray(vnode)) {
|
||||||
|
return h(Fragment, {}, vnode)
|
||||||
|
} else {
|
||||||
|
return vnode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
246
src/components/Validate.vue
Normal file
246
src/components/Validate.vue
Normal file
@ -0,0 +1,246 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_fd-validate">
|
||||||
|
<template v-for="(item, idx) in validate">
|
||||||
|
<div class="_fd-validate-item">
|
||||||
|
<div class="_fd-validate-title">
|
||||||
|
<div>
|
||||||
|
<span>{{ idx + 1 }}</span>
|
||||||
|
{{ modes[item.mode] }}
|
||||||
|
</div>
|
||||||
|
<i class="fc-icon icon-delete-circle" @click="remove(idx)"></i>
|
||||||
|
</div>
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="getSpan(item)">
|
||||||
|
<el-form-item :label="t('validate.mode')">
|
||||||
|
<el-select v-model="item.trigger" @change="onInput">
|
||||||
|
<el-option
|
||||||
|
v-for="item in triggers"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="getSpan(item)">
|
||||||
|
<el-form-item :label="modes[item.mode]">
|
||||||
|
<template v-if="item.mode === 'pattern'">
|
||||||
|
<PatternInput v-model="item[item.mode]" @change="onInput"></PatternInput>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="item.mode === 'validator'">
|
||||||
|
<FnInput v-model="item[item.mode]" name="validator"
|
||||||
|
:args="['rule', 'value', 'callback']"
|
||||||
|
@change="onInput">{{ t('validate.modes.validator') }}
|
||||||
|
</FnInput>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<el-input-number v-model="item[item.mode]" @change="onInput"></el-input-number>
|
||||||
|
</template>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item :label="t('validate.message')">
|
||||||
|
<LanguageInput v-model="item.message" :placeholder="t('validate.requiredPlaceholder')"
|
||||||
|
@change="onInput">
|
||||||
|
</LanguageInput>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<el-dropdown trigger="click" size="default" popper-class="_fd-validate-pop" @command="handleCommand">
|
||||||
|
<el-button class="_fd-validate-btn _fd-plain-button" plain size="small">{{ t('validate.rule') }} +
|
||||||
|
</el-button>
|
||||||
|
<template #dropdown>
|
||||||
|
<el-dropdown-menu>
|
||||||
|
<el-dropdown-item :command="value" v-for="(label, value) in modes" :key="value">
|
||||||
|
<div>{{ label }}</div>
|
||||||
|
</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</template>
|
||||||
|
</el-dropdown>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
import {localeOptions} from '../utils';
|
||||||
|
import PatternInput from './computed/PatternInput.vue';
|
||||||
|
import FnInput from './FnInput.vue';
|
||||||
|
import {deepCopy} from '@form-create/utils/lib/deepextend';
|
||||||
|
import LanguageInput from './language/LanguageInput.vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'Validate',
|
||||||
|
inject: ['designer'],
|
||||||
|
emits: ['update:modelValue'],
|
||||||
|
props: {
|
||||||
|
modelValue: Array,
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
LanguageInput,
|
||||||
|
FnInput,
|
||||||
|
PatternInput,
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
modelValue(n) {
|
||||||
|
this.validate = this.parseValue(n || []);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
validate: this.parseValue(this.modelValue || []),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
t() {
|
||||||
|
return this.designer.setupState.t;
|
||||||
|
},
|
||||||
|
modes() {
|
||||||
|
const activeRule = this.designer.setupState.activeRule;
|
||||||
|
if (activeRule && activeRule._menu.subForm === 'object') {
|
||||||
|
return {
|
||||||
|
validator: this.t('validate.modes.validator'),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
min: this.t('validate.modes.min'),
|
||||||
|
max: this.t('validate.modes.max'),
|
||||||
|
len: this.t('validate.modes.len'),
|
||||||
|
pattern: this.t('validate.modes.pattern'),
|
||||||
|
validator: this.t('validate.modes.validator'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
triggers() {
|
||||||
|
return localeOptions(this.t, [
|
||||||
|
{label: 'blur', value: 'blur'},
|
||||||
|
{label: 'change', value: 'change'},
|
||||||
|
{label: 'submit', value: 'submit'},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleCommand(mode) {
|
||||||
|
this.validate.push({
|
||||||
|
transform: new Function('val', 'this.type = val == null ? \'string\' : (Array.isArray(val) ? \'array\' : (typeof val)); return val;'),
|
||||||
|
mode,
|
||||||
|
trigger: 'blur'
|
||||||
|
});
|
||||||
|
},
|
||||||
|
autoMessage(item) {
|
||||||
|
const title = this.designer.setupState.activeRule.title;
|
||||||
|
if (this.designer.setupState.activeRule) {
|
||||||
|
item.message = this.t('validate.autoRequired', {title})
|
||||||
|
this.onInput();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getSpan(item) {
|
||||||
|
return ['pattern', 'validator', 'required'].indexOf(item.mode) > -1 ? 24 : 12;
|
||||||
|
},
|
||||||
|
onInput: function () {
|
||||||
|
this.$emit('update:modelValue', this.validate.map(item => {
|
||||||
|
item = {...item};
|
||||||
|
if (!item.message) {
|
||||||
|
delete item.message;
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
remove(idx) {
|
||||||
|
this.validate.splice(idx, 1);
|
||||||
|
this.onInput();
|
||||||
|
},
|
||||||
|
parseValue(val) {
|
||||||
|
return deepCopy(val.map(v => {
|
||||||
|
if (v.validator) {
|
||||||
|
v.mode = 'validator';
|
||||||
|
}
|
||||||
|
if (!v.mode) {
|
||||||
|
Object.keys(v).forEach(k => {
|
||||||
|
if (['message', 'type', 'trigger', 'mode'].indexOf(k) < 0) {
|
||||||
|
v.mode = k;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
._fd-validate {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-validate-btn {
|
||||||
|
font-weight: 400;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-validate-pop .el-dropdown-menu__item {
|
||||||
|
width: 248px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-validate-item {
|
||||||
|
border-bottom: 1px dashed var(--fc-line-color-3);
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-validate-item .el-col-12:first-child {
|
||||||
|
padding-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-validate-item .el-col-12 + .el-col-12 {
|
||||||
|
padding-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-validate-item .el-input-number {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-validate-title {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-validate-title > div {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-validate-title > div > span {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
background: var(--fc-bg-color-3);
|
||||||
|
text-align: center;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 16px;
|
||||||
|
border-radius: 15px;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-validate-title i {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-validate-title i:hover {
|
||||||
|
color: var(--fc-style-color-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-validate .append-msg {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-validate .el-input-group__append {
|
||||||
|
padding: 0 10px;
|
||||||
|
}
|
||||||
|
</style>
|
45
src/components/Warning.vue
Normal file
45
src/components/Warning.vue
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<template>
|
||||||
|
<el-tooltip
|
||||||
|
effect="dark"
|
||||||
|
placement="top-start"
|
||||||
|
popper-class="_fd-warning-pop"
|
||||||
|
>
|
||||||
|
<template #content>
|
||||||
|
<span v-html="tooltip"></span>
|
||||||
|
</template>
|
||||||
|
<template v-if="$slots.default">
|
||||||
|
<span class="_fd-warning-text">
|
||||||
|
<slot></slot>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<i class="fc-icon icon-question"></i>
|
||||||
|
</template>
|
||||||
|
</el-tooltip>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'Warning',
|
||||||
|
props: {
|
||||||
|
tooltip: String,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fd-warning-pop {
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-warning-text {
|
||||||
|
text-decoration: underline;
|
||||||
|
text-decoration-style: dashed;
|
||||||
|
cursor: help;
|
||||||
|
}
|
||||||
|
</style>
|
431
src/components/ai/AiChat.vue
Normal file
431
src/components/ai/AiChat.vue
Normal file
@ -0,0 +1,431 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_fd-ai-chat">
|
||||||
|
<div class="_fd-ai-chat-header">
|
||||||
|
<div class="_fc-l-label">
|
||||||
|
<i class="fc-icon icon-ai bright"></i>
|
||||||
|
FormCreate {{ t('ai.name') }}
|
||||||
|
</div>
|
||||||
|
<div class="_fc-l-info">
|
||||||
|
{{ t('ai.info') }}
|
||||||
|
</div>
|
||||||
|
<div class="_fd-ai-chat-prompt">
|
||||||
|
<span>{{ t('ai.try') }}</span>
|
||||||
|
<span class="_fd-ai-chat-refresh" @click="refresh"> <i
|
||||||
|
class="fc-icon icon-refresh2"></i>{{ t('ai.change') }}</span>
|
||||||
|
</div>
|
||||||
|
<template v-for="(item) in pageData">
|
||||||
|
<div class="_fd-ai-chat-question" @click="message = item">
|
||||||
|
<span>{{ item }}</span>
|
||||||
|
<i class="fc-icon icon-down"></i>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class="_fd-ai-chat-history">
|
||||||
|
<template v-for="(item,idx) in history" :key="idx">
|
||||||
|
<div class="_fd-ai-chat-history-item" ref="chat">
|
||||||
|
<div class="_fd-ai-chat-history-chat">
|
||||||
|
<div>{{ item.message }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="_fd-ai-chat-history-status" :class="item.status || 'success'">
|
||||||
|
<div>
|
||||||
|
<template v-if="item.status === 'loading'">
|
||||||
|
<div>{{ t('ai.loading') }}</div>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="item.status === 'fail'">
|
||||||
|
<div><i class="fc-icon icon-warning"></i>{{ t('ai.fail') }}</div>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<div><i class="fc-icon icon-yes"></i>{{ t('ai.success') }}</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class="_fd-ai-chat-input">
|
||||||
|
<div class="_fd-ai-chat-clear">
|
||||||
|
<el-button size="small" text round @click="clear"><i class="fc-icon icon-delete2"></i> 清空</el-button>
|
||||||
|
</div>
|
||||||
|
<el-input type="textarea" v-model="message" :placeholder="t('ai.placeholder')" resize="none"></el-input>
|
||||||
|
<div class="fc-icon icon-suspend" v-if="chat && chat.status === 'loading'" @click="suspend"></div>
|
||||||
|
<div class="fc-icon icon-send" :class="{disabled: !message || !message.trim()}" v-else @click="send"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// +-----------------------------------------------------------------------
|
||||||
|
// | FormCreate商业版 [ 让表单设计更简单 ]
|
||||||
|
// +----------------------------------------------------------------------
|
||||||
|
// | Copyright (c) 2018~2025 https://form-create.com All rights reserved.
|
||||||
|
// +----------------------------------------------------------------------
|
||||||
|
// | Licensed FormCreate商业版并不是自由软件,未经授权不得使用、修改或移除版权信息
|
||||||
|
// +----------------------------------------------------------------------
|
||||||
|
// | Author: FormCreate Team <admin@form-create.com>
|
||||||
|
// +----------------------------------------------------------------------
|
||||||
|
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
import {message} from '../../utils/message';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'AiChat',
|
||||||
|
inject: ['designer'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
message: '',
|
||||||
|
page: 0,
|
||||||
|
limit: 3,
|
||||||
|
pageData: [],
|
||||||
|
question: [
|
||||||
|
'生成一个就诊满意度问卷表单',
|
||||||
|
'创建一个建议收集表单,包含联系人、联系邮箱、分类和建议内容',
|
||||||
|
'追加一个用户信息表单',
|
||||||
|
'添加一个标签组件,显示文本为 "Tag"',
|
||||||
|
'删除商品简介字段',
|
||||||
|
'当单选框选择 "选项1" 时,显示输入框组件',
|
||||||
|
'设置输入框为必填,并限制长度必须大于13',
|
||||||
|
'商品价格字段使用数字输入框组件',
|
||||||
|
'给输入类组件补充占位提示文本(placeholder)'
|
||||||
|
],
|
||||||
|
chat: null,
|
||||||
|
history: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
t() {
|
||||||
|
return this.designer.setupState.t;
|
||||||
|
},
|
||||||
|
api() {
|
||||||
|
return this.designer.props.config?.ai?.api || 'https://api.form-create.com/ai/v1/chat/form';
|
||||||
|
},
|
||||||
|
token() {
|
||||||
|
return this.designer.props.config?.ai?.token;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
refresh() {
|
||||||
|
if (this.page * this.limit < this.question.length) {
|
||||||
|
this.page++;
|
||||||
|
} else {
|
||||||
|
this.page = 1;
|
||||||
|
}
|
||||||
|
const startIndex = (this.page - 1) * this.limit;
|
||||||
|
const endIndex = startIndex + this.limit;
|
||||||
|
|
||||||
|
this.pageData = this.question.slice(startIndex, endIndex);
|
||||||
|
},
|
||||||
|
send() {
|
||||||
|
const message = (this.message || '').trim()
|
||||||
|
if (!message) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.chat = {
|
||||||
|
message,
|
||||||
|
status: 'loading'
|
||||||
|
};
|
||||||
|
this.history.push(this.chat);
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.chat[this.$refs.chat.length - 1].scrollIntoView({block: 'end'});
|
||||||
|
})
|
||||||
|
this.message = '';
|
||||||
|
this.fetch()
|
||||||
|
},
|
||||||
|
suspend() {
|
||||||
|
this.chat.status = 'success';
|
||||||
|
this.chat = null;
|
||||||
|
},
|
||||||
|
fetch() {
|
||||||
|
fetch(this.api, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Authorization': this.token,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
ui: 'element-plus',
|
||||||
|
message: this.chat.message,
|
||||||
|
rule: this.designer.setupState.getJson()
|
||||||
|
})
|
||||||
|
}).then((res) => {
|
||||||
|
res.json().then(res => {
|
||||||
|
if (this.chat) {
|
||||||
|
if (res.status === 200) {
|
||||||
|
this.chat.status = 'success';
|
||||||
|
this.designer.setupState.setRule(res.data.rule);
|
||||||
|
} else {
|
||||||
|
this.chat.status = 'fail';
|
||||||
|
res.message && message(res.message);
|
||||||
|
}
|
||||||
|
this.chat = null;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}).catch(() => {
|
||||||
|
this.chat = null;
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getHistory() {
|
||||||
|
const data = localStorage.getItem('fc_ai_history');
|
||||||
|
if (data) {
|
||||||
|
this.history = JSON.parse(data)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clear() {
|
||||||
|
this.history = [];
|
||||||
|
localStorage.removeItem('fc_ai_history');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.getHistory();
|
||||||
|
this.refresh();
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
if (this.$refs.chat) {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.chat[this.$refs.chat.length - 1].scrollIntoView({block: 'end'});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
unmounted() {
|
||||||
|
localStorage.setItem('fc_ai_history', JSON.stringify(this.history));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fd-ai-chat {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-ai-chat-header {
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid var(--fc-line-color-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-ai-chat-prompt {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 8px 12px 10px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-ai-chat-prompt .fc-icon {
|
||||||
|
font-size: 12px;
|
||||||
|
margin-right: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-ai-chat-refresh {
|
||||||
|
color: var(--fc-style-color-1);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-ai-chat-question {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
background: var(--fc-bg-color-2);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 7px 10px;
|
||||||
|
margin: 0 12px 12px;
|
||||||
|
color: var(--fc-text-color-2);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-ai-chat-question + ._fd-ai-chat-question {
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-ai-chat-question .fc-icon {
|
||||||
|
margin-left: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-ai-chat-question .icon-down:before {
|
||||||
|
display: inline-block;
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-ai-chat-history {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
margin-top: 12px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-ai-chat-history-chat {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-ai-chat-history-chat > div {
|
||||||
|
background: var(--fc-style-bg-color-1);
|
||||||
|
border-radius: 6px 0px 6px 6px;
|
||||||
|
color: var(--fc-style-color-1);
|
||||||
|
padding: 10px;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
max-width: 70%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-ai-chat-history-status {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
._fd-ai-chat-history-status > div {
|
||||||
|
position: relative;
|
||||||
|
background: var(--fc-bg-color-2);
|
||||||
|
border-radius: 0px 6px 6px 6px;
|
||||||
|
color: var(--fc-text-color-2);
|
||||||
|
padding: 10px;
|
||||||
|
max-width: 70%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
._fd-ai-chat-history-status > div > div {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-ai-chat-history-status.loading > div:after {
|
||||||
|
content: '';
|
||||||
|
background: linear-gradient(0deg, var(--fc-style-color-1) 0%, var(--fc-style-color-4) 100%);
|
||||||
|
padding: 1px;
|
||||||
|
position: absolute;
|
||||||
|
top: -2px;
|
||||||
|
left: -2px;
|
||||||
|
bottom: -2px;
|
||||||
|
right: -2px;
|
||||||
|
animation: rotate-animation 3s linear infinite;
|
||||||
|
filter: blur(5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-ai-chat-history-status.loading > div {
|
||||||
|
padding: 1px;
|
||||||
|
border-radius: 0px 6px 6px 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-ai-chat-history-status.loading > div > div {
|
||||||
|
background-color: var(--fc-bg-color-1);
|
||||||
|
border-radius: 0px 6px 6px 6px;
|
||||||
|
padding: 7px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-ai-chat-history-status.success > div {
|
||||||
|
background: var(--fc-style-bg-color-2);
|
||||||
|
color: var(--fc-style-color-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-ai-chat-history-status .fc-icon {
|
||||||
|
width: 15px;
|
||||||
|
height: 15px;
|
||||||
|
background: var(--fc-style-color-2);
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 15px;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-right: 5px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-ai-chat-history-status.fail > div {
|
||||||
|
background: var(--fc-style-bg-color-3);
|
||||||
|
color: var(--fc-style-color-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-ai-chat-history-status.fail .fc-icon {
|
||||||
|
background: var(--fc-style-color-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-ai-chat-input {
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 6px;
|
||||||
|
position: relative;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-ai-chat-clear {
|
||||||
|
text-align: right;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-ai-chat-clear .el-button {
|
||||||
|
padding: 0;
|
||||||
|
margin-right: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-ai-chat-clear .fc-icon {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-ai-chat-input .el-textarea {
|
||||||
|
position: relative;
|
||||||
|
padding: 1px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
height: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-ai-chat-input .el-textarea:before {
|
||||||
|
content: "";
|
||||||
|
background: linear-gradient(135deg, var(--fc-style-color-1), var(--fc-style-color-4));
|
||||||
|
border-radius: 6px;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-ai-chat-input .el-textarea__inner {
|
||||||
|
height: 100%;
|
||||||
|
resize: none;
|
||||||
|
box-shadow: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: var(--fc-bg-color-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-ai-chat-input > .fc-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 15px;
|
||||||
|
position: absolute;
|
||||||
|
right: 12px;
|
||||||
|
bottom: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
background: var(--fc-style-color-1);;
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-ai-chat-input .icon-suspend {
|
||||||
|
background: linear-gradient(90deg, var(--fc-style-color-1) 0%, var(--fc-style-color-4) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-ai-chat-input .disabled {
|
||||||
|
background: var(--fc-text-color-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rotate-animation {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
46
src/components/aide/AudioBox.vue
Normal file
46
src/components/aide/AudioBox.vue
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<template>
|
||||||
|
<audio
|
||||||
|
:key="src"
|
||||||
|
:controls="controls"
|
||||||
|
:autoplay="autoplay"
|
||||||
|
:loop="loop"
|
||||||
|
:preload="preload"
|
||||||
|
:muted="muted"
|
||||||
|
@pause="$emit('pause', $event)"
|
||||||
|
@play="$emit('play', $event)"
|
||||||
|
@ended="$emit('ended', $event)"
|
||||||
|
>
|
||||||
|
<source
|
||||||
|
:src="src"
|
||||||
|
:type="type"
|
||||||
|
/>
|
||||||
|
Your browser does not support the audio element.
|
||||||
|
</audio>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'AudioBox',
|
||||||
|
emits: ['pause', 'play', 'ended'],
|
||||||
|
data() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
src: String,
|
||||||
|
type: String,
|
||||||
|
controls: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
autoplay: Boolean,
|
||||||
|
loop: Boolean,
|
||||||
|
preload: {
|
||||||
|
type: String,
|
||||||
|
default: 'auto',
|
||||||
|
},
|
||||||
|
muted: Boolean,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
60
src/components/aide/BarCodeBox.vue
Normal file
60
src/components/aide/BarCodeBox.vue
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<template>
|
||||||
|
<img class="_fc-barcode" ref="bar"/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
import JsBarcode from 'jsbarcode'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'BarCodeBox',
|
||||||
|
data() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
value: String,
|
||||||
|
format: String,
|
||||||
|
displayValue: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
fontSize: Number,
|
||||||
|
textPosition: String,
|
||||||
|
textAlign: String,
|
||||||
|
textMargin: Number,
|
||||||
|
width: {
|
||||||
|
type: Number,
|
||||||
|
default: 2,
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: Number,
|
||||||
|
default: 50,
|
||||||
|
},
|
||||||
|
background: String,
|
||||||
|
lineColor: String,
|
||||||
|
},
|
||||||
|
methods: {},
|
||||||
|
computed: {},
|
||||||
|
components: {},
|
||||||
|
watch: {
|
||||||
|
'$props': {
|
||||||
|
handler() {
|
||||||
|
const value = this.value;
|
||||||
|
const options = {};
|
||||||
|
Object.keys(this.$props).forEach((key) => {
|
||||||
|
if (this.$props[key] != null && this.$props[key] !== '') {
|
||||||
|
options[key] = this.$props[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
delete options.value;
|
||||||
|
delete options.formCreateInject;
|
||||||
|
this.$nextTick(() => {
|
||||||
|
JsBarcode(this.$refs.bar, value || '', options);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
69
src/components/aide/FcTitle.vue
Normal file
69
src/components/aide/FcTitle.vue
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_fc-title" :class="size || 'h2'" :style="textStyle">
|
||||||
|
{{ title }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'FcTitle',
|
||||||
|
data() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
title: String,
|
||||||
|
size: String,
|
||||||
|
align: String,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
textStyle() {
|
||||||
|
return {
|
||||||
|
textAlign: this.align || 'left',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
._fc-title {
|
||||||
|
width: 100%;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-top: 1em;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-title.h1, ._fc-title.h2 {
|
||||||
|
padding-bottom: .3em;
|
||||||
|
border-bottom: 1px solid #eee
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-title.h1 {
|
||||||
|
font-size: 32px;
|
||||||
|
line-height: 1.2
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-title.h2 {
|
||||||
|
font-size: 24px;
|
||||||
|
line-height: 1.225
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-title.h3 {
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 1.43
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-title.h4 {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-title.h5 {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-title.h6 {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
</style>
|
30
src/components/aide/IframeBox.vue
Normal file
30
src/components/aide/IframeBox.vue
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<template>
|
||||||
|
<iframe
|
||||||
|
class="_fc-iframe-box"
|
||||||
|
:src="src"
|
||||||
|
frameborder="0"
|
||||||
|
@load="$emit('load', $event)"
|
||||||
|
></iframe>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'IframeBox',
|
||||||
|
emits: ['load'],
|
||||||
|
data() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
src: String,
|
||||||
|
loading: String,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fc-iframe-box {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
785
src/components/aide/Markdown.vue
Normal file
785
src/components/aide/Markdown.vue
Normal file
@ -0,0 +1,785 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_fc-markdown" v-html="html">
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent, markRaw} from 'vue';
|
||||||
|
import {Marked} from 'marked';
|
||||||
|
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'FcMarkdown',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
marked: markRaw(new Marked()),
|
||||||
|
html: '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
content: String,
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
content: {
|
||||||
|
handler() {
|
||||||
|
this.html = this.marked.parse(this.content || '');
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fc-markdown {
|
||||||
|
color-scheme: light;
|
||||||
|
-ms-text-size-adjust: 100%;
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.5;
|
||||||
|
word-wrap: break-word;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown details,
|
||||||
|
._fc-markdown figcaption,
|
||||||
|
._fc-markdown figure {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown summary {
|
||||||
|
display: list-item;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown [hidden] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown a {
|
||||||
|
background-color: transparent;
|
||||||
|
color: #0969da;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown abbr[title] {
|
||||||
|
border-bottom: none;
|
||||||
|
-webkit-text-decoration: underline dotted;
|
||||||
|
text-decoration: underline dotted;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown b,
|
||||||
|
._fc-markdown strong {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown dfn {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown h1 {
|
||||||
|
margin: .67em 0;
|
||||||
|
font-weight: 600;
|
||||||
|
padding-bottom: .3em;
|
||||||
|
font-size: 2em;
|
||||||
|
border-bottom: 1px solid #d1d9e0b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown mark {
|
||||||
|
background-color: #fff8c5;
|
||||||
|
color: #1f2328;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown small {
|
||||||
|
font-size: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown sub,
|
||||||
|
._fc-markdown sup {
|
||||||
|
font-size: 75%;
|
||||||
|
line-height: 0;
|
||||||
|
position: relative;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown sub {
|
||||||
|
bottom: -0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown sup {
|
||||||
|
top: -0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown img {
|
||||||
|
border-style: none;
|
||||||
|
max-width: 100%;
|
||||||
|
box-sizing: content-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown code,
|
||||||
|
._fc-markdown kbd,
|
||||||
|
._fc-markdown pre,
|
||||||
|
._fc-markdown samp {
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown figure {
|
||||||
|
margin: 1em 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown hr {
|
||||||
|
box-sizing: content-box;
|
||||||
|
overflow: hidden;
|
||||||
|
background: transparent;
|
||||||
|
border-bottom: 1px solid #d1d9e0b3;
|
||||||
|
height: .25em;
|
||||||
|
padding: 0;
|
||||||
|
margin: 1.5rem 0;
|
||||||
|
background-color: #d1d9e0;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown input {
|
||||||
|
font: inherit;
|
||||||
|
margin: 0;
|
||||||
|
overflow: visible;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown [type=button],
|
||||||
|
._fc-markdown [type=reset],
|
||||||
|
._fc-markdown [type=submit] {
|
||||||
|
-webkit-appearance: button;
|
||||||
|
appearance: button;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown [type=checkbox],
|
||||||
|
._fc-markdown [type=radio] {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown [type=number]::-webkit-inner-spin-button,
|
||||||
|
._fc-markdown [type=number]::-webkit-outer-spin-button {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown [type=search]::-webkit-search-cancel-button,
|
||||||
|
._fc-markdown [type=search]::-webkit-search-decoration {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown ::-webkit-input-placeholder {
|
||||||
|
color: inherit;
|
||||||
|
opacity: .54;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown ::-webkit-file-upload-button {
|
||||||
|
-webkit-appearance: button;
|
||||||
|
appearance: button;
|
||||||
|
font: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown ::placeholder {
|
||||||
|
color: #59636e;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown hr::before {
|
||||||
|
display: table;
|
||||||
|
content: "";
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown hr::after {
|
||||||
|
display: table;
|
||||||
|
clear: both;
|
||||||
|
content: "";
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown table {
|
||||||
|
border-spacing: 0;
|
||||||
|
border-collapse: collapse;
|
||||||
|
display: block;
|
||||||
|
width: max-content;
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
font-variant: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown td,
|
||||||
|
._fc-markdown th {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown details summary {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown a:focus,
|
||||||
|
._fc-markdown [role=button]:focus,
|
||||||
|
._fc-markdown input[type=radio]:focus,
|
||||||
|
._fc-markdown input[type=checkbox]:focus {
|
||||||
|
outline: 2px solid #0969da;
|
||||||
|
outline-offset: -2px;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown a:focus:not(:focus-visible),
|
||||||
|
._fc-markdown [role=button]:focus:not(:focus-visible),
|
||||||
|
._fc-markdown input[type=radio]:focus:not(:focus-visible),
|
||||||
|
._fc-markdown input[type=checkbox]:focus:not(:focus-visible) {
|
||||||
|
outline: solid 1px transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown a:focus-visible,
|
||||||
|
._fc-markdown [role=button]:focus-visible,
|
||||||
|
._fc-markdown input[type=radio]:focus-visible,
|
||||||
|
._fc-markdown input[type=checkbox]:focus-visible {
|
||||||
|
outline: 2px solid #0969da;
|
||||||
|
outline-offset: -2px;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown a:not([class]):focus,
|
||||||
|
._fc-markdown a:not([class]):focus-visible,
|
||||||
|
._fc-markdown input[type=radio]:focus,
|
||||||
|
._fc-markdown input[type=radio]:focus-visible,
|
||||||
|
._fc-markdown input[type=checkbox]:focus,
|
||||||
|
._fc-markdown input[type=checkbox]:focus-visible {
|
||||||
|
outline-offset: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown kbd {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.25rem;
|
||||||
|
font: 11px ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;
|
||||||
|
line-height: 10px;
|
||||||
|
color: #1f2328;
|
||||||
|
vertical-align: middle;
|
||||||
|
background-color: #f6f8fa;
|
||||||
|
border: solid 1px #d1d9e0b3;
|
||||||
|
border-bottom-color: #d1d9e0b3;
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: inset 0 -1px 0 #d1d9e0b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown h1,
|
||||||
|
._fc-markdown h2,
|
||||||
|
._fc-markdown h3,
|
||||||
|
._fc-markdown h4,
|
||||||
|
._fc-markdown h5,
|
||||||
|
._fc-markdown h6 {
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.25;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown h2 {
|
||||||
|
font-weight: 600;
|
||||||
|
padding-bottom: .3em;
|
||||||
|
font-size: 1.5em;
|
||||||
|
border-bottom: 1px solid #d1d9e0b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown h3 {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 1.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown h4 {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown h5 {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: .875em;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown h6 {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: .85em;
|
||||||
|
color: #59636e;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown p {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown blockquote {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0 1em;
|
||||||
|
color: #59636e;
|
||||||
|
border-left: .25em solid #d1d9e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown ul,
|
||||||
|
._fc-markdown ol {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
padding-left: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown ol ol,
|
||||||
|
._fc-markdown ul ol {
|
||||||
|
list-style-type: lower-roman;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown ul ul ol,
|
||||||
|
._fc-markdown ul ol ol,
|
||||||
|
._fc-markdown ol ul ol,
|
||||||
|
._fc-markdown ol ol ol {
|
||||||
|
list-style-type: lower-alpha;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown dd {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown tt,
|
||||||
|
._fc-markdown code,
|
||||||
|
._fc-markdown samp {
|
||||||
|
font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown pre {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
word-wrap: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown input::-webkit-outer-spin-button,
|
||||||
|
._fc-markdown input::-webkit-inner-spin-button {
|
||||||
|
margin: 0;
|
||||||
|
appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown::before {
|
||||||
|
display: table;
|
||||||
|
content: "";
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown::after {
|
||||||
|
display: table;
|
||||||
|
clear: both;
|
||||||
|
content: "";
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown > *:first-child {
|
||||||
|
margin-top: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown > *:last-child {
|
||||||
|
margin-bottom: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown a:not([href]) {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown p,
|
||||||
|
._fc-markdown blockquote,
|
||||||
|
._fc-markdown ul,
|
||||||
|
._fc-markdown ol,
|
||||||
|
._fc-markdown dl,
|
||||||
|
._fc-markdown table,
|
||||||
|
._fc-markdown pre,
|
||||||
|
._fc-markdown details {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown blockquote > :first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown blockquote > :last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown h1:hover .anchor,
|
||||||
|
._fc-markdown h2:hover .anchor,
|
||||||
|
._fc-markdown h3:hover .anchor,
|
||||||
|
._fc-markdown h4:hover .anchor,
|
||||||
|
._fc-markdown h5:hover .anchor,
|
||||||
|
._fc-markdown h6:hover .anchor {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown h1 tt,
|
||||||
|
._fc-markdown h1 code,
|
||||||
|
._fc-markdown h2 tt,
|
||||||
|
._fc-markdown h2 code,
|
||||||
|
._fc-markdown h3 tt,
|
||||||
|
._fc-markdown h3 code,
|
||||||
|
._fc-markdown h4 tt,
|
||||||
|
._fc-markdown h4 code,
|
||||||
|
._fc-markdown h5 tt,
|
||||||
|
._fc-markdown h5 code,
|
||||||
|
._fc-markdown h6 tt,
|
||||||
|
._fc-markdown h6 code {
|
||||||
|
padding: 0 .2em;
|
||||||
|
font-size: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown summary h1,
|
||||||
|
._fc-markdown summary h2,
|
||||||
|
._fc-markdown summary h3,
|
||||||
|
._fc-markdown summary h4,
|
||||||
|
._fc-markdown summary h5,
|
||||||
|
._fc-markdown summary h6 {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown summary h1,
|
||||||
|
._fc-markdown summary h2 {
|
||||||
|
padding-bottom: 0;
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown ul.no-list,
|
||||||
|
._fc-markdown ol.no-list {
|
||||||
|
padding: 0;
|
||||||
|
list-style-type: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown ol[type="a s"] {
|
||||||
|
list-style-type: lower-alpha;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown ol[type="A s"] {
|
||||||
|
list-style-type: upper-alpha;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown ol[type="i s"] {
|
||||||
|
list-style-type: lower-roman;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown ol[type="I s"] {
|
||||||
|
list-style-type: upper-roman;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown ol[type="1"] {
|
||||||
|
list-style-type: decimal;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown div > ol:not([type]) {
|
||||||
|
list-style-type: decimal;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown ul ul,
|
||||||
|
._fc-markdown ul ol,
|
||||||
|
._fc-markdown ol ol,
|
||||||
|
._fc-markdown ol ul {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown li > p {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown li + li {
|
||||||
|
margin-top: .25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown dl {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown dl dt {
|
||||||
|
padding: 0;
|
||||||
|
margin-top: 1rem;
|
||||||
|
font-size: 1em;
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown dl dd {
|
||||||
|
padding: 0 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown table th {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown table th,
|
||||||
|
._fc-markdown table td {
|
||||||
|
padding: 6px 13px;
|
||||||
|
border: 1px solid #d1d9e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown table td > :last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown table tr {
|
||||||
|
background-color: #ffffff;
|
||||||
|
border-top: 1px solid #d1d9e0b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown table tr:nth-child(2n) {
|
||||||
|
background-color: #f6f8fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown table img {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown img[align=right] {
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown img[align=left] {
|
||||||
|
padding-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown .emoji {
|
||||||
|
max-width: none;
|
||||||
|
vertical-align: text-top;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown span.frame {
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown span.frame > span {
|
||||||
|
display: block;
|
||||||
|
float: left;
|
||||||
|
width: auto;
|
||||||
|
padding: 7px;
|
||||||
|
margin: 13px 0 0;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid #d1d9e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown span.frame span img {
|
||||||
|
display: block;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown span.frame span span {
|
||||||
|
display: block;
|
||||||
|
padding: 5px 0 0;
|
||||||
|
clear: both;
|
||||||
|
color: #1f2328;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown span.align-center {
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown span.align-center > span {
|
||||||
|
display: block;
|
||||||
|
margin: 13px auto 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown span.align-center span img {
|
||||||
|
margin: 0 auto;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown span.align-right {
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown span.align-right > span {
|
||||||
|
display: block;
|
||||||
|
margin: 13px 0 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown span.align-right span img {
|
||||||
|
margin: 0;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown span.float-left {
|
||||||
|
display: block;
|
||||||
|
float: left;
|
||||||
|
margin-right: 13px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown span.float-left span {
|
||||||
|
margin: 13px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown span.float-right {
|
||||||
|
display: block;
|
||||||
|
float: right;
|
||||||
|
margin-left: 13px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown span.float-right > span {
|
||||||
|
display: block;
|
||||||
|
margin: 13px auto 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown code,
|
||||||
|
._fc-markdown tt {
|
||||||
|
padding: .2em .4em;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 85%;
|
||||||
|
white-space: break-spaces;
|
||||||
|
background-color: #818b981f;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown code br,
|
||||||
|
._fc-markdown tt br {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown del code {
|
||||||
|
text-decoration: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown samp {
|
||||||
|
font-size: 85%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown pre code {
|
||||||
|
font-size: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown pre > code {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
word-break: normal;
|
||||||
|
white-space: pre;
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown .highlight {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown .highlight pre {
|
||||||
|
margin-bottom: 0;
|
||||||
|
word-break: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown .highlight pre,
|
||||||
|
._fc-markdown pre {
|
||||||
|
padding: 1rem;
|
||||||
|
overflow: auto;
|
||||||
|
font-size: 85%;
|
||||||
|
line-height: 1.45;
|
||||||
|
color: #1f2328;
|
||||||
|
background-color: #f6f8fa;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown pre code,
|
||||||
|
._fc-markdown pre tt {
|
||||||
|
display: inline;
|
||||||
|
max-width: auto;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
overflow: visible;
|
||||||
|
line-height: inherit;
|
||||||
|
word-wrap: normal;
|
||||||
|
background-color: transparent;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown [data-footnote-ref]::before {
|
||||||
|
content: "[";
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown [data-footnote-ref]::after {
|
||||||
|
content: "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown [role=button]:focus:not(:focus-visible),
|
||||||
|
._fc-markdown [role=tabpanel][tabindex="0"]:focus:not(:focus-visible),
|
||||||
|
._fc-markdown button:focus:not(:focus-visible),
|
||||||
|
._fc-markdown summary:focus:not(:focus-visible),
|
||||||
|
._fc-markdown a:focus:not(:focus-visible) {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown [tabindex="0"]:focus:not(:focus-visible),
|
||||||
|
._fc-markdown details-dialog:focus:not(:focus-visible) {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown g-emoji {
|
||||||
|
display: inline-block;
|
||||||
|
min-width: 1ch;
|
||||||
|
font-family: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||||
|
font-size: 1em;
|
||||||
|
font-style: normal !important;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1;
|
||||||
|
vertical-align: -0.075em;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown g-emoji img {
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown .task-list-item {
|
||||||
|
list-style-type: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown .task-list-item label {
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown .task-list-item.enabled label {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown .task-list-item + .task-list-item {
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown .task-list-item .handle {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown .task-list-item-checkbox {
|
||||||
|
margin: 0 .2em .25em -1.4em;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown ul:dir(rtl) .task-list-item-checkbox {
|
||||||
|
margin: 0 -1.6em .25em .2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-markdown ol:dir(rtl) .task-list-item-checkbox {
|
||||||
|
margin: 0 -1.6em .25em .2em;
|
||||||
|
}
|
||||||
|
</style>
|
61
src/components/aide/QrCodeBox.vue
Normal file
61
src/components/aide/QrCodeBox.vue
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_fc-qrcode" ref="qr"/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent, markRaw} from 'vue';
|
||||||
|
import QRCodeStyling from 'qr-code-styling';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'QrCodeBox',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
qrcode: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
data: String,
|
||||||
|
image: String,
|
||||||
|
width: Number,
|
||||||
|
height: Number,
|
||||||
|
circleType: String,
|
||||||
|
circleColor: String,
|
||||||
|
},
|
||||||
|
methods: {},
|
||||||
|
computed: {},
|
||||||
|
components: {},
|
||||||
|
watch: {
|
||||||
|
'$props': {
|
||||||
|
handler() {
|
||||||
|
const options = {
|
||||||
|
dotsOptions: {}
|
||||||
|
};
|
||||||
|
Object.keys(this.$props).forEach((key) => {
|
||||||
|
if (this.$props[key] != null && this.$props[key] !== '') {
|
||||||
|
options[key] = this.$props[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
delete options.formCreateInject;
|
||||||
|
if (options.circleType) {
|
||||||
|
options.dotsOptions.type = options.circleType;
|
||||||
|
}
|
||||||
|
if (options.circleColor) {
|
||||||
|
options.dotsOptions.color = options.circleColor;
|
||||||
|
}
|
||||||
|
delete options.circleColor;
|
||||||
|
delete options.circleType;
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (this.qrcode) {
|
||||||
|
this.qrcode.update(options);
|
||||||
|
} else {
|
||||||
|
this.qrcode = markRaw(new QRCodeStyling(options));
|
||||||
|
this.qrcode.append(this.$refs.qr);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
82
src/components/aide/VideoBox.vue
Normal file
82
src/components/aide/VideoBox.vue
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
<template>
|
||||||
|
<video
|
||||||
|
ref="video"
|
||||||
|
class="_fc-video-box"
|
||||||
|
:controls="controls"
|
||||||
|
:loop="loop"
|
||||||
|
@pause="$emit('pause', $event)"
|
||||||
|
@play="$emit('play', $event)"
|
||||||
|
@ended="$emit('ended', $event)"
|
||||||
|
></video>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import loadjs from '../../utils/loadjs/loadjs';
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'VideoBox',
|
||||||
|
emits: ['pause', 'play', 'ended', 'error'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
player: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
src: String,
|
||||||
|
type: String,
|
||||||
|
controls: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
autoplay: Boolean,
|
||||||
|
isLive: Boolean,
|
||||||
|
withCredentials: Boolean,
|
||||||
|
loop: Boolean
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
src: {
|
||||||
|
handler: function () {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
loadjs.ready('mpegts', () => {
|
||||||
|
const video = this.$refs.video;
|
||||||
|
const player = window.mpegts.createPlayer({
|
||||||
|
isLive: this.isLive,
|
||||||
|
type: this.type,
|
||||||
|
url: this.src,
|
||||||
|
});
|
||||||
|
|
||||||
|
player.attachMediaElement(video);
|
||||||
|
|
||||||
|
player.on('error', (e) => {
|
||||||
|
this.$emit('error', e);
|
||||||
|
});
|
||||||
|
|
||||||
|
player.load();
|
||||||
|
if (this.autoplay) {
|
||||||
|
player.play().catch((e) => {
|
||||||
|
this.$emit('error', e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.player = player;
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
if (window.mpegts) {
|
||||||
|
loadjs.done('mpegts');
|
||||||
|
} else if (!loadjs.isDefined('mpegts')) {
|
||||||
|
loadjs.loadNpm('mpegts.js@1.8.0/dist/mpegts.js', 'mpegts');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fc-video-box {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
23
src/components/cell/Cell.vue
Normal file
23
src/components/cell/Cell.vue
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="cell" class="_fc-cell">
|
||||||
|
<slot name="default"></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'FcCell',
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fc-cell {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-cell .el-input-number, ._fc-cell .el-select, ._fc-cell .el-slider, ._fc-cell .el-cascader, ._fc-cell .el-date-editor {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
84
src/components/cell/CellView.vue
Normal file
84
src/components/cell/CellView.vue
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="cell" class="_fd-cell" :class="{'is-new': isNew}" :style="style">
|
||||||
|
<div v-bind="$attrs" style="height: 100%;width: 100%;">
|
||||||
|
<slot name="default"></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent, nextTick} from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'FcCell',
|
||||||
|
inheritAttrs: false,
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isNew: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
style() {
|
||||||
|
const style = this.$attrs.style || {};
|
||||||
|
const css = {
|
||||||
|
'--fc-cell-display': style.display || 'block',
|
||||||
|
'--fc-cell-flexDirection': style.flexDirection || 'inherit',
|
||||||
|
'--fc-cell-flexWrap': style.flexWrap || 'inherit',
|
||||||
|
'--fc-cell-alignContent': style.alignContent || 'inherit',
|
||||||
|
'--fc-cell-justifyContent': style.justifyContent || 'inherit',
|
||||||
|
'--fc-cell-alignItems': style.alignItems || 'inherit',
|
||||||
|
}
|
||||||
|
if (style.height) {
|
||||||
|
css.height = style.height || 'auto';
|
||||||
|
}
|
||||||
|
if (style.width) {
|
||||||
|
css.width = style.width || 'auto';
|
||||||
|
}
|
||||||
|
return css;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.isNew = this.$el.parentNode.classList.contains('_fd-drag-item');
|
||||||
|
if (this.isNew) {
|
||||||
|
this.$watch('$attrs.style.width', (n) => {
|
||||||
|
nextTick(() => {
|
||||||
|
this.$el.parentNode.style.width = n ? n : '100%';
|
||||||
|
})
|
||||||
|
}, {
|
||||||
|
immediate: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fd-cell {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-cell.is-new {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-cell > div {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-cell > div > ._fd-drag-tool > ._fd-drag-box, ._fd-cell > div > ._fd-drag-tool.is-inline {
|
||||||
|
display: var(--fc-cell-display) !important;
|
||||||
|
flex-direction: var(--fc-cell-flexDirection) !important;
|
||||||
|
flex-wrap: var(--fc-cell-flexWrap) !important;
|
||||||
|
align-content: var(--fc-cell-alignContent) !important;
|
||||||
|
justify-content: var(--fc-cell-justifyContent) !important;
|
||||||
|
align-items: var(--fc-cell-alignItems) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-cell > div > ._fd-drag-tool > ._fd-drag-box > ._fd-drag-item:has( > ._fd-drag-tool >.el-col-24, >.el-col-24), ._fd-cell > div > ._fd-drag-item:has( > ._fd-drag-tool >.el-col-24, >.el-col-24) {
|
||||||
|
flex: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-cell .el-input-number, ._fd-cell .el-select, ._fd-cell .el-slider, ._fd-cell .el-cascader, ._fd-cell .el-date-editor {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
684
src/components/computed/ComputedConfig.vue
Normal file
684
src/components/computed/ComputedConfig.vue
Normal file
@ -0,0 +1,684 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_fd-computed">
|
||||||
|
<el-badge type="warning" is-dot :hidden="!configured">
|
||||||
|
<el-button class="_fd-plain-button" plain @click="visible=true" size="small">{{ btn || title }}</el-button>
|
||||||
|
</el-badge>
|
||||||
|
<el-dialog class="_fd-comp-dialog _fd-config-dialog" :title="title" v-model="visible" destroy-on-close
|
||||||
|
:close-on-click-modal="false" append-to-body
|
||||||
|
width="980px">
|
||||||
|
<el-tabs class="_fd-preview-tabs" v-model="status" v-if="type !== 'value'">
|
||||||
|
<el-tab-pane name="condition">
|
||||||
|
<template #label>
|
||||||
|
{{ type === 'linkage' ? t('computed.value.title') : t('computed.condition') }}
|
||||||
|
<Warning :tooltip="t('warning.computedCondition')"></Warning>
|
||||||
|
</template>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane name="computed">
|
||||||
|
<template #label>
|
||||||
|
{{ t('computed.name') }}
|
||||||
|
<Warning :tooltip="t('warning.computedFormula')"></Warning>
|
||||||
|
</template>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
|
||||||
|
<el-container class="_fd-comp-condition" v-if="status === 'condition'">
|
||||||
|
<el-main>
|
||||||
|
<div class="_fd-comp-title">
|
||||||
|
{{ t('computed.setting') }}
|
||||||
|
</div>
|
||||||
|
<ConditionGroup v-if="visible" v-model="condition" ref="condition"/>
|
||||||
|
<template v-if="type === 'linkage'">
|
||||||
|
<div class="_fd-comp-title" style="margin-top: 30px;">
|
||||||
|
{{ t('computed.linkage.trigger') }}
|
||||||
|
</div>
|
||||||
|
<div class="_fd-comp-linkage">
|
||||||
|
{{ t('computed.linkage.info.0') }}
|
||||||
|
<RuleSelect v-model="linkage" size="small" onlyField valueType="field"
|
||||||
|
clearable></RuleSelect>
|
||||||
|
{{ t('computed.linkage.info.1') }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<div class="_fd-comp-title" style="margin-top: 30px;">
|
||||||
|
{{ t('computed.invert') }}
|
||||||
|
</div>
|
||||||
|
<el-radio-group v-model="invert">
|
||||||
|
<el-radio :value="true">{{ invertLabel }}</el-radio>
|
||||||
|
<el-radio :value="false">{{ validLabel }}</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</template>
|
||||||
|
</el-main>
|
||||||
|
</el-container>
|
||||||
|
|
||||||
|
<el-container class="_fd-comp-con" v-else>
|
||||||
|
<el-aside>
|
||||||
|
<el-tree
|
||||||
|
ref="treeRef"
|
||||||
|
:data="treeInfo"
|
||||||
|
:default-expanded-keys="expandedKeys"
|
||||||
|
:expand-on-click-node="false"
|
||||||
|
node-key="id"
|
||||||
|
:indent="10"
|
||||||
|
@nodeClick="nodeClick"
|
||||||
|
>
|
||||||
|
<template #default="{ node, data }">
|
||||||
|
<div class="_fd-comp-node"
|
||||||
|
:class="{disabled: data.disabled}"
|
||||||
|
@mouseover="nodeOver(data)">
|
||||||
|
<div>
|
||||||
|
<template v-if="data.rule">
|
||||||
|
<span v-if="data.rule._menu.subForm === 'object'"
|
||||||
|
class="_group">{ {{ t('props.group') }} }</span>
|
||||||
|
<span v-if="data.rule._menu.subForm === 'array'"
|
||||||
|
class="_subform">[ {{ t('props.collection') }} ]</span>
|
||||||
|
</template>
|
||||||
|
<span>{{
|
||||||
|
data.rule ? getTitle(data.rule) : (data.label || '').trim()
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
<span class="_fd-comp-id" v-if="data.rule" @click.stop="setField(data)">
|
||||||
|
ID
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-tree>
|
||||||
|
</el-aside>
|
||||||
|
<el-main>
|
||||||
|
<el-container class="_fd-comp-r">
|
||||||
|
<el-header class="_fd-comp-head" height="40px">
|
||||||
|
{{ name || title }}
|
||||||
|
</el-header>
|
||||||
|
<el-main>
|
||||||
|
<div ref="editor" class="_fd-comp-script" v-if="visible"></div>
|
||||||
|
</el-main>
|
||||||
|
<div class="_fd-comp-info" v-if="formulaInfo || err">
|
||||||
|
<div v-if="formulaInfo">{{ t('computed.formulaInfo') }}: {{ formulaInfo }}</div>
|
||||||
|
<div v-if="formulaExample">{{ t('computed.formulaExample') }}: {{ formulaExample }}</div>
|
||||||
|
<div v-if="err" style="color: #f56c6c;">{{ t('validate.message') }}: {{ err }}</div>
|
||||||
|
</div>
|
||||||
|
</el-container>
|
||||||
|
</el-main>
|
||||||
|
</el-container>
|
||||||
|
<template #footer>
|
||||||
|
<div>
|
||||||
|
<el-button @click="visible=false" size="default">{{ t('props.cancel') }}</el-button>
|
||||||
|
<el-button type="primary" @click="submit" size="default">{{
|
||||||
|
t('props.ok')
|
||||||
|
}}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import 'codemirror/lib/codemirror.css';
|
||||||
|
import CodeMirror from 'codemirror/lib/codemirror';
|
||||||
|
import {defineComponent, markRaw} from 'vue';
|
||||||
|
import {formulaInfo, formulaTree} from '../../utils/formulas';
|
||||||
|
import {addAutoKeyMap, escapeRegExp} from '../../utils';
|
||||||
|
import ConditionGroup from './ConditionGroup.vue';
|
||||||
|
import is from '@form-create/utils/lib/type';
|
||||||
|
import {deepCopy} from '@form-create/utils/lib/deepextend';
|
||||||
|
import RuleSelect from '../RuleSelect.vue';
|
||||||
|
import Warning from '../Warning.vue';
|
||||||
|
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'ComputedConfig',
|
||||||
|
components: {Warning, RuleSelect, ConditionGroup},
|
||||||
|
props: {
|
||||||
|
modelValue: [String, Object, Array],
|
||||||
|
type: String,
|
||||||
|
title: String,
|
||||||
|
name: String,
|
||||||
|
btn: String,
|
||||||
|
validLabel: String,
|
||||||
|
invertLabel: String,
|
||||||
|
},
|
||||||
|
inject: ['designer'],
|
||||||
|
data() {
|
||||||
|
const getFields = (children, field, disabled, parent = []) => {
|
||||||
|
const fields = [];
|
||||||
|
children.forEach(({rule, children}) => {
|
||||||
|
const temp = [...parent];
|
||||||
|
let _disabled = disabled;
|
||||||
|
if (rule.field) {
|
||||||
|
temp.push(rule);
|
||||||
|
if (!_disabled) {
|
||||||
|
_disabled = (rule.field === field && this.type === 'value');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const childrenFields = getFields(children || [], field, _disabled, temp);
|
||||||
|
if (rule.field) {
|
||||||
|
const item = {
|
||||||
|
value: rule.field,
|
||||||
|
label: rule?.__fc__?.refRule?.__$title?.value || rule.title,
|
||||||
|
rule,
|
||||||
|
parent,
|
||||||
|
formula: true,
|
||||||
|
// disabled: _disabled
|
||||||
|
};
|
||||||
|
if (childrenFields.length) {
|
||||||
|
item.children = childrenFields;
|
||||||
|
}
|
||||||
|
fields.push(item);
|
||||||
|
} else {
|
||||||
|
fields.push(...childrenFields)
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
editor: null,
|
||||||
|
visible: false,
|
||||||
|
expandedKeys: [
|
||||||
|
'_form', '_formula', '_subform'
|
||||||
|
],
|
||||||
|
err: '',
|
||||||
|
status: 'computed',
|
||||||
|
value: '',
|
||||||
|
condition: undefined,
|
||||||
|
formulaInfo: '',
|
||||||
|
formulaExample: '',
|
||||||
|
oldValue: '',
|
||||||
|
invert: false,
|
||||||
|
linkage: '',
|
||||||
|
getFields,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
t() {
|
||||||
|
return this.designer.setupState.t;
|
||||||
|
},
|
||||||
|
configured() {
|
||||||
|
return !!this.modelValue
|
||||||
|
},
|
||||||
|
activeRule() {
|
||||||
|
return this.designer.setupState.activeRule;
|
||||||
|
},
|
||||||
|
treeInfo() {
|
||||||
|
let ctx = this.activeRule?.__fc__.parent;
|
||||||
|
const activePage = this.designer.setupState.activePage;
|
||||||
|
let subTree = [];
|
||||||
|
if (activePage.default) {
|
||||||
|
subTree = this.getFields(this.designer.setupState.treeInfo, this.activeRule.field);
|
||||||
|
} else {
|
||||||
|
subTree = this.getFields(activePage.main.field && activePage.main === this.activeRule ? this.designer.setupState.treeInfo : this.designer.setupState.treeInfo[0].children, this.activeRule.field);
|
||||||
|
}
|
||||||
|
const tree = [
|
||||||
|
{
|
||||||
|
id: '_form',
|
||||||
|
label: this.t('computed.form'),
|
||||||
|
children: subTree
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '_formula',
|
||||||
|
label: this.t('computed.formula'),
|
||||||
|
children: formulaTree.map(item => {
|
||||||
|
return {
|
||||||
|
label: this.t('formula.' + item.key),
|
||||||
|
children: item.children.map(label => {
|
||||||
|
return {
|
||||||
|
label,
|
||||||
|
info: this.t('formula.' + label),
|
||||||
|
example: formulaInfo[label] || '',
|
||||||
|
formula: true,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
];
|
||||||
|
while (ctx) {
|
||||||
|
if (ctx.rule === activePage.main) {
|
||||||
|
ctx = undefined;
|
||||||
|
} else if (ctx.rule._menu && ['array', 'object'].indexOf(ctx.rule._menu.subForm) > -1) {
|
||||||
|
const subTree = this.getFields(this.designer.setupState.findTree(ctx.rule._fc_id), this.activeRule.field)
|
||||||
|
if (subTree.length) {
|
||||||
|
tree.unshift({
|
||||||
|
id: '_subform',
|
||||||
|
label: ctx?.refRule?.__$title?.value || ctx.rule.title || ctx.rule._menu.label,
|
||||||
|
children: subTree
|
||||||
|
})
|
||||||
|
}
|
||||||
|
ctx = undefined;
|
||||||
|
} else {
|
||||||
|
ctx = ctx.parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tree;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
visible(v) {
|
||||||
|
if (v) {
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
status(n) {
|
||||||
|
if (n === 'computed') {
|
||||||
|
this.load();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
beforeUnmount() {
|
||||||
|
document.querySelector('._fd-comp-script') && document.querySelector('._fd-comp-script').removeEventListener('mouseover', this.spanOver);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
update() {
|
||||||
|
this.linkage = '';
|
||||||
|
if (this.type === 'value' || (this.modelValue && is.String(this.modelValue))) {
|
||||||
|
this.status = 'computed';
|
||||||
|
this.load();
|
||||||
|
this.condition = undefined;
|
||||||
|
} else {
|
||||||
|
this.status = 'condition';
|
||||||
|
this.condition = this.modelValue ? deepCopy(this.modelValue) : undefined;
|
||||||
|
if (this.condition) {
|
||||||
|
this.invert = this.condition.invert === true;
|
||||||
|
this.linkage = this.condition.linkage || '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getTitle(rule) {
|
||||||
|
return (rule?.__fc__?.refRule?.__$title?.value || rule.title || '').trim() || (rule._menu && rule._menu.label) || rule.field || rule._fc_id;
|
||||||
|
},
|
||||||
|
setField(data) {
|
||||||
|
if (data.disabled === true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.markRule(`"${data.rule.field}"`, this.getTitle(data.rule), 'id');
|
||||||
|
},
|
||||||
|
spanOver(e) {
|
||||||
|
if (e.target.classList.contains('cm-keyword')) {
|
||||||
|
const label = e.target.innerText.trim();
|
||||||
|
this.formulaInfo = this.t('formula.' + label) || '';
|
||||||
|
this.formulaExample = formulaInfo[label] || '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
nodeOver(data) {
|
||||||
|
this.formulaInfo = data.info || '';
|
||||||
|
this.formulaExample = data.example || '';
|
||||||
|
},
|
||||||
|
markRule(label, title, cls) {
|
||||||
|
const value = this.editor.getValue();
|
||||||
|
if (value) {
|
||||||
|
const ch = this.editor.getCursor().ch;
|
||||||
|
if ([' ', '(', ',', ')', '{', '}', '[', ']'].indexOf(value.substr(ch - 1, 1)) === -1) {
|
||||||
|
this.editor.replaceRange(' ', this.editor.getCursor());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.editor.replaceRange(label, this.editor.getCursor());
|
||||||
|
const cur = this.editor.getCursor();
|
||||||
|
const span = document.createElement('span');
|
||||||
|
span.innerText = title;
|
||||||
|
span.classList.add('cm-fc-' + cls);
|
||||||
|
this.editor.markText({
|
||||||
|
line: cur.line,
|
||||||
|
ch: cur.ch - label.length
|
||||||
|
}, cur, {
|
||||||
|
replacedWith: span
|
||||||
|
});
|
||||||
|
},
|
||||||
|
nodeClick(data) {
|
||||||
|
if (!data.formula || data.disabled === true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (data.rule) {
|
||||||
|
const fields = [];
|
||||||
|
const titles = [];
|
||||||
|
let flag = false;
|
||||||
|
data.parent.forEach(item => {
|
||||||
|
if (item._menu && item._menu.subForm === 'array') {
|
||||||
|
flag = true;
|
||||||
|
}
|
||||||
|
fields.push(item.field);
|
||||||
|
titles.push(this.getTitle(item));
|
||||||
|
})
|
||||||
|
if (flag) {
|
||||||
|
return this.setColumn(data);
|
||||||
|
}
|
||||||
|
fields.push(data.rule.field);
|
||||||
|
titles.push(this.getTitle(data.rule));
|
||||||
|
this.markRule(fields.join('.'), titles.join('.'), 'field');
|
||||||
|
} else {
|
||||||
|
this.editor.replaceRange(data.label + '()', this.editor.getCursor());
|
||||||
|
this.editor.moveH(-1, 'char');
|
||||||
|
}
|
||||||
|
this.editor.focus();
|
||||||
|
},
|
||||||
|
setColumn(data) {
|
||||||
|
let flag = false;
|
||||||
|
const fields = [];
|
||||||
|
const titles = [];
|
||||||
|
const columns = [];
|
||||||
|
data.parent.forEach(item => {
|
||||||
|
if (!flag) {
|
||||||
|
flag = item._menu && item._menu.subForm === 'array';
|
||||||
|
fields.push(item.field);
|
||||||
|
titles.push(this.getTitle(item));
|
||||||
|
} else {
|
||||||
|
columns.push(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
columns.push(data.rule);
|
||||||
|
columns.reverse().forEach(rule => {
|
||||||
|
this.nodeClick({label: 'COLUMN', formula: true});
|
||||||
|
this.editor.replaceRange(',', this.editor.getCursor());
|
||||||
|
this.setField({rule});
|
||||||
|
this.editor.moveH(-1 - rule.field.length - 2, 'char');
|
||||||
|
});
|
||||||
|
this.markRule(fields.join('.'), titles.join('.'), 'field');
|
||||||
|
},
|
||||||
|
submit() {
|
||||||
|
if (this.status === 'computed') {
|
||||||
|
const value = this.editor.getValue().trim();
|
||||||
|
if (this.oldValue !== value || !is.String(this.modelValue)) {
|
||||||
|
this.oldValue = value;
|
||||||
|
this.$emit('update:modelValue', value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let value = this.condition ? {...this.condition} : this.condition;
|
||||||
|
if (value) {
|
||||||
|
if (this.type === 'linkage') {
|
||||||
|
if (this.linkage) {
|
||||||
|
value.linkage = this.linkage;
|
||||||
|
} else {
|
||||||
|
value = '';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (this.invert) {
|
||||||
|
value.invert = true;
|
||||||
|
} else {
|
||||||
|
delete value.invert;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.$emit('update:modelValue', value || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.visible = false;
|
||||||
|
},
|
||||||
|
setValue(value) {
|
||||||
|
const fields = this.designer.setupState.fields().map(escapeRegExp);
|
||||||
|
value = value.replace(new RegExp(`["'](${fields.join('|')})(\\.(${fields.join('|')}))*(?![a-zA-Z0-9_$])["']`, 'g'), v => {
|
||||||
|
return '__var___' + v + '__var__';
|
||||||
|
})
|
||||||
|
value = value.replace(new RegExp(`(?<!__var___")(${fields.join('|')})(\\.(${fields.join('|')}))*(?![a-zA-Z0-9_$])`, 'g'), v => {
|
||||||
|
return '__var___' + v + '__var__';
|
||||||
|
})
|
||||||
|
value.split('__var__').forEach(v => {
|
||||||
|
let rule;
|
||||||
|
if (v.indexOf('_') === 0) {
|
||||||
|
v = v.slice(1);
|
||||||
|
const flag = ['\'', '"'].indexOf(v[0]) > -1;
|
||||||
|
if (flag) {
|
||||||
|
v = v.slice(1).slice(0, -1);
|
||||||
|
}
|
||||||
|
let level = 0;
|
||||||
|
if (v.indexOf('.') > -1) {
|
||||||
|
const temp = v.split('.');
|
||||||
|
v = temp.pop();
|
||||||
|
level = temp.length;
|
||||||
|
}
|
||||||
|
rule = this.designer.setupState.dragForm.api.all().filter(item => item && item.field === v)[0];
|
||||||
|
if (rule) {
|
||||||
|
if (flag) {
|
||||||
|
this.setField({rule});
|
||||||
|
} else {
|
||||||
|
const fields = [rule.field];
|
||||||
|
const titles = [this.getTitle(rule)];
|
||||||
|
let ctx = rule.__fc__.parent;
|
||||||
|
while (ctx && level > 0) {
|
||||||
|
if (ctx.input) {
|
||||||
|
level--;
|
||||||
|
fields.unshift(ctx.rule.field);
|
||||||
|
titles.unshift(this.getTitle(ctx.rule));
|
||||||
|
}
|
||||||
|
ctx = ctx.parent;
|
||||||
|
}
|
||||||
|
this.markRule(fields.join('.'), titles.join('.'), 'field');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.editor.replaceRange(v, this.editor.getCursor());
|
||||||
|
});
|
||||||
|
},
|
||||||
|
load() {
|
||||||
|
this.value = is.String(this.modelValue) ? this.modelValue : '';
|
||||||
|
this.oldValue = this.value;
|
||||||
|
this.err = this.formulaInfo = '';
|
||||||
|
this.$nextTick(() => {
|
||||||
|
document.querySelector('._fd-comp-script').addEventListener('mouseover', this.spanOver);
|
||||||
|
this.editor = markRaw(CodeMirror(this.$refs.editor, {
|
||||||
|
lineNumbers: true,
|
||||||
|
mode: 'fcComputedMode',
|
||||||
|
line: true,
|
||||||
|
tabSize: 2,
|
||||||
|
lineWrapping: true,
|
||||||
|
value: '',
|
||||||
|
extraKeys: {
|
||||||
|
Enter: function () {
|
||||||
|
return false; // 阻止默认行为
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
this.setValue(this.value || '');
|
||||||
|
|
||||||
|
this.editor.on('beforeChange', (cm, change) => {
|
||||||
|
if (change.origin === 'paste') {
|
||||||
|
const text = change.text[0] || '';
|
||||||
|
if (text) {
|
||||||
|
this.setValue(text);
|
||||||
|
}
|
||||||
|
change.cancel();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
addAutoKeyMap(this.editor);
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fd-computed {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-computed .el-badge {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-computed .el-button {
|
||||||
|
font-weight: 400;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-comp-con, ._fd-comp-condition {
|
||||||
|
height: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-comp-condition .el-main {
|
||||||
|
padding: 20px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-comp-con .el-tree > .el-tree-node {
|
||||||
|
margin: 1px;
|
||||||
|
padding: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--fc-text-color-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-comp-con .el-tree > .el-tree-node + .el-tree-node {
|
||||||
|
border-top: 1px solid var(--fc-line-color-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-comp-con .el-tree-node {
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-comp-con .el-tree-node__content {
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-comp-dialog .el-dialog__body {
|
||||||
|
padding: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-comp-dialog .el-tabs__header {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-comp-con .el-main {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-comp-r {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
border: 1px solid var(--fc-line-color-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-comp-head {
|
||||||
|
display: flex;
|
||||||
|
padding: 5px 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 13px;
|
||||||
|
border-bottom: 1px solid var(--fc-line-color-3);
|
||||||
|
height: 38px;
|
||||||
|
align-items: center;
|
||||||
|
color: var(--fc-text-color-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-comp-r > .el-main, ._fd-comp-script {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex: 1;
|
||||||
|
flex-basis: auto;
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-width: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-comp-script {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-comp-r > .el-main {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-comp-info {
|
||||||
|
background: var(--fc-bg-color-2);
|
||||||
|
border-radius: 6px;
|
||||||
|
height: 90px;
|
||||||
|
margin: 10px;
|
||||||
|
color: var(--fc-text-color-2);
|
||||||
|
line-height: 20px;
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-comp-con .CodeMirror {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-comp-con .CodeMirror-wrap pre.CodeMirror-line {
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-comp-node {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
height: 26px;
|
||||||
|
line-height: 26px;
|
||||||
|
padding-right: 5px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
._fd-comp-node ._group {
|
||||||
|
color: #61affe;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-comp-node ._subform {
|
||||||
|
color: #fca130;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
._fd-comp-node.disabled {
|
||||||
|
color: var(--fc-text-color-3);
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-comp-node.disabled ._fd-comp-id {
|
||||||
|
background-color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-comp-id {
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
color: #fff;
|
||||||
|
background-color: var(--fc-style-color-1);
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 20px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-comp-dialog .el-aside {
|
||||||
|
width: 300px;
|
||||||
|
border: 1px solid var(--fc-line-color-3);
|
||||||
|
border-right: 0 none;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-comp-title {
|
||||||
|
position: relative;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--fc-text-color-1);
|
||||||
|
margin-bottom: 15px;
|
||||||
|
padding-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-comp-title:before {
|
||||||
|
content: ' ';
|
||||||
|
display: inline-block;
|
||||||
|
width: 3px;
|
||||||
|
height: 1em;
|
||||||
|
background-color: var(--fc-style-color-1);
|
||||||
|
position: absolute;
|
||||||
|
top: 3px;
|
||||||
|
left: -5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-comp-script .CodeMirror pre.CodeMirror-line {
|
||||||
|
line-height: 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-comp-linkage {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-comp-linkage > ._fd-rule-select {
|
||||||
|
display: inline-block;
|
||||||
|
width: 120px;
|
||||||
|
margin: 0 6px;
|
||||||
|
}
|
||||||
|
</style>
|
450
src/components/computed/ConditionGroup.vue
Normal file
450
src/components/computed/ConditionGroup.vue
Normal file
@ -0,0 +1,450 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_fd-cdg-input">
|
||||||
|
<div class="_fd-cdg-item">
|
||||||
|
<div class="_fd-cdg-and">
|
||||||
|
<el-select size="default" v-model="mode" @change="onInput" v-if="list.length > 0">
|
||||||
|
<el-option label="AND" value="AND"/>
|
||||||
|
<el-option label="OR" value="OR"/>
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
<div class="_fd-cdg-options">
|
||||||
|
<template v-for="(item, idx) in list">
|
||||||
|
<div class="_fd-cdg-option is-group" v-if="item.mode != null" :key="item.field + 'a' + idx + list.length">
|
||||||
|
<ConditionGroup v-model="list[idx]" @change="onInput"></ConditionGroup>
|
||||||
|
<i class="fc-icon icon-add-circle" :class="{disabled: list.length === 1}"
|
||||||
|
@click="removeItem(idx)"></i>
|
||||||
|
</div>
|
||||||
|
<div class="_fd-cdg-option" v-else :key="idx">
|
||||||
|
<el-select style="width: 85px;" size="default" v-model="item.type"
|
||||||
|
@change="changeType(item)">
|
||||||
|
<el-option :label="t('props.field')" value="field"/>
|
||||||
|
<el-option :label="t('props.variable')" value="variable"/>
|
||||||
|
</el-select>
|
||||||
|
<template v-if="item.type === 'variable'">
|
||||||
|
<el-input class="_fd-cdg-variable" size="default" v-model="item.field" clearable
|
||||||
|
@change="changeField(item)" key="variable">
|
||||||
|
<template #suffix>
|
||||||
|
<VariableConfig popover
|
||||||
|
@confirm="(val) => selectVar(item, val)"></VariableConfig>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<RuleSelect class="_fd-cdg-field" size="default" onlyField valueType="field"
|
||||||
|
v-model="item.field" clearable :multiple="false"
|
||||||
|
@change="changeField(item)" key="field"></RuleSelect>
|
||||||
|
</template>
|
||||||
|
<el-select class="_fd-cdg-term" size="default" v-if="item.formula" v-model="item.condition"
|
||||||
|
@change="onInput">
|
||||||
|
<el-option v-for="item in item.formula" :key="item.value" :label="item.label"
|
||||||
|
:value="item.value"/>
|
||||||
|
</el-select>
|
||||||
|
<div class="_fd-cfg-value"
|
||||||
|
v-if="item.input && ['empty', 'notEmpty'].indexOf(item.condition) === -1">
|
||||||
|
<template v-if="item.var">
|
||||||
|
<RuleSelect class="_fd-cdg-field" size="default" onlyField valueType="field"
|
||||||
|
v-model="item.compare" clearable :multiple="false"
|
||||||
|
@change="onInput"></RuleSelect>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="item.type === 'variable'">
|
||||||
|
<ValueInput size="default" v-model="item.value" @change="onInput"></ValueInput>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="item.condition === 'pattern'">
|
||||||
|
<PatternInput size="default" :key="item.field" v-model="item.value"
|
||||||
|
@change="onInput"></PatternInput>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<ConditionInput v-bind="item.input" :key="item.field" v-model="item.value"
|
||||||
|
@change="onInput"></ConditionInput>
|
||||||
|
</template>
|
||||||
|
<el-checkbox v-model="item.var" size="default" :label="t('props.field')"/>
|
||||||
|
</div>
|
||||||
|
<i class="fc-icon icon-delete"
|
||||||
|
@click="removeItem(idx)"></i>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="_fd-cdg-btns">
|
||||||
|
<el-button link type="primary" @click="addItem">
|
||||||
|
<i class="fc-icon icon-add-circle"></i>
|
||||||
|
{{ t('computed.addCondition') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button link type="primary" @click="addItemGroup">
|
||||||
|
<i class="fc-icon icon-add-circle"></i>
|
||||||
|
{{ t('computed.addGroup') }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent, markRaw} from 'vue';
|
||||||
|
import ConditionInput from './ConditionInput.vue';
|
||||||
|
import is from '@form-create/utils/lib/type';
|
||||||
|
import {deepGet} from '../../utils';
|
||||||
|
import PatternInput from './PatternInput.vue';
|
||||||
|
import RuleSelect from '../RuleSelect.vue';
|
||||||
|
import ValueInput from './ValueInput.vue';
|
||||||
|
import VariableConfig from './VariableConfig.vue';
|
||||||
|
|
||||||
|
const formulaType = {
|
||||||
|
input: ['==', '!=', 'on', 'notOn', 'empty', 'notEmpty', 'pattern'],
|
||||||
|
select: ['==', '!=', 'on', 'notOn', 'empty', 'notEmpty'],
|
||||||
|
switch: ['==', '!='],
|
||||||
|
number: ['==', '!=', '>', '>=', '<', '<=', 'empty', 'notEmpty'],
|
||||||
|
};
|
||||||
|
formulaType.cascader = formulaType.select;
|
||||||
|
|
||||||
|
const ConditionGroup = defineComponent({
|
||||||
|
name: 'ConditionGroup',
|
||||||
|
components: {VariableConfig, ValueInput, RuleSelect, PatternInput, ConditionInput},
|
||||||
|
inject: ['designer'],
|
||||||
|
emits: ['update:modelValue', 'change'],
|
||||||
|
props: {
|
||||||
|
modelValue: [Object, Array],
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
formulaLabel() {
|
||||||
|
return ['==', '!=', 'on', 'notOn', 'empty', 'notEmpty', 'pattern', '>', '>=', '<', '<='].reduce((p, v) => {
|
||||||
|
p[v] = this.t('computed.formulas.' + v);
|
||||||
|
return p;
|
||||||
|
}, {});
|
||||||
|
},
|
||||||
|
activeRule() {
|
||||||
|
return this.designer.setupState.activeRule;
|
||||||
|
},
|
||||||
|
rules() {
|
||||||
|
let ctx = this.activeRule?.__fc__.parent;
|
||||||
|
let rules = [];
|
||||||
|
while (ctx) {
|
||||||
|
if (ctx.rule._menu && ctx.rule._menu.subForm) {
|
||||||
|
rules = this.getFields(this.designer.setupState.findTree(ctx.rule._fc_id));
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
ctx = ctx.parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [...rules, ...this.getFields(this.designer.setupState.treeInfo)]
|
||||||
|
},
|
||||||
|
t() {
|
||||||
|
return this.designer.setupState.t;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
mode: 'AND',
|
||||||
|
list: [],
|
||||||
|
ConditionGroup: markRaw(ConditionGroup),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
selectVar(item, val) {
|
||||||
|
item.field = val.slice(2, -2);
|
||||||
|
this.changeField(item);
|
||||||
|
},
|
||||||
|
addItem() {
|
||||||
|
this.list.push({
|
||||||
|
type: 'field',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
addItemGroup() {
|
||||||
|
this.list.push({mode: 'AND'});
|
||||||
|
},
|
||||||
|
removeItem(idx) {
|
||||||
|
this.list.splice(idx, 1);
|
||||||
|
this.onInput();
|
||||||
|
},
|
||||||
|
changeType(item) {
|
||||||
|
item.field = '';
|
||||||
|
item.input = null;
|
||||||
|
item.formula = null;
|
||||||
|
},
|
||||||
|
changeField(item) {
|
||||||
|
if (item.field) {
|
||||||
|
item.condition = '==';
|
||||||
|
if (item.type === 'field') {
|
||||||
|
this.tidyItem(item);
|
||||||
|
} else {
|
||||||
|
item.input = true;
|
||||||
|
item.formula = formulaType.select.map(v => {
|
||||||
|
return {
|
||||||
|
label: this.formulaLabel[v],
|
||||||
|
value: v
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
item.input = null;
|
||||||
|
item.formula = null;
|
||||||
|
}
|
||||||
|
this.onInput();
|
||||||
|
},
|
||||||
|
getFields(children, parent = []) {
|
||||||
|
const fields = [];
|
||||||
|
children.forEach(({rule, children}) => {
|
||||||
|
const temp = [...parent];
|
||||||
|
if (rule.field) {
|
||||||
|
temp.push(rule);
|
||||||
|
}
|
||||||
|
const childrenFields = this.getFields(children || [], temp);
|
||||||
|
if (rule.field) {
|
||||||
|
const item = {
|
||||||
|
field: rule.field,
|
||||||
|
value: parent.length ? parent.map(item => item.field).join('.') + '.' + rule.field : rule.field,
|
||||||
|
label: rule.title,
|
||||||
|
rule,
|
||||||
|
};
|
||||||
|
fields.push(item, ...childrenFields);
|
||||||
|
} else {
|
||||||
|
fields.push(...childrenFields)
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
return fields;
|
||||||
|
},
|
||||||
|
tidyValue() {
|
||||||
|
let value = this.modelValue;
|
||||||
|
if (value) {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
value = {
|
||||||
|
mode: 'AND',
|
||||||
|
group: value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.mode = value.mode === 'OR' ? 'OR' : 'AND';
|
||||||
|
this.list = (value.group || []).map(item => {
|
||||||
|
if (item.mode != null) {
|
||||||
|
return item;
|
||||||
|
} else {
|
||||||
|
return this.tidyItem({...item});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!this.list.length) {
|
||||||
|
this.list.push({type: 'field'}, {type: 'field'});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tidyItem(item) {
|
||||||
|
if (item.variable) {
|
||||||
|
item.input = true;
|
||||||
|
item.field = item.variable;
|
||||||
|
item.formula = formulaType.select.map(v => {
|
||||||
|
return {
|
||||||
|
label: this.formulaLabel[v],
|
||||||
|
value: v
|
||||||
|
}
|
||||||
|
});
|
||||||
|
item.type = 'variable';
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
item.type = 'field';
|
||||||
|
this.rules.forEach(data => {
|
||||||
|
if (data.value === item.field || data.field === item.field) {
|
||||||
|
const condition = data.rule._menu.condition;
|
||||||
|
const input = condition ? (is.Function(condition) ? condition(data.rule) : is.String(condition) ? {
|
||||||
|
type: condition
|
||||||
|
} : {...condition}) : {
|
||||||
|
type: 'input'
|
||||||
|
}
|
||||||
|
if (input.options) {
|
||||||
|
input.options = is.String(input.options) ? deepGet(data.rule.__fc__.prop, input.options) : input.options;
|
||||||
|
}
|
||||||
|
item.formula = (formulaType[input.type] || formulaType.input).map(v => {
|
||||||
|
return {
|
||||||
|
label: this.formulaLabel[v],
|
||||||
|
value: v
|
||||||
|
}
|
||||||
|
});
|
||||||
|
item.var = !!item.compare;
|
||||||
|
item.input = input;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return item;
|
||||||
|
},
|
||||||
|
onInput() {
|
||||||
|
let value = []
|
||||||
|
this.list.forEach(item => {
|
||||||
|
if (item.field && item.condition && (item.compare || ['empty', 'notEmpty'].indexOf(item.condition) > -1 || (item.value != null && item.value !== ''))) {
|
||||||
|
const val = {
|
||||||
|
[item.type]: item.field,
|
||||||
|
condition: item.condition,
|
||||||
|
};
|
||||||
|
if (item.compare && item.var) {
|
||||||
|
val.compare = item.compare;
|
||||||
|
} else {
|
||||||
|
val.value = item.value;
|
||||||
|
}
|
||||||
|
value.push(val);
|
||||||
|
} else if (item.group) {
|
||||||
|
value.push(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (value.length === 1 && value[0].mode != null) {
|
||||||
|
value = value[0];
|
||||||
|
} else {
|
||||||
|
value = value.length > 0 ? {
|
||||||
|
mode: this.mode,
|
||||||
|
group: value
|
||||||
|
} : undefined
|
||||||
|
}
|
||||||
|
if (!value && (!this.modelValue || !this.modelValue.group)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.$emit('update:modelValue', value);
|
||||||
|
this.$emit('change', value);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.tidyValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
export default ConditionGroup;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
._fd-cdg-input {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-cdg-btns {
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-left: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-cdg-btns .el-button {
|
||||||
|
color: var(--fc-text-color-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-cdg-item {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-cdg-item .el-select {
|
||||||
|
background-color: var(--fc-bg-color-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-cdg-and {
|
||||||
|
display: flex;
|
||||||
|
width: 100px;
|
||||||
|
position: relative;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-cdg-and > .el-select {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: -5px;
|
||||||
|
width: 80px;
|
||||||
|
margin-top: -16px;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-cdg-and:before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
left: 30px;
|
||||||
|
background-color: var(--fc-line-color-2);
|
||||||
|
top: 1px;
|
||||||
|
bottom: 1px;
|
||||||
|
margin-top: 14px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-cdg-options {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-cdg-option {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-cdg-field {
|
||||||
|
width: 208px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-cdg-variable {
|
||||||
|
width: 208px;
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-cdg-term {
|
||||||
|
width: 104px
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-cdg-option > ._fd-cfg-value {
|
||||||
|
width: 208px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-cdg-option > .fc-icon {
|
||||||
|
margin-left: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--fc-text-color-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-cdg-option > .fc-icon.disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-cdg-option > ._fd-cfg-value > div {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-cdg-option > .el-select + .el-select, ._fd-cdg-option > .el-input + .el-select, ._fd-cdg-option > .el-select + .el-input {
|
||||||
|
margin-left: 10px
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-cfg-value {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-cfg-value .el-checkbox {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-cdg-option:before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
width: 105px;
|
||||||
|
height: 1px;
|
||||||
|
background-color: var(--fc-line-color-2);
|
||||||
|
left: -70px;
|
||||||
|
top: 50%;
|
||||||
|
margin-top: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-cdg-option.is-group {
|
||||||
|
border: 1px dashed #ccd3db;
|
||||||
|
padding: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-cdg-option.is-group > .fc-icon {
|
||||||
|
position: absolute;
|
||||||
|
right: -10px;
|
||||||
|
top: -10px;
|
||||||
|
z-index: 2;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-cdg-option.is-group:before {
|
||||||
|
margin-top: -17px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-cdg-option + ._fd-cdg-option {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
69
src/components/computed/ConditionInput.vue
Normal file
69
src/components/computed/ConditionInput.vue
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_fd-cdi-input">
|
||||||
|
<template v-if="type === 'cascader'">
|
||||||
|
<el-cascader size="default" :props="{checkStrictly: true, emitPath: false}" v-bind="props || {}"
|
||||||
|
:options="options"
|
||||||
|
v-model="value"
|
||||||
|
@change="onInput"></el-cascader>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="type === 'number'">
|
||||||
|
<el-input-number size="default" v-bind="props || {}" v-model="value" @change="onInput"></el-input-number>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="type === 'select'">
|
||||||
|
<el-select size="default"
|
||||||
|
filterable
|
||||||
|
allow-create
|
||||||
|
default-first-option v-bind="props || {}" v-model="value" @change="onInput">
|
||||||
|
<el-option v-for="opt in options" :label="opt.label" :value="opt.value" :key="opt.value"></el-option>
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="type === 'switch'">
|
||||||
|
<el-switch size="default" v-bind="props || {}" v-model="value" @change="onInput">
|
||||||
|
</el-switch>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<el-input size="default" v-bind="props || {}" v-model="value" @blur="onInput"></el-input>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'ConditionInput',
|
||||||
|
inject: ['designer'],
|
||||||
|
emits: ['update:modelValue', 'change'],
|
||||||
|
props: {
|
||||||
|
type: String,
|
||||||
|
options: Array,
|
||||||
|
props: Object,
|
||||||
|
modelValue: [String, Number, Array, Object, Boolean]
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
modelValue() {
|
||||||
|
this.value = this.modelValue || undefined;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
value: this.modelValue || undefined,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onInput() {
|
||||||
|
this.$emit('update:modelValue', this.value);
|
||||||
|
this.$emit('change', this.value);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fd-cdi-input > div {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
137
src/components/computed/PatternInput.vue
Normal file
137
src/components/computed/PatternInput.vue
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_fd-pattern-input">
|
||||||
|
<el-input :size="size" v-model="value" @blur="onInput" clearable>
|
||||||
|
<template #append>
|
||||||
|
<el-dropdown size="default" trigger="click" popper-class="_fd-pattern-popper">
|
||||||
|
<i class="fc-icon icon-setting"></i>
|
||||||
|
<template #dropdown>
|
||||||
|
<el-dropdown-menu>
|
||||||
|
<el-dropdown-item v-for="item in options" :key="item.value" @click="setValue(item.value)">
|
||||||
|
{{ item.label }}
|
||||||
|
</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</template>
|
||||||
|
</el-dropdown>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'PatternInput',
|
||||||
|
emits: ['update:modelValue', 'change'],
|
||||||
|
props: {
|
||||||
|
size: String,
|
||||||
|
modelValue: String,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
value: this.modelValue || '',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: '邮箱',
|
||||||
|
value: '^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '域名',
|
||||||
|
value: '^((http:\\/\\/)|(https:\\/\\/))?([a-zA-Z0-9]([a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,6}(\\/)$'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '手机号',
|
||||||
|
value: '^(?:(?:\\+|00)86)?1[3-9]\\d{9}$'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '座机电话',
|
||||||
|
value: '^(?:(?:\\d{3}-)?\\d{8}|^(?:\\d{4}-)?\\d{7,8})(?:-\\d+)?$'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '身份证号',
|
||||||
|
value: '^[1-9]\\d{5}(?:18|19|20)\\d{2}(?:0[1-9]|10|11|12)(?:0[1-9]|[1-2]\\d|30|31)\\d{3}[\\dXx]$'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '银行卡号',
|
||||||
|
value: '^[1-9]\\d{9,29}$'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '车牌号',
|
||||||
|
value: '^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-HJ-NP-Z][A-HJ-NP-Z0-9]{4,5}[A-HJ-NP-Z0-9挂学警港澳]$'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '中文',
|
||||||
|
value: '^(?:[\\u3400-\\u4DB5\\u4E00-\\u9FEA\\uFA0E\\uFA0F\\uFA11\\uFA13\\uFA14\\uFA1F\\uFA21\\uFA23\\uFA24\\uFA27-\\uFA29]|[\\uD840-\\uD868\\uD86A-\\uD86C\\uD86F-\\uD872\\uD874-\\uD879][\\uDC00-\\uDFFF]|\\uD869[\\uDC00-\\uDED6\\uDF00-\\uDFFF]|\\uD86D[\\uDC00-\\uDF34\\uDF40-\\uDFFF]|\\uD86E[\\uDC00-\\uDC1D\\uDC20-\\uDFFF]|\\uD873[\\uDC00-\\uDEA1\\uDEB0-\\uDFFF]|\\uD87A[\\uDC00-\\uDFE0])+$'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '数字',
|
||||||
|
value: '^\\d+$'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '整数',
|
||||||
|
value: '^(?:0|(?:-?[1-9]\\d*))$'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '正整数',
|
||||||
|
value: '^\\+?[1-9]\\d*$'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '负整数',
|
||||||
|
value: '^-[1-9]\\d*$'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '浮点数',
|
||||||
|
value: '^(-?[1-9]\\d*\\.\\d+|-?0\\.\\d*[1-9])$'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '正浮点数',
|
||||||
|
value: '^([1-9]\\d*\\.\\d+|-?0\\.\\d*[1-9])$'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '负浮点数',
|
||||||
|
value: '^-([1-9]\\d*\\.\\d+|-?0\\.\\d*[1-9])$'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '英文字母',
|
||||||
|
value: '^[a-zA-Z]+$'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '数字和字母',
|
||||||
|
value: '^[A-Za-z0-9]+$'
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
setValue(val) {
|
||||||
|
this.value = val;
|
||||||
|
this.onInput();
|
||||||
|
},
|
||||||
|
onInput() {
|
||||||
|
this.$emit('update:modelValue', this.value);
|
||||||
|
this.$emit('change', this.value);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fd-pattern-input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-pattern-input .el-input-group__append {
|
||||||
|
padding: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-pattern-input .fc-icon {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-pattern-popper .el-dropdown__list {
|
||||||
|
height: 350px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
</style>
|
88
src/components/computed/ValueInput.vue
Normal file
88
src/components/computed/ValueInput.vue
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
<template>
|
||||||
|
<el-input class="_fd-value-input" v-model="value" @blur="onBlur" v-bind="$attrs">
|
||||||
|
<template #prepend>
|
||||||
|
<el-select v-model="type" style="width: 60px">
|
||||||
|
<el-option :label="t('validate.types.string')" value="1"/>
|
||||||
|
<el-option :label="t('validate.types.number')" value="2"/>
|
||||||
|
<el-option :label="t('validate.types.boolean')" value="3"/>
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
<template #append v-if="$slots.append">
|
||||||
|
<slot name="append"></slot>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'ValueInput',
|
||||||
|
emits: ['update:modelValue', 'change', 'change-type', 'blur'],
|
||||||
|
inject: ['designer'],
|
||||||
|
props: {
|
||||||
|
modelValue: [String, Number, Boolean],
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
type: '1',
|
||||||
|
value: '',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
t() {
|
||||||
|
return this.designer.setupState.t;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
modelValue: {
|
||||||
|
handler: function (val) {
|
||||||
|
if (typeof val === 'number') {
|
||||||
|
this.type = '2';
|
||||||
|
} else if (typeof val === 'boolean') {
|
||||||
|
this.type = '3';
|
||||||
|
} else {
|
||||||
|
this.type = '1';
|
||||||
|
}
|
||||||
|
this.value = null == val ? '' : ('' + val);
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
},
|
||||||
|
type() {
|
||||||
|
this.updateValue(this.value);
|
||||||
|
this.$emit('change-type', this.type);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onBlur(...args) {
|
||||||
|
if (this.value !== this.toValue(this.modelValue)) {
|
||||||
|
this.updateValue(this.value);
|
||||||
|
}
|
||||||
|
this.$emit('blur', ...args);
|
||||||
|
},
|
||||||
|
updateValue(val) {
|
||||||
|
const value = this.toValue(val);
|
||||||
|
this.$emit('update:modelValue', value);
|
||||||
|
this.$emit('change', value);
|
||||||
|
},
|
||||||
|
toValue(val) {
|
||||||
|
if (this.type === '1') {
|
||||||
|
return '' + val;
|
||||||
|
} else if (this.type === '2') {
|
||||||
|
return parseFloat(val) || 0;
|
||||||
|
}
|
||||||
|
return val === 'true';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fd-value-input .el-input__validateIcon {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-value-input .el-select, ._fd-value-input .el-select__wrapper {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
604
src/components/computed/VariableConfig.vue
Normal file
604
src/components/computed/VariableConfig.vue
Normal file
@ -0,0 +1,604 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_fd-variable">
|
||||||
|
<template v-if="popover">
|
||||||
|
<el-popover ref="pop" placement="bottom" :width="330" :hide-after="0" trigger="click" :persistent="false"
|
||||||
|
popper-class="_fd-variable-pop">
|
||||||
|
<template #reference>
|
||||||
|
<i class="fc-icon icon-variable" style="cursor: pointer;"></i>
|
||||||
|
</template>
|
||||||
|
<el-container style="height: 100%;">
|
||||||
|
<el-header height="55px" class="_fd-variable-pop-header">
|
||||||
|
<el-input size="small" v-model="variable">
|
||||||
|
<template #prefix>
|
||||||
|
<span>{{</span>
|
||||||
|
</template>
|
||||||
|
<template #suffix>
|
||||||
|
}}
|
||||||
|
</template>
|
||||||
|
<template #append>
|
||||||
|
<div @click="confirm">{{ t('props.append') }}</div>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
<i class="fc-icon icon-setting" @click="openVariableConfig"></i>
|
||||||
|
</el-header>
|
||||||
|
<el-main>
|
||||||
|
<el-tree
|
||||||
|
ref="treeRef"
|
||||||
|
:data="treeInfo"
|
||||||
|
:default-expanded-keys="expandedKeys"
|
||||||
|
:expand-on-click-node="false"
|
||||||
|
:indent="10"
|
||||||
|
node-key="id"
|
||||||
|
@nodeClick="nodeClick"
|
||||||
|
>
|
||||||
|
<template #default="{ node, data }">
|
||||||
|
<div class="_fd-variable-pop-node"
|
||||||
|
:class="{disabled: data.disabled}">
|
||||||
|
<div>
|
||||||
|
<span>{{
|
||||||
|
(data.label || '').trim() || (data.rule ? getTitle(data.rule) : data.id)
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
<span>
|
||||||
|
{{ data.id }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-tree>
|
||||||
|
</el-main>
|
||||||
|
</el-container>
|
||||||
|
</el-popover>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<el-badge :value="eventNum" type="warning" :hidden="eventNum < 1">
|
||||||
|
<div class="_fd-variable-btn" @click="open">
|
||||||
|
<i class="fc-icon icon-variable"></i>{{ t('computed.variable.bind') }}
|
||||||
|
</div>
|
||||||
|
</el-badge>
|
||||||
|
<el-dialog class="_fd-variable-dialog _fd-config-dialog"
|
||||||
|
v-model="visible"
|
||||||
|
destroy-on-close
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
append-to-body
|
||||||
|
width="980px">
|
||||||
|
<template #header>
|
||||||
|
{{ t('computed.variable.bind') }}
|
||||||
|
<Warning :tooltip="t('warning.variable')"></Warning>
|
||||||
|
</template>
|
||||||
|
<el-container class="_fd-variable-con" style="height: 600px">
|
||||||
|
<el-main>
|
||||||
|
<el-container class="_fd-variable-l">
|
||||||
|
<el-header>
|
||||||
|
<div style="width: 230px">{{ t('computed.variable.attr') }}</div>
|
||||||
|
<div>{{ t('computed.variable.bind') }}</div>
|
||||||
|
</el-header>
|
||||||
|
<el-main>
|
||||||
|
<template v-for="(item, idx) in fields" :key="item.label">
|
||||||
|
<div class="_fd-variable-item"
|
||||||
|
:class="{active: idx === activeIdx, '_fd-variable-top': item.attach === true}">
|
||||||
|
<div class="_fd-variable-item-label">{{ item.label }}</div>
|
||||||
|
<div>=</div>
|
||||||
|
<el-input v-model="item.value" placeholder="_" @focus="activeIdx = idx"
|
||||||
|
clearable>
|
||||||
|
<template #prefix>
|
||||||
|
<span>{{</span>
|
||||||
|
</template>
|
||||||
|
<template #suffix>
|
||||||
|
}}
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-main>
|
||||||
|
</el-container>
|
||||||
|
</el-main>
|
||||||
|
<el-aside style="width:328px;">
|
||||||
|
<el-container class="_fd-variable-r">
|
||||||
|
<el-header>
|
||||||
|
{{ t('computed.variable.list') }}
|
||||||
|
<i class="fc-icon icon-setting" @click="openVariableConfig"></i>
|
||||||
|
</el-header>
|
||||||
|
<el-main>
|
||||||
|
<div class="_fd-variable-info">
|
||||||
|
{{ t('warning.variableInfo') }}
|
||||||
|
</div>
|
||||||
|
<el-tree
|
||||||
|
ref="treeRef"
|
||||||
|
:data="treeInfo"
|
||||||
|
:default-expanded-keys="expandedKeys"
|
||||||
|
:expand-on-click-node="false"
|
||||||
|
node-key="id"
|
||||||
|
:indent="10"
|
||||||
|
@nodeClick="nodeClick"
|
||||||
|
>
|
||||||
|
<template #default="{ node, data }">
|
||||||
|
<div class="_fd-variable-node"
|
||||||
|
:class="{disabled: data.disabled}">
|
||||||
|
<div>
|
||||||
|
<span>{{
|
||||||
|
(data.label || '').trim() || (data.rule ? getTitle(data.rule) : data.id)
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
<span>
|
||||||
|
{{ data.id }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-tree>
|
||||||
|
</el-main>
|
||||||
|
</el-container>
|
||||||
|
</el-aside>
|
||||||
|
</el-container>
|
||||||
|
<template #footer>
|
||||||
|
<div>
|
||||||
|
<el-button size="default" @click="visible=false">{{ t('props.cancel') }}</el-button>
|
||||||
|
<el-button type="primary" size="default" @click="submit">{{
|
||||||
|
t('props.ok')
|
||||||
|
}}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
import {lower} from '@form-create/utils/lib/tocase';
|
||||||
|
import Warning from '../Warning.vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'VariableConfig',
|
||||||
|
components: {Warning},
|
||||||
|
emits: ['submit', 'confirm'],
|
||||||
|
props: {
|
||||||
|
popover: Boolean,
|
||||||
|
},
|
||||||
|
inject: ['designer'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
visible: false,
|
||||||
|
activeIdx: 0,
|
||||||
|
variable: '',
|
||||||
|
value: {},
|
||||||
|
fields: [],
|
||||||
|
expandedKeys: [
|
||||||
|
'$topForm', '$cookie', '$localStorage', '$sessionStorage', '$globalData', '$var'
|
||||||
|
],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
t() {
|
||||||
|
return this.designer.setupState.t;
|
||||||
|
},
|
||||||
|
activeRule() {
|
||||||
|
return this.designer.setupState.activeRule;
|
||||||
|
},
|
||||||
|
eventNum() {
|
||||||
|
return ((this.activeRule || {})._loadData || []).length;
|
||||||
|
},
|
||||||
|
treeInfo() {
|
||||||
|
const varObj = this.toObject(this.designer.setupState.varList || []);
|
||||||
|
const tree = [
|
||||||
|
{
|
||||||
|
id: '$topForm',
|
||||||
|
label: this.t('computed.form'),
|
||||||
|
driver: true,
|
||||||
|
children: this.getFormTree(this.designer.setupState.treeInfo)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '$cookie',
|
||||||
|
label: 'cookie',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '$localStorage',
|
||||||
|
label: 'localStorage',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '$sessionStorage',
|
||||||
|
label: 'sessionStorage',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '$globalData',
|
||||||
|
label: this.t('props.globalData'),
|
||||||
|
driver: true,
|
||||||
|
children: Object.keys((this.designer.setupState.formOptions.globalData || {})).map(k => {
|
||||||
|
return {
|
||||||
|
label: this.designer.setupState.formOptions.globalData[k].label,
|
||||||
|
id: k,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '$var',
|
||||||
|
label: this.t('computed.variable.title'),
|
||||||
|
driver: true,
|
||||||
|
children: Object.keys((this.designer.setupState.formOptions.globalVariable || {})).map(k => {
|
||||||
|
return {
|
||||||
|
label: this.designer.setupState.formOptions.globalVariable[k].label,
|
||||||
|
id: k,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const activePage = this.designer.setupState.activePage;
|
||||||
|
if (!activePage.default && activePage.main.field && activePage.main !== this.activeRule) {
|
||||||
|
tree[0].id = '$scopeForm';
|
||||||
|
if (tree[0].children.length) {
|
||||||
|
tree[0].children = tree[0].children[0].children;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let ctx = this.activeRule?.__fc__.parent;
|
||||||
|
while (ctx) {
|
||||||
|
if (ctx.rule === activePage.main) {
|
||||||
|
ctx = undefined;
|
||||||
|
} else if (ctx.rule._menu && ['array', 'object', 'scope'].indexOf(ctx.rule._menu.subForm) > -1) {
|
||||||
|
const subTree = this.getFormTree(this.designer.setupState.findTree(ctx.rule._fc_id))
|
||||||
|
if (subTree.length) {
|
||||||
|
tree.unshift({
|
||||||
|
id: '$form',
|
||||||
|
driver: true,
|
||||||
|
label: ctx.refRule?.__$title?.value || ctx.rule.title || ctx.rule._menu.label,
|
||||||
|
children: subTree
|
||||||
|
})
|
||||||
|
}
|
||||||
|
ctx = undefined;
|
||||||
|
} else {
|
||||||
|
ctx = ctx.parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.designer.setupState.getConfig('showLanguage') !== false) {
|
||||||
|
let lang = this.designer.setupState.formOptions?.language || {};
|
||||||
|
let language = lang[this.designer.props?.locale?.name || 'zh-cn'] || lang[Object.keys(lang)[0]] || {};
|
||||||
|
tree.push({
|
||||||
|
id: '$t',
|
||||||
|
label: this.t('language.name'),
|
||||||
|
driver: true,
|
||||||
|
children: Object.keys(language).map(k => {
|
||||||
|
return {
|
||||||
|
label: language[k],
|
||||||
|
id: k,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (varObj['$cookie']) {
|
||||||
|
tree[1] = {...tree[1], ...varObj['$cookie']};
|
||||||
|
}
|
||||||
|
if (varObj['$localStorage']) {
|
||||||
|
tree[2] = {...tree[2], ...varObj['$localStorage']};
|
||||||
|
}
|
||||||
|
if (varObj['$sessionStorage']) {
|
||||||
|
tree[3] = {...tree[3], ...varObj['$sessionStorage']};
|
||||||
|
}
|
||||||
|
if (varObj['$globalData'] && varObj['$globalData'].children) {
|
||||||
|
tree[4].children = Object.values({...this.toObject(tree[4].children), ...this.toObject(varObj['$globalData'].children || [])});
|
||||||
|
}
|
||||||
|
if (varObj['$var'] && varObj['$var'].children) {
|
||||||
|
tree[5].children = Object.values({...this.toObject(tree[5].children), ...this.toObject(varObj['$var'].children || [])});
|
||||||
|
}
|
||||||
|
|
||||||
|
delete varObj['$cookie'];
|
||||||
|
delete varObj['$localStorage'];
|
||||||
|
delete varObj['$sessionStorage'];
|
||||||
|
delete varObj['$globalData'];
|
||||||
|
delete varObj['$var'];
|
||||||
|
tree.push(...Object.values(varObj));
|
||||||
|
return tree;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
visible(v) {
|
||||||
|
if (v) {
|
||||||
|
this.updateFields();
|
||||||
|
this.activeIdx = 0;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
openVariableConfig() {
|
||||||
|
this.designer.setupState.openGlobalVariableDialog();
|
||||||
|
},
|
||||||
|
toObject(list) {
|
||||||
|
const data = {};
|
||||||
|
list && list.forEach(item => {
|
||||||
|
data[item.id] = item;
|
||||||
|
})
|
||||||
|
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
nodeClick(data, node) {
|
||||||
|
if ((this.popover || this.fields[this.activeIdx]) && !data.driver) {
|
||||||
|
let val = data.id;
|
||||||
|
node = node.parent;
|
||||||
|
while (node.level >= 1) {
|
||||||
|
val = (node.data.id) + '.' + val;
|
||||||
|
node = node.parent;
|
||||||
|
}
|
||||||
|
if (this.popover) {
|
||||||
|
this.variable = val;
|
||||||
|
} else {
|
||||||
|
this.fields[this.activeIdx].value = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getTitle(rule) {
|
||||||
|
return (rule?.__fc__?.refRule?.__$title?.value || rule.title || '').trim() || (rule._menu && rule._menu.label) || rule.field || rule._fc_id;
|
||||||
|
},
|
||||||
|
getFormTree(children) {
|
||||||
|
const fields = [];
|
||||||
|
children.forEach(({rule, children}) => {
|
||||||
|
const childrenFields = (rule.field && (!rule._menu || rule._menu.subForm !== 'object')) ? [] : this.getFormTree(children || []);
|
||||||
|
if (rule.field) {
|
||||||
|
const item = {
|
||||||
|
id: rule.field,
|
||||||
|
label: rule?.__fc__?.refRule?.__$title?.value || rule.title,
|
||||||
|
rule,
|
||||||
|
};
|
||||||
|
if (childrenFields.length) {
|
||||||
|
item.children = childrenFields;
|
||||||
|
}
|
||||||
|
fields.push(item);
|
||||||
|
} else {
|
||||||
|
fields.push(...childrenFields)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return fields;
|
||||||
|
},
|
||||||
|
tranField(str) {
|
||||||
|
if (str.indexOf('formCreate') === 0) {
|
||||||
|
str = lower(str.replace('formCreate', ''));
|
||||||
|
} else {
|
||||||
|
str = 'props.' + str;
|
||||||
|
}
|
||||||
|
return str.replaceAll('>', '.');
|
||||||
|
},
|
||||||
|
updateFields() {
|
||||||
|
const vm = this.designer.setupState;
|
||||||
|
const fields = [];
|
||||||
|
const loadData = {};
|
||||||
|
(vm.activeRule._loadData || []).forEach(item => {
|
||||||
|
loadData[item.to] = item.attr;
|
||||||
|
});
|
||||||
|
const important = [];
|
||||||
|
if (vm.activeRule.field) {
|
||||||
|
important.push({
|
||||||
|
label: this.t('computed.value.name'),
|
||||||
|
attach: true,
|
||||||
|
modify: true,
|
||||||
|
field: 'value'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const rules = vm.propsForm.api.model();
|
||||||
|
Object.keys(rules).forEach(k => {
|
||||||
|
if (k && (k[0] !== '_' || rules[k]._fc_important_prop) && rules[k].title && rules[k]._fc_important_prop !== false && !rules[k].hidden && false !== rules[k].display) {
|
||||||
|
const prop = typeof rules[k]._fc_important_prop === 'string' ? rules[k]._fc_important_prop : k;
|
||||||
|
(rules[k]._fc_important_prop === true ? important : fields).push({
|
||||||
|
label: rules[k].title,
|
||||||
|
modify: prop === 'formCreateChild',
|
||||||
|
field: this.tranField(prop)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
fields.unshift(...important);
|
||||||
|
fields.forEach(item => {
|
||||||
|
item.value = loadData[item.field] || '';
|
||||||
|
});
|
||||||
|
|
||||||
|
this.fields = fields;
|
||||||
|
},
|
||||||
|
open() {
|
||||||
|
this.visible = true;
|
||||||
|
},
|
||||||
|
active(idx) {
|
||||||
|
if (this.activeIdx !== idx) {
|
||||||
|
this.activeIdx = idx;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
submit() {
|
||||||
|
const loadData = [];
|
||||||
|
this.fields.forEach(item => {
|
||||||
|
let val = (item.value || '').trim();
|
||||||
|
if (val) {
|
||||||
|
const data = {
|
||||||
|
attr: val,
|
||||||
|
to: item.field
|
||||||
|
};
|
||||||
|
if (item.modify) {
|
||||||
|
data.modify = true;
|
||||||
|
}
|
||||||
|
loadData.push(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.designer.setupState.activeRule._loadData = loadData;
|
||||||
|
this.visible = false;
|
||||||
|
},
|
||||||
|
confirm() {
|
||||||
|
const val = (this.variable || '').trim();
|
||||||
|
if (val) {
|
||||||
|
this.$emit('confirm', `{{${val}}}`);
|
||||||
|
this.$refs.pop.hide();
|
||||||
|
this.variable = '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fd-variable-btn {
|
||||||
|
margin-left: 6px;
|
||||||
|
background: var(--fc-style-bg-color-1);
|
||||||
|
border-radius: 5px;
|
||||||
|
color: var(--fc-style-color-1);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 2px 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-variable-con .el-main {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-variable-l, ._fd-variable-r {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
border: 1px solid var(--fc-line-color-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-variable-r {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-variable-l .el-header, ._fd-variable-r .el-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 40px;
|
||||||
|
background: var(--fc-bg-color-3);
|
||||||
|
color: var(--fc-text-color-1);
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-variable-r .el-header .fc-icon {
|
||||||
|
font-size: 13px;
|
||||||
|
margin-left: 2px;
|
||||||
|
color: var(--fc-style-color-1);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-variable-r .el-main {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-variable-info {
|
||||||
|
display: flex;
|
||||||
|
font-size: 12px;
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
padding: 8px 13px;
|
||||||
|
line-height: 18px;
|
||||||
|
background: rgba(170, 170, 170, 0.1);
|
||||||
|
border-radius: 6px;
|
||||||
|
color: var(--fc-text-color-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-variable-node {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
height: 26px;
|
||||||
|
line-height: 26px;
|
||||||
|
padding-right: 5px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--fc-text-color-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-variable-node > span {
|
||||||
|
color: var(--fc-text-color-3);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-variable-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 40px;
|
||||||
|
padding: 0 20px;
|
||||||
|
border-bottom: 1px solid var(--fc-line-color-3);;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-variable-top ._fd-variable-item-label {
|
||||||
|
color: #fca130;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-variable-item-label {
|
||||||
|
width: 198px;
|
||||||
|
margin-right: 18px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-variable-item .el-input {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
margin-top: 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-variable-item.active, ._fd-variable-item.active ._fd-variable-item-label, ._fd-variable-item.active input {
|
||||||
|
color: var(--fc-style-color-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-variable-item.active .el-input {
|
||||||
|
--el-input-icon-color: var(--fc-style-color-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-variable-item input {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-variable-item .el-input .el-input__wrapper {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-variable-pop.el-popover.el-popper {
|
||||||
|
height: 400px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-variable-pop-header .fc-icon {
|
||||||
|
color: var(--fc-style-color-1);
|
||||||
|
margin-left: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-variable-pop .el-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 1px solid var(--fc-line-color-3);
|
||||||
|
padding: 0 10px;
|
||||||
|
background-color: var(--fc-bg-color-2)
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-variable-pop .el-main {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-variable-pop .el-tree-node__content > .el-tree-node__expand-icon {
|
||||||
|
padding: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-variable-pop .el-input-group__append {
|
||||||
|
background: var(--fc-style-color-1);
|
||||||
|
color: #FFFFFF;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 60px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-variable-pop .el-input-group__append div {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-variable-pop-node {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
height: 26px;
|
||||||
|
line-height: 26px;
|
||||||
|
padding-right: 5px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-variable-pop-node > span {
|
||||||
|
color: var(--fc-text-color-3);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
200
src/components/dataSelect/DataSelect.vue
Normal file
200
src/components/dataSelect/DataSelect.vue
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_fc-data-select">
|
||||||
|
<el-select :disabled="disabled" :placeholder="placeholder"
|
||||||
|
:multiple="multiple" :multipleLimit="multipleLimit" :clearable="clearable"
|
||||||
|
:model-value="selectValue"
|
||||||
|
@update:modelValue="changeSelectValue"
|
||||||
|
popper-class="_fc-data-select-pop" @visible-change="handleClick" @clear="handleClear">
|
||||||
|
<template v-for="item in options" :key="item.value">
|
||||||
|
<el-option :label="item.label" :value="item.value"></el-option>
|
||||||
|
</template>
|
||||||
|
</el-select>
|
||||||
|
<FcDialog ref="dialog" :formCreateInject="formCreateInject" :rule="formRule" @update:modelValue="formChange"
|
||||||
|
:footer="multiple" :title="title" width="900px"
|
||||||
|
@confirm="confirm"></FcDialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
import FcDialog from '../dialog/Dialog.vue';
|
||||||
|
import {deepClone} from 'vant/es/utils/deep-clone';
|
||||||
|
import uniqueId from '@form-create/utils/lib/unique';
|
||||||
|
import {uniqueArray} from '../../utils';
|
||||||
|
import debounce from '@form-create/utils/lib/debounce';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'FcDataSelect',
|
||||||
|
components: {FcDialog},
|
||||||
|
emits: ['update:modelValue', 'change', 'clear'],
|
||||||
|
props: {
|
||||||
|
title: String,
|
||||||
|
formCreateInject: Object,
|
||||||
|
placeholder: String,
|
||||||
|
multiple: Boolean,
|
||||||
|
disabled: Boolean,
|
||||||
|
multipleLimit: Number,
|
||||||
|
valueKey: String,
|
||||||
|
labelKey: String,
|
||||||
|
clearable: Boolean,
|
||||||
|
searchRule: Object,
|
||||||
|
tableRule: Object,
|
||||||
|
autoLoad: Boolean,
|
||||||
|
modelValue: [Object, Array],
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
options() {
|
||||||
|
return this.list.map((item) => {
|
||||||
|
return {
|
||||||
|
label: item[this.labelKey || 'label'],
|
||||||
|
value: item[this.valueKey || 'value'],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
selectValue() {
|
||||||
|
const value = this.options.map((item) => {
|
||||||
|
return item.value;
|
||||||
|
})
|
||||||
|
return this.multiple === true ? value : value[0];
|
||||||
|
},
|
||||||
|
fapi() {
|
||||||
|
return this.$refs?.dialog?.fapi;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
list: [],
|
||||||
|
visible: false,
|
||||||
|
formRule: [],
|
||||||
|
load: debounce(() => {
|
||||||
|
this.$refs.dialog.fapi.el(this.formRule[1].name).initPage();
|
||||||
|
}, 500)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
modelValue: {
|
||||||
|
handler(n) {
|
||||||
|
if (n) {
|
||||||
|
this.list = Array.isArray(n) ? n : [n];
|
||||||
|
} else {
|
||||||
|
this.list = [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getTableEl() {
|
||||||
|
return this.$refs.dialog.fapi.el(this.formRule[1].name);
|
||||||
|
},
|
||||||
|
getDialogEl() {
|
||||||
|
return this.$refs.dialog;
|
||||||
|
},
|
||||||
|
formChange() {
|
||||||
|
if (this.autoLoad) {
|
||||||
|
this.load();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
changeSelectValue(value) {
|
||||||
|
if (value == null) {
|
||||||
|
this.list = [];
|
||||||
|
} else {
|
||||||
|
this.list = this.list.filter((item) => {
|
||||||
|
return value.indexOf(item[this.valueKey || 'value']) !== -1;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.updateValue();
|
||||||
|
},
|
||||||
|
confirm() {
|
||||||
|
const list = uniqueArray([...this.list, ...this.$refs.dialog.fapi.el(this.formRule[1].name).getEl().getSelectionRows()]);
|
||||||
|
const keys = list.map((item) => {
|
||||||
|
return item[this.valueKey || 'value'];
|
||||||
|
});
|
||||||
|
this.list = list.filter((item, idx) => keys.indexOf(item[this.valueKey || 'value']) === idx);
|
||||||
|
this.$refs.dialog.close();
|
||||||
|
this.updateValue();
|
||||||
|
},
|
||||||
|
tableRowClick(value) {
|
||||||
|
if (this.multiple) {
|
||||||
|
this.$refs.dialog.fapi.el(this.formRule[1].name).getEl().toggleRowSelection(value);
|
||||||
|
} else {
|
||||||
|
this.list = [value];
|
||||||
|
this.updateValue();
|
||||||
|
this.$refs.dialog.close();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateValue() {
|
||||||
|
if (this.multipleLimit > 0 && this.list.length > this.multipleLimit) {
|
||||||
|
this.list = this.list.splice(0, this.multipleLimit);
|
||||||
|
}
|
||||||
|
const value = this.list.map((item) => {
|
||||||
|
return {
|
||||||
|
[this.valueKey || 'value']: item[this.valueKey || 'value'],
|
||||||
|
[this.labelKey || 'label']: item[this.labelKey || 'label'],
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.$emit('update:modelValue', this.multiple ? value : value[0]);
|
||||||
|
this.$emit('change', this.multiple ? value : value[0]);
|
||||||
|
},
|
||||||
|
getFormRule() {
|
||||||
|
const formRule = deepClone([
|
||||||
|
this.searchRule,
|
||||||
|
this.tableRule
|
||||||
|
]);
|
||||||
|
if (!formRule[1].on) {
|
||||||
|
formRule[1].on = {};
|
||||||
|
}
|
||||||
|
if (!formRule[1].props) {
|
||||||
|
formRule[1].props = {};
|
||||||
|
}
|
||||||
|
if (this.multiple) {
|
||||||
|
if (!formRule[1].props.rowKey) {
|
||||||
|
formRule[1].props.rowKey = this.valueKey || 'value';
|
||||||
|
}
|
||||||
|
if (!formRule[1].name) {
|
||||||
|
formRule[1].name = uniqueId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
formRule[1].props.selection = true;
|
||||||
|
if (formRule[1].on.rowClick) {
|
||||||
|
formRule[1].on.rowClick = [(...args) => this.tableRowClick(...args), formRule[1].on.rowClick];
|
||||||
|
} else {
|
||||||
|
formRule[1].on.rowClick = (...args) => this.tableRowClick(...args)
|
||||||
|
}
|
||||||
|
if (formRule[1].on.selectionChange) {
|
||||||
|
formRule[1].on.selectionChange = [(...args) => this.selectionChange(...args), formRule[1].on.selectionChange];
|
||||||
|
} else {
|
||||||
|
formRule[1].on.selectionChange = (...args) => this.selectionChange(...args)
|
||||||
|
}
|
||||||
|
return formRule;
|
||||||
|
},
|
||||||
|
selectionChange(value) {
|
||||||
|
if (!this.multiple) {
|
||||||
|
this.tableRowClick(value[0]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleClear(...args) {
|
||||||
|
this.$emit('clear', ...args);
|
||||||
|
},
|
||||||
|
handleClick(flag) {
|
||||||
|
if (this.disabled || !flag) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.visible = true;
|
||||||
|
this.formRule = this.getFormRule();
|
||||||
|
this.$refs.dialog.open();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fc-data-select-pop {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-data-select {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
62
src/components/dataSelect/DataSelectView.vue
Normal file
62
src/components/dataSelect/DataSelectView.vue
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_fd-data-select" :style="{'--fc-drag-empty': `'${t('com.select.emptyText')}'`}">
|
||||||
|
<el-select @click.capture="handleClick" :disabled="disabled" :placeholder="placeholder"></el-select>
|
||||||
|
<div class="el-dialog" v-if="visible">
|
||||||
|
<header class="el-dialog__header show-close">
|
||||||
|
<span class="el-dialog__title">{{ title }}</span>
|
||||||
|
</header>
|
||||||
|
<slot name="search"></slot>
|
||||||
|
<slot name="table"></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
import DataTable from '../dataTable/DataTable.vue';
|
||||||
|
import FcInlineForm from '../InlineForm.vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'FcDataSelectView',
|
||||||
|
components: {FcInlineForm, DataTable},
|
||||||
|
inject: ['designer'],
|
||||||
|
props: {
|
||||||
|
title: String,
|
||||||
|
placeholder: String,
|
||||||
|
multiple: Boolean,
|
||||||
|
disabled: Boolean,
|
||||||
|
multipleLimit: Number,
|
||||||
|
valueKey: String,
|
||||||
|
labelKey: String,
|
||||||
|
clearable: Boolean,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
t() {
|
||||||
|
return this.designer.setupState.t;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
visible: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleClick(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
this.visible = !this.visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fd-data-select {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-data-select > .el-dialog {
|
||||||
|
width: calc(100% - 20px);
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
412
src/components/dataTable/DataTable.vue
Normal file
412
src/components/dataTable/DataTable.vue
Normal file
@ -0,0 +1,412 @@
|
|||||||
|
<script>
|
||||||
|
import {defineComponent, h, resolveComponent, resolveDirective, withDirectives} from 'vue';
|
||||||
|
import {parseFn} from '@form-create/utils/lib/json';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'DataTable',
|
||||||
|
emits: ['sortChange', 'handleClick'],
|
||||||
|
props: {
|
||||||
|
column: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
globalDataKey: [String, Object],
|
||||||
|
fetch: Object,
|
||||||
|
data: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
button: Object,
|
||||||
|
index: Boolean,
|
||||||
|
selection: Boolean,
|
||||||
|
page: Object,
|
||||||
|
formCreateInject: Object,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
total: 0,
|
||||||
|
loading: false,
|
||||||
|
unwatch: null,
|
||||||
|
list: [],
|
||||||
|
currentPage: 1,
|
||||||
|
id: 1,
|
||||||
|
order: '',
|
||||||
|
orderBy: '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
globalDataKey() {
|
||||||
|
this.initPage();
|
||||||
|
},
|
||||||
|
fetch() {
|
||||||
|
if (!this.globalDataKey) {
|
||||||
|
this.initPage();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
if (!this.globalDataKey && !this.fetch) {
|
||||||
|
this.initPage();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selection() {
|
||||||
|
this.id++;
|
||||||
|
},
|
||||||
|
index() {
|
||||||
|
this.id++;
|
||||||
|
},
|
||||||
|
page: {
|
||||||
|
handler() {
|
||||||
|
this.initPage();
|
||||||
|
this.id++;
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
handler() {
|
||||||
|
this.id++;
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
filterList() {
|
||||||
|
let data = this.list || [];
|
||||||
|
const filters = [];
|
||||||
|
this.column.forEach(item => {
|
||||||
|
if (item.prop && Array.isArray(item.filter) && item.filter.length > 0) {
|
||||||
|
filters.push((v => {
|
||||||
|
return item.filter.indexOf(v[item.prop]) > -1;
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
filters.forEach(fn => {
|
||||||
|
data = data.filter(fn);
|
||||||
|
})
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
return withDirectives(h('div', {
|
||||||
|
class: '_fc-data-table'
|
||||||
|
}, [
|
||||||
|
h(resolveComponent('el-table'), {
|
||||||
|
data: this.filterList,
|
||||||
|
...this.$attrs,
|
||||||
|
key: this.id,
|
||||||
|
ref: 'table',
|
||||||
|
onSortChange: (data) => {
|
||||||
|
this.$emit('sortChange', data);
|
||||||
|
if (data.order) {
|
||||||
|
this.orderBy = data.order === 'descending' ? 'DESC' : 'ASC';
|
||||||
|
this.order = data.prop
|
||||||
|
} else {
|
||||||
|
this.orderBy = '';
|
||||||
|
this.order = '';
|
||||||
|
}
|
||||||
|
this.initPage();
|
||||||
|
}
|
||||||
|
}, () => {
|
||||||
|
const cols = this.column.filter(col => col.hidden !== true).map(col => {
|
||||||
|
return this.makeColumn(col);
|
||||||
|
})
|
||||||
|
if (this.selection) {
|
||||||
|
cols.unshift(h(resolveComponent('el-table-column'), {
|
||||||
|
type: 'selection',
|
||||||
|
width: '50px'
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
const btns = this.makeButtonCol();
|
||||||
|
if (btns) {
|
||||||
|
cols.push(btns);
|
||||||
|
}
|
||||||
|
if (this.index) {
|
||||||
|
cols.unshift(h(resolveComponent('el-table-column'), {
|
||||||
|
type: 'index',
|
||||||
|
width: '50px'
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
return cols;
|
||||||
|
}),
|
||||||
|
this.makePage()
|
||||||
|
]), [
|
||||||
|
[resolveDirective('loading'), this.loading]
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getEl() {
|
||||||
|
return this.$refs.table;
|
||||||
|
},
|
||||||
|
deepGet(object, path, defaultValue) {
|
||||||
|
path = (path || '').split('.');
|
||||||
|
let index = 0,
|
||||||
|
length = path.length;
|
||||||
|
while (object != null && index < length) {
|
||||||
|
object = object[path[index++]];
|
||||||
|
}
|
||||||
|
return (index && index === length) ? (object != null ? object : defaultValue) : defaultValue;
|
||||||
|
},
|
||||||
|
initPage() {
|
||||||
|
this.loading = false;
|
||||||
|
if (this.page && this.page.open) {
|
||||||
|
this.currentPage = 1;
|
||||||
|
this.nextList();
|
||||||
|
} else {
|
||||||
|
if (this.globalDataKey || this.fetch) {
|
||||||
|
this.fetchData().then(({list}) => {
|
||||||
|
this.list = list;
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.list = this.data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
btnProps(btn, scope) {
|
||||||
|
const prop = btn.prop || [];
|
||||||
|
const props = {
|
||||||
|
type: btn.type,
|
||||||
|
size: btn.size,
|
||||||
|
round: prop.indexOf('round') > -1,
|
||||||
|
link: prop.indexOf('link') > -1,
|
||||||
|
plain: prop.indexOf('plain') > -1,
|
||||||
|
disabled: prop.indexOf('disabled') > -1,
|
||||||
|
onClick: (evt) => {
|
||||||
|
evt.stopPropagation();
|
||||||
|
const fn = parseFn(btn.click);
|
||||||
|
try {
|
||||||
|
fn && fn(scope, this.formCreateInject.api);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
this.$emit('handleClick', {name: btn.name, key: btn.key, scope, column: scope.row});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const fn = parseFn(btn.handle);
|
||||||
|
try {
|
||||||
|
const res = fn && fn(props, scope, this.formCreateInject.api);
|
||||||
|
if (typeof res === 'boolean') {
|
||||||
|
props.disabled = res;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return props;
|
||||||
|
},
|
||||||
|
getLimit() {
|
||||||
|
return (this.page.props && this.page.props.pageSize) || 20;
|
||||||
|
},
|
||||||
|
nextList() {
|
||||||
|
if (this.globalDataKey || this.fetch) {
|
||||||
|
this.fetchData(true).then(({list, total}) => {
|
||||||
|
this.list = list;
|
||||||
|
this.total = total;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const data = this.data;
|
||||||
|
const limit = this.getLimit();
|
||||||
|
const end = this.currentPage * limit;
|
||||||
|
this.list = data.slice(end - limit, end);
|
||||||
|
this.total = data.length;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fetchData(page) {
|
||||||
|
this.unwatch && this.unwatch();
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
let config = this.fetch;
|
||||||
|
if (this.globalDataKey) {
|
||||||
|
const key = typeof this.globalDataKey === 'string' ? this.globalDataKey : this.globalDataKey.key;
|
||||||
|
config = this.formCreateInject.api.options.globalData[key];
|
||||||
|
}
|
||||||
|
if (config) {
|
||||||
|
if (config.type === 'fetch' || !this.globalDataKey) {
|
||||||
|
config = {...config};
|
||||||
|
let params = {};
|
||||||
|
if (page) {
|
||||||
|
const limit = (this.page.props && this.page.props.pageSize) || 20;
|
||||||
|
const pageField = this.page.pageField || 'page';
|
||||||
|
const pageSizeField = this.page.pageSizeField || 'limit';
|
||||||
|
params = {
|
||||||
|
[pageField]: this.currentPage,
|
||||||
|
[pageSizeField]: limit,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.order) {
|
||||||
|
const orderField = this.page.orderField || 'order';
|
||||||
|
const orderByField = this.page.orderByField || 'orderBy';
|
||||||
|
params[orderField] = this.order;
|
||||||
|
params[orderByField] = this.orderBy;
|
||||||
|
}
|
||||||
|
const str = Object.keys(params).map(k => {
|
||||||
|
return encodeURIComponent(k) + '=' + encodeURIComponent(params[k]);
|
||||||
|
}, '').join('&');
|
||||||
|
if (str) {
|
||||||
|
config.action += (config.action.indexOf('?') !== -1 ? '&' : '?') + str;
|
||||||
|
}
|
||||||
|
this.loading = true;
|
||||||
|
config.wait = 1000;
|
||||||
|
this.unwatch = this.formCreateInject.api.watchFetch(config, (res, change) => {
|
||||||
|
this.loading = false;
|
||||||
|
const totalField = this.page.totalField;
|
||||||
|
const dataField = this.page.dataField;
|
||||||
|
const list = dataField ? this.deepGet(res, dataField, []) : res;
|
||||||
|
let total = totalField ? this.deepGet(res, totalField) : 0;
|
||||||
|
if (!total) {
|
||||||
|
total = list.length || 0;
|
||||||
|
}
|
||||||
|
resolve({list, total});
|
||||||
|
}, (e) => {
|
||||||
|
console.error(e);
|
||||||
|
this.loading = false;
|
||||||
|
}, (opt, change) => {
|
||||||
|
if (change) {
|
||||||
|
this.unwatch && this.unwatch();
|
||||||
|
this.unwatch = null;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.changePage(1);
|
||||||
|
})
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
let list = config.data || [];
|
||||||
|
let total = config.data.length;
|
||||||
|
if (page) {
|
||||||
|
const limit = this.getLimit();
|
||||||
|
const end = this.currentPage * limit;
|
||||||
|
list = list.slice(end - limit, end);
|
||||||
|
total = list.length;
|
||||||
|
}
|
||||||
|
resolve({list, total});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
resolve({list: [], total: 0});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
changePage(n) {
|
||||||
|
this.currentPage = n;
|
||||||
|
this.nextList();
|
||||||
|
},
|
||||||
|
makePage() {
|
||||||
|
if (this.page && this.page.open === true) {
|
||||||
|
return h(resolveComponent('el-pagination'), {
|
||||||
|
layout: 'prev, pager, next',
|
||||||
|
total: this.total,
|
||||||
|
currentPage: this.currentPage,
|
||||||
|
'onUpdate:currentPage': (n) => {
|
||||||
|
if (this.currentPage !== n) {
|
||||||
|
this.changePage(n);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
class: (this.page.position || 'right'),
|
||||||
|
...(this.page.props || {}),
|
||||||
|
pageSize: (this.page.props && this.page.props.pageSize) || 20
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
makeButtonCol() {
|
||||||
|
if (this.button && this.button.open === true && this.button.column) {
|
||||||
|
return h(resolveComponent('el-table-column'), {
|
||||||
|
label: this.button.label || this.formCreateInject.t('operation') || '操作',
|
||||||
|
fixed: this.button.fixed === undefined ? 'right' : this.button.fixed,
|
||||||
|
width: this.button.width || '100px',
|
||||||
|
}, {
|
||||||
|
default: (scope) => {
|
||||||
|
return this.button.column.filter(btn => btn.hidden !== true).map(btn => {
|
||||||
|
return h(resolveComponent('el-button'), this.btnProps(btn, scope), () => [btn.name]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
makeColumn(col) {
|
||||||
|
return h(resolveComponent('el-table-column'), {
|
||||||
|
label: col.label,
|
||||||
|
prop: col.prop,
|
||||||
|
width: col.width,
|
||||||
|
align: col.align,
|
||||||
|
className: col.className,
|
||||||
|
fixed: col.fixed,
|
||||||
|
sortable: col.sortable,
|
||||||
|
}, {
|
||||||
|
default: (scope) => {
|
||||||
|
if (col.children && col.children.length > 0) {
|
||||||
|
return col.children.map(child => {
|
||||||
|
return this.makeColumn(child);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!col.format || col.format === 'default') {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return this.makeTd(col, scope);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
makeTd(col, scope) {
|
||||||
|
if (col.format === 'custom' && col.render) {
|
||||||
|
return col.render(scope, h, resolveComponent, this.formCreateInject.api);
|
||||||
|
} else if (col.format === 'tag') {
|
||||||
|
return h(resolveComponent('el-tag'), {disableTransitions: true}, () => [this.deepGet(scope.row, col.prop, '')]);
|
||||||
|
} else if (col.format === 'image') {
|
||||||
|
return h('div', {
|
||||||
|
class: '_fc-data-table-img-list'
|
||||||
|
}, (() => {
|
||||||
|
let img = this.deepGet(scope.row, col.prop, '');
|
||||||
|
img = (Array.isArray(img) ? img : [img]).filter(src => !!src);
|
||||||
|
return img.map((src, i) => {
|
||||||
|
return h(resolveComponent('el-image'), {
|
||||||
|
src: src,
|
||||||
|
previewSrcList: img,
|
||||||
|
previewTeleported: true,
|
||||||
|
initialIndex: i,
|
||||||
|
fit: 'cover'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})())
|
||||||
|
} else {
|
||||||
|
return '' + this.deepGet(scope.row, col.prop, '')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.initPage();
|
||||||
|
this.$watch(() => this.data && this.data.length, () => {
|
||||||
|
if (!this.globalDataKey && !this.fetch) {
|
||||||
|
this.initPage();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fc-data-table {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-data-table .el-table {
|
||||||
|
--el-table-header-bg-color: #e8eefc;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-data-table .el-pagination {
|
||||||
|
display: flex;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-data-table .el-pagination.left {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-data-table .el-pagination.center {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-data-table .el-pagination.right {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-data-table ._fc-data-table-img-list .el-image {
|
||||||
|
max-width: 150px;
|
||||||
|
height: 60px;
|
||||||
|
}
|
||||||
|
</style>
|
248
src/components/dataTable/TableButtonConfig.vue
Normal file
248
src/components/dataTable/TableButtonConfig.vue
Normal file
@ -0,0 +1,248 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_fd-table-button-config">
|
||||||
|
<el-badge type="warning" is-dot :hidden="!configured">
|
||||||
|
<el-button class="_fd-plain-button" plain @click="visible=true" size="small">{{ t('com.dataTable.button.btn') }}</el-button>
|
||||||
|
</el-badge>
|
||||||
|
<el-dialog class="_fd-tcb-dialog _fd-config-dialog" :title="t('com.dataTable.button.title')" v-model="visible" destroy-on-close
|
||||||
|
:close-on-click-modal="false" append-to-body
|
||||||
|
width="980px">
|
||||||
|
<template v-if="activeRow">
|
||||||
|
<FnEditor ref="fn" v-model="activeRow[activeKey]" :args="activeArgs"
|
||||||
|
:name="activeKey"></FnEditor>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<el-table :data="column" size="small">
|
||||||
|
<el-table-column type="index" width="50"/>
|
||||||
|
<el-table-column :label="t('props.preview')" width="100">
|
||||||
|
<template #default="{row}">
|
||||||
|
<el-button v-bind="btnProps(row)">{{ row.name }}</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column width="100">
|
||||||
|
<template #default="{row}">
|
||||||
|
<el-input v-model="row.key"></el-input>
|
||||||
|
</template>
|
||||||
|
<template #header>
|
||||||
|
ID<span style="color:red;">*</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column>
|
||||||
|
<template #default="{row}">
|
||||||
|
<el-input v-model="row.name"></el-input>
|
||||||
|
</template>
|
||||||
|
<template #header>
|
||||||
|
{{ t('props.name') }}<span style="color:red;">*</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column :label="t('event.type')" width="120">
|
||||||
|
<template #default="{row}">
|
||||||
|
<el-select v-model="row.type">
|
||||||
|
<el-option v-for="opt in type"
|
||||||
|
:label="opt.label"
|
||||||
|
:value="opt.value"
|
||||||
|
:key="opt.value"></el-option>
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column :label="t('style.font.size')" width="120">
|
||||||
|
<template #default="{row}">
|
||||||
|
<el-select v-model="row.size">
|
||||||
|
<el-option v-for="opt in size"
|
||||||
|
:label="opt.label"
|
||||||
|
:value="opt.value"
|
||||||
|
:key="opt.value"></el-option>
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column :label="t('style.decoration.name')" width="120">
|
||||||
|
<template #default="{row}">
|
||||||
|
<el-select multiple v-model="row.prop">
|
||||||
|
<el-option v-for="opt in decoration"
|
||||||
|
:label="opt.label"
|
||||||
|
:value="opt.value"
|
||||||
|
:key="opt.value"></el-option>
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column :label="t('props.hide')" width="80">
|
||||||
|
<template #default="{row}">
|
||||||
|
<el-switch v-model="row.hidden"></el-switch>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column :label="t('props.callback')" width="80">
|
||||||
|
<template #default="{row}">
|
||||||
|
<div class="_fd-tcb-btn" @click="handle(row, 'handle', ['props', 'scope', 'api'])">{{ t('com.dataTable.handle') }}<i class="fc-icon icon-edit"></i>
|
||||||
|
</div>
|
||||||
|
<div class="_fd-tcb-btn" @click="handle(row, 'click', ['scope', 'api'])">{{ t('com.dataTable.click') }}<i class="fc-icon icon-edit"></i>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column :label="t('tableOptions.handle')" width="80">
|
||||||
|
<template #default="{$index}">
|
||||||
|
<i class="fc-icon icon-add-circle" @click="add($index)"></i>
|
||||||
|
<i class="fc-icon icon-delete-circle" @click="remove($index)"></i>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<el-button link type="primary" @click="add()">
|
||||||
|
<i class="fc-icon icon-add-circle"></i>
|
||||||
|
{{ t('tableOptions.add') }}
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
<template #footer>
|
||||||
|
<div>
|
||||||
|
<el-button size="default" @click="close">{{ t('props.cancel') }}</el-button>
|
||||||
|
<el-button type="primary" size="default" @click="submit">{{
|
||||||
|
t('props.ok')
|
||||||
|
}}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
import errorMessage from '../../utils/message';
|
||||||
|
import {deepCopy} from '@form-create/utils/lib/deepextend';
|
||||||
|
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'TableColumnConfig',
|
||||||
|
props: {
|
||||||
|
modelValue: Array,
|
||||||
|
},
|
||||||
|
inject: ['designer'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
column: [],
|
||||||
|
visible: false,
|
||||||
|
activeRow: null,
|
||||||
|
activeKey: '',
|
||||||
|
activeArgs: [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
t() {
|
||||||
|
return this.designer.setupState.t;
|
||||||
|
},
|
||||||
|
configured() {
|
||||||
|
return !!this.modelValue
|
||||||
|
},
|
||||||
|
decoration() {
|
||||||
|
return ['link', 'round', 'plain', 'disabled'].map(v => {
|
||||||
|
return {label: this.t('com.dataTable.button.' + v), value: v};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
size() {
|
||||||
|
return ['large', 'default', 'small'].map(v => {
|
||||||
|
return {label: this.t('props.' + v), value: v};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
type() {
|
||||||
|
return ['primary', 'success', 'warning', 'danger', 'info'].map(v => {
|
||||||
|
return {label: this.t('props.' + v), value: v};
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
visible(v) {
|
||||||
|
if (v) {
|
||||||
|
this.tidyValue();
|
||||||
|
} else {
|
||||||
|
this.activeRow = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
btnProps(row) {
|
||||||
|
const prop = row.prop || [];
|
||||||
|
return {
|
||||||
|
type: row.type,
|
||||||
|
size: row.size,
|
||||||
|
round: prop.indexOf('round') > -1,
|
||||||
|
link: prop.indexOf('link') > -1,
|
||||||
|
plain: prop.indexOf('plain') > -1,
|
||||||
|
disabled: prop.indexOf('disabled') > -1,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
defaultData() {
|
||||||
|
return {
|
||||||
|
key: this.column.length + 1,
|
||||||
|
name: this.t('props.button') + (this.column.length + 1)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
add(idx) {
|
||||||
|
idx != null ? this.column.splice(idx + 1, 0, this.defaultData()) : this.column.push(this.defaultData());
|
||||||
|
},
|
||||||
|
handle(rule, key, args) {
|
||||||
|
this.activeKey = key;
|
||||||
|
this.activeRow = rule;
|
||||||
|
this.activeArgs = args;
|
||||||
|
},
|
||||||
|
remove(idx) {
|
||||||
|
this.column.splice(idx, 1);
|
||||||
|
},
|
||||||
|
tidyValue() {
|
||||||
|
this.column = deepCopy(this.modelValue || []);
|
||||||
|
if (!this.column.length) {
|
||||||
|
this.add();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
if (this.activeRow) {
|
||||||
|
this.activeRow = null;
|
||||||
|
} else {
|
||||||
|
this.visible = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
submit() {
|
||||||
|
if (this.activeRow) {
|
||||||
|
if (this.$refs.fn.save()) {
|
||||||
|
this.activeRow = null;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const value = [];
|
||||||
|
for (let i = 0; i < this.column.length; i++) {
|
||||||
|
const col = this.column[i];
|
||||||
|
if (!col.name) {
|
||||||
|
errorMessage(this.t('com.dataTable.requiredName'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!col.key) {
|
||||||
|
errorMessage(this.t('com.dataTable.requiredKey'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
value.push({...col});
|
||||||
|
}
|
||||||
|
this.$emit('update:modelValue', value);
|
||||||
|
this.$emit('change', value);
|
||||||
|
this.visible = false;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fd-table-button-config, ._fd-table-button-config .el-badge {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-table-button-config .el-button {
|
||||||
|
font-weight: 400;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-tcb-dialog .el-dialog__body {
|
||||||
|
height: 500px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-tcb-btn{
|
||||||
|
display: flex;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
359
src/components/dataTable/TableColumnConfig.vue
Normal file
359
src/components/dataTable/TableColumnConfig.vue
Normal file
@ -0,0 +1,359 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_fd-table-column-config">
|
||||||
|
<el-badge type="warning" is-dot :hidden="!configured">
|
||||||
|
<el-button class="_fd-plain-button" plain @click="visible=true" size="small">
|
||||||
|
{{ t('com.dataTable.column.btn') }}
|
||||||
|
</el-button>
|
||||||
|
</el-badge>
|
||||||
|
<el-dialog class="_fd-tcc-dialog _fd-config-dialog" :title="t('com.dataTable.column.title')" v-model="visible"
|
||||||
|
destroy-on-close
|
||||||
|
:close-on-click-modal="false" append-to-body
|
||||||
|
width="980px">
|
||||||
|
<template v-if="activeRow">
|
||||||
|
<FnEditor ref="fn" v-model="activeRow.render" :args="['scope', 'h',' resolveComponent', 'api']"
|
||||||
|
name="render"></FnEditor>
|
||||||
|
</template>
|
||||||
|
<el-table v-show="!activeRow" :data="column" size="small" row-key="id" class="_fd-tcc-table">
|
||||||
|
<el-table-column type="index" width="50"/>
|
||||||
|
<el-table-column :label="t('com.dataTable.column.prop')" width="130">
|
||||||
|
<template #default="{row}">
|
||||||
|
<template v-if="!row.children || !row.children.length">
|
||||||
|
<el-input v-model="row.prop" v-if="!propColumns || !propColumns.length"></el-input>
|
||||||
|
<el-select v-model="row.prop" allow-create clearable default-first-option filterable
|
||||||
|
v-else>
|
||||||
|
<template v-for="value in propColumns">
|
||||||
|
<el-option :label="value" :value="value">{{ value }}</el-option>
|
||||||
|
</template>
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
{{ t('com.dataTable.header') }}
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column width="100">
|
||||||
|
<template #default="{row}">
|
||||||
|
<el-input v-model="row.label"></el-input>
|
||||||
|
</template>
|
||||||
|
<template #header>
|
||||||
|
{{ t('props.title') }}<span style="color:red;">*</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column :label="t('style.width')" width="100">
|
||||||
|
<template #default="{row}">
|
||||||
|
<el-input v-model="row.width" v-if="!row.children || !row.children.length"></el-input>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column :label="t('com.dataTable.filter')" width="120">
|
||||||
|
<template #default="{row}">
|
||||||
|
<el-select v-model="row.filter" multiple v-if="!row.children || !row.children.length" clearable>
|
||||||
|
<template v-for="value in getColumnData(row.prop)">
|
||||||
|
<el-option :label="value" :value="value">{{ value }}</el-option>
|
||||||
|
</template>
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="Class">
|
||||||
|
<template #default="{row}">
|
||||||
|
<el-input v-model="row.className"></el-input>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column :label="t('com.dataTable.column.sort')" width="100">
|
||||||
|
<template #default="{row}">
|
||||||
|
<el-select v-model="row.sortable" v-if="!row.children || !row.children.length" clearable>
|
||||||
|
<el-option v-for="opt in sortable"
|
||||||
|
:label="opt.label"
|
||||||
|
:value="opt.value"
|
||||||
|
:key="opt.value"></el-option>
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column :label="t('props.position')" width="100">
|
||||||
|
<template #default="{row}">
|
||||||
|
<el-select v-model="row.fixed" clearable>
|
||||||
|
<el-option v-for="opt in fixed"
|
||||||
|
:label="opt.label"
|
||||||
|
:value="opt.value"
|
||||||
|
:key="opt.value || 'default'"></el-option>
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column :label="t('style.font.align')" width="100">
|
||||||
|
<template #default="{row}">
|
||||||
|
<el-select v-model="row.align" clearable>
|
||||||
|
<el-option v-for="opt in align"
|
||||||
|
:label="opt.label"
|
||||||
|
:value="opt.value"
|
||||||
|
:key="opt.value"></el-option>
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column width="110">
|
||||||
|
<template #default="{row}">
|
||||||
|
<div class="flex">
|
||||||
|
<el-select v-model="row.format" clearable>
|
||||||
|
<el-option v-for="opt in format" :label="opt.label" :value="opt.value"
|
||||||
|
:key="opt.value"></el-option>
|
||||||
|
</el-select>
|
||||||
|
<i class="fc-icon icon-edit" v-if="row.format === 'custom'" @click="editFn(row)"></i>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #header>
|
||||||
|
{{ t('props.render') }}<span style="color:red;">*</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column :label="t('props.hide')" width="50" fixed="right">
|
||||||
|
<template #default="{row}">
|
||||||
|
<el-switch v-model="row.hidden" v-if="!row.children || !row.children.length"></el-switch>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column :label="t('tableOptions.handle')" width="90" fixed="right">
|
||||||
|
<template #default="{row, $index}">
|
||||||
|
<i class="fc-icon icon-add-circle" @click="add($index)"></i>
|
||||||
|
<i class="fc-icon icon-add-child" @click="addChild(row)"></i>
|
||||||
|
<i class="fc-icon icon-delete-circle" @click="remove(row)"></i>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<el-button v-show="!activeRow" link type="primary" @click="add()">
|
||||||
|
<i class="fc-icon icon-add-circle"></i>
|
||||||
|
{{ t('tableOptions.add') }}
|
||||||
|
</el-button>
|
||||||
|
<template #footer>
|
||||||
|
<div>
|
||||||
|
<el-button size="default" @click="close">{{ t('props.cancel') }}</el-button>
|
||||||
|
<el-button type="primary" size="default" @click="submit">{{
|
||||||
|
t('props.ok')
|
||||||
|
}}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
import errorMessage from '../../utils/message';
|
||||||
|
import {deepCopy} from '@form-create/utils/lib/deepextend';
|
||||||
|
import {hasProperty} from '@form-create/utils/lib/type';
|
||||||
|
import FnEditor from '../FnEditor.vue';
|
||||||
|
import uniqueId from '@form-create/utils/lib/unique';
|
||||||
|
import {parseFn} from '@form-create/utils/lib/json';
|
||||||
|
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'TableColumnConfig',
|
||||||
|
components: {FnEditor},
|
||||||
|
props: {
|
||||||
|
modelValue: Array,
|
||||||
|
},
|
||||||
|
inject: ['designer'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
column: [],
|
||||||
|
visible: false,
|
||||||
|
activeRow: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
t() {
|
||||||
|
return this.designer.setupState.t;
|
||||||
|
},
|
||||||
|
configured() {
|
||||||
|
return !!this.modelValue
|
||||||
|
},
|
||||||
|
list() {
|
||||||
|
return (this.designer.setupState.activeRule?.__fc__.el.list) || [];
|
||||||
|
},
|
||||||
|
propColumns() {
|
||||||
|
return Object.keys(this.list[0] || {});
|
||||||
|
},
|
||||||
|
format() {
|
||||||
|
return ['default', 'tag', 'image', 'custom'].map(v => {
|
||||||
|
return {label: this.t('com.dataTable.format.' + v), value: v};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
align() {
|
||||||
|
return ['left', 'center', 'right'].map(v => {
|
||||||
|
return {label: this.t('props.' + v), value: v};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
fixed() {
|
||||||
|
return [false, 'left', 'right'].map(v => {
|
||||||
|
return {label: this.t('com.dataTable.fixed.' + (v || 'default')), value: v};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
sortable() {
|
||||||
|
return [false, true, 'custom'].map(v => {
|
||||||
|
return {
|
||||||
|
label: this.t('com.dataTable.sortable.' + (typeof v === 'boolean' ? (v ? 'default' : 'disabled') : 'custom')),
|
||||||
|
value: v
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
visible(v) {
|
||||||
|
if (v) {
|
||||||
|
this.tidyValue();
|
||||||
|
} else {
|
||||||
|
this.activeRow = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getColumnData(prop) {
|
||||||
|
const data = [];
|
||||||
|
if (!prop) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
(this.list || []).forEach(v => {
|
||||||
|
if (hasProperty(v, prop) && data.indexOf(v[prop]) === -1) {
|
||||||
|
data.push(v[prop]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
add(idx) {
|
||||||
|
const item = {format: 'default', filter: [], id: uniqueId()};
|
||||||
|
idx != null ? this.column.splice(idx + 1, 0, item) : this.column.push(item);
|
||||||
|
},
|
||||||
|
addChild(column) {
|
||||||
|
const item = {p: column, format: 'default', filter: [], id: uniqueId()};
|
||||||
|
if (!column.children) {
|
||||||
|
column.children = [];
|
||||||
|
}
|
||||||
|
column.children.push(item);
|
||||||
|
},
|
||||||
|
remove(column) {
|
||||||
|
const columns = (column.p && column.p.children) || this.column;
|
||||||
|
columns.splice(columns.indexOf(column), 1);
|
||||||
|
if (column.p && !columns.length) {
|
||||||
|
delete column.p.children;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
editFn(row) {
|
||||||
|
this.activeRow = row;
|
||||||
|
},
|
||||||
|
updateFn() {
|
||||||
|
this.activeRow = null;
|
||||||
|
},
|
||||||
|
tidyValue() {
|
||||||
|
this.column = this.fullId(deepCopy(this.modelValue || []));
|
||||||
|
if (!this.column.length) {
|
||||||
|
this.add();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fullId(columns, p) {
|
||||||
|
columns.map(column => {
|
||||||
|
if (!column.id) {
|
||||||
|
column.id = uniqueId();
|
||||||
|
}
|
||||||
|
column.p = p;
|
||||||
|
if (column.children) {
|
||||||
|
this.fullId(column.children, column);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return columns;
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
if (this.activeRow) {
|
||||||
|
this.activeRow = null;
|
||||||
|
} else {
|
||||||
|
this.visible = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
parseColumns(columns) {
|
||||||
|
return columns.map(col => {
|
||||||
|
const temp = {...col};
|
||||||
|
delete temp.p;
|
||||||
|
if (temp.children && temp.children.length > 0) {
|
||||||
|
temp.children = this.parseColumns(temp.children);
|
||||||
|
} else {
|
||||||
|
delete temp.children;
|
||||||
|
}
|
||||||
|
return temp;
|
||||||
|
})
|
||||||
|
},
|
||||||
|
submit() {
|
||||||
|
if (this.activeRow) {
|
||||||
|
if (this.$refs.fn.save()) {
|
||||||
|
this.activeRow = null;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const value = [];
|
||||||
|
const columns = this.parseColumns(this.column)
|
||||||
|
for (let i = 0; i < columns.length; i++) {
|
||||||
|
const col = columns[i];
|
||||||
|
if (!col.label) {
|
||||||
|
errorMessage(this.t('com.dataTable.requiredLabel'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const temp = {...col};
|
||||||
|
if (temp.label) {
|
||||||
|
if (!temp.children) {
|
||||||
|
if (temp.format !== 'custom') {
|
||||||
|
delete temp.render;
|
||||||
|
} else if (!temp.render) {
|
||||||
|
errorMessage(this.t('com.dataTable.requiredRender'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (temp.render) {
|
||||||
|
temp.render = parseFn(temp.render);
|
||||||
|
}
|
||||||
|
value.push(temp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.$emit('update:modelValue', value);
|
||||||
|
this.$emit('change', value);
|
||||||
|
this.visible = false;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fd-table-column-config, ._fd-table-column-config .el-badge {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-table-column-config .el-button {
|
||||||
|
font-weight: 400;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-tcc-dialog .flex {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-tcc-dialog .el-dialog__body {
|
||||||
|
height: 500px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-tcc-dialog ._fd-fn {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-tcc-table .fc-icon {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-tcc-table .fc-icon + .fc-icon {
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-tcc-table .cell {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-tcc-table .el-table__indent {
|
||||||
|
padding-left: 8px !important;
|
||||||
|
}
|
||||||
|
</style>
|
115
src/components/dialog/Dialog.vue
Normal file
115
src/components/dialog/Dialog.vue
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog class="_fc-dialog" v-bind="$attrs" :fullscreen="max" v-model="visible" destroyOnClose>
|
||||||
|
<button class="el-dialog__headerbtn" type="button" style="right: 36px;" v-if="!$attrs.fullscreen">
|
||||||
|
<i class="fc-icon icon-page-min" v-if="max" @click="max=false"></i>
|
||||||
|
<i class="fc-icon icon-page-max" v-else @click="max=true"></i>
|
||||||
|
</button>
|
||||||
|
<component :is="Form" :option="formOptions" :rule="formRule" :extendOption="true"
|
||||||
|
v-model:api="fapi"
|
||||||
|
:model-value="value"
|
||||||
|
:subForm="false"
|
||||||
|
@change="formChange"
|
||||||
|
@emit-event="$emit"></component>
|
||||||
|
<template #footer v-if="footer !== false">
|
||||||
|
<el-button @click="close">{{formCreateInject.t('close') || '关闭'}}</el-button>
|
||||||
|
<el-button type="primary" @click="handleConfirm">{{formCreateInject.t('ok') || '确定'}}</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent, markRaw, onUnmounted, reactive} from 'vue';
|
||||||
|
import {deepCopy} from '@form-create/utils/lib/deepextend';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'FcDialog',
|
||||||
|
emits: ['confirm', 'submit', 'validateFail', 'update:modelValue'],
|
||||||
|
props: {
|
||||||
|
formData: Object,
|
||||||
|
options: {
|
||||||
|
type: Object,
|
||||||
|
default: () => reactive(({
|
||||||
|
submitBtn: false,
|
||||||
|
resetBtn: false,
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
rule: Array,
|
||||||
|
autoClose: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
preview: Boolean,
|
||||||
|
modelValue: Object,
|
||||||
|
formCreateInject: Object,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
formOptions() {
|
||||||
|
const opt = {...this.options};
|
||||||
|
if(this.preview) {
|
||||||
|
opt.preview = this.preview;
|
||||||
|
}
|
||||||
|
return opt;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
visible: false,
|
||||||
|
max: this.$attrs.fullscreen || false,
|
||||||
|
fapi: {},
|
||||||
|
value: {},
|
||||||
|
formRule: [],
|
||||||
|
Form: markRaw(this.formCreateInject.form.$form()),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
formChange() {
|
||||||
|
this.$emit('update:modelValue', this.fapi.formData());
|
||||||
|
},
|
||||||
|
open(formData) {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.visible = true;
|
||||||
|
this.value = deepCopy(formData || this.modelValue || this.formData || {});
|
||||||
|
this.formRule = deepCopy(this.rule || []);
|
||||||
|
})
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
this.visible = false;
|
||||||
|
},
|
||||||
|
handleConfirm() {
|
||||||
|
this.$emit('confirm', this.fapi);
|
||||||
|
this.fapi.submit().then(formData => {
|
||||||
|
this.$emit('submit', formData, this.fapi, this.close);
|
||||||
|
if (this.autoClose) {
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
}).catch(e => {
|
||||||
|
this.$emit('validateFail', e, this.fapi);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.formCreateInject.api.top.bus.$on('fc.closeDialog', this.close);
|
||||||
|
onUnmounted(() => {
|
||||||
|
this.formCreateInject.api.top.bus.$off('fc.closeDialog', this.close);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fc-dialog .el-dialog__headerbtn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: var(--el-color-info);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-dialog .el-dialog__headerbtn:hover .fc-icon {
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
84
src/components/dialog/DialogView.vue
Normal file
84
src/components/dialog/DialogView.vue
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_fd-dialog el-dialog" :style="`--fc-dialog-height:${dragConHeight-23}px;`">
|
||||||
|
<header class="el-dialog__header show-close">
|
||||||
|
<span class="el-dialog__title">{{ title }}</span>
|
||||||
|
<button class="el-dialog__headerbtn" type="button" style="right: 48px;" v-if="!fullscreen">
|
||||||
|
<i class="fc-icon icon-page-max"></i></button>
|
||||||
|
<button class="el-dialog__headerbtn" type="button">
|
||||||
|
<i class="el-icon el-dialog__close">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
||||||
|
<path fill="currentColor"
|
||||||
|
d="M764.288 214.592 512 466.88 259.712 214.592a31.936 31.936 0 0 0-45.12 45.12L466.752 512 214.528 764.224a31.936 31.936 0 1 0 45.12 45.184L512 557.184l252.288 252.288a31.936 31.936 0 0 0 45.12-45.12L557.12 512.064l252.288-252.352a31.936 31.936 0 1 0-45.12-45.184z"></path>
|
||||||
|
</svg>
|
||||||
|
</i>
|
||||||
|
</button>
|
||||||
|
</header>
|
||||||
|
<div class="el-dialog__body">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
<footer class="el-dialog__footer">
|
||||||
|
<template v-if="footer !== false">
|
||||||
|
<el-button>{{ t('props.close') }}</el-button>
|
||||||
|
<el-button type="primary">{{ t('props.ok') }}</el-button>
|
||||||
|
</template>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'FcDialog',
|
||||||
|
inject: ['designer'],
|
||||||
|
inheritAttrs: false,
|
||||||
|
props: {
|
||||||
|
title: String,
|
||||||
|
footer: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
fullscreen: Boolean,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
dragConHeight() {
|
||||||
|
return this.designer.setupState.dragConHeight;
|
||||||
|
},
|
||||||
|
t() {
|
||||||
|
return this.designer.setupState.t;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fd-dialog.el-dialog {
|
||||||
|
width: calc(100% - 20px);
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-dialog .el-dialog__headerbtn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: var(--el-color-info);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-dialog .el-dialog__headerbtn:hover .fc-icon {
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-dialog .el-dialog__body > ._fd-drag-box {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-content: flex-start;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: flex-start;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: relative;
|
||||||
|
overflow: auto;
|
||||||
|
height: calc(var(--fc-dialog-height) - 125px);
|
||||||
|
outline: 1px dashed var(--fc-tool-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
144
src/components/drawer/Drawer.vue
Normal file
144
src/components/drawer/Drawer.vue
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
<template>
|
||||||
|
<el-drawer class="_fc-drawer" v-bind="$attrs" :size="max ? '100%' : size" v-model="visible" destroyOnClose>
|
||||||
|
<template #header>
|
||||||
|
<span class="el-drawer__title">
|
||||||
|
{{ title }}
|
||||||
|
</span>
|
||||||
|
<button class="el-drawer__close-btn" type="button" v-if="size !== '100%'">
|
||||||
|
<i class="fc-icon icon-page-min" v-if="max" @click="max=false"></i>
|
||||||
|
<i class="fc-icon icon-page-max" v-else @click="max=true"></i>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
<component :is="Form" :option="formOptions" :rule="formRule" :extendOption="true"
|
||||||
|
v-model:api="fapi"
|
||||||
|
:model-value="value"
|
||||||
|
:subForm="false"
|
||||||
|
@change="formChange"
|
||||||
|
@emit-event="$emit"></component>
|
||||||
|
<template #footer>
|
||||||
|
<template v-if="footer !== false">
|
||||||
|
<el-button @click="close">{{formCreateInject.t('close') || '关闭'}}</el-button>
|
||||||
|
<el-button type="primary" @click="handleConfirm">{{formCreateInject.t('ok') || '确定'}}</el-button>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent, markRaw, onUnmounted, reactive} from 'vue';
|
||||||
|
import {deepCopy} from '@form-create/utils/lib/deepextend';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'FcDialog',
|
||||||
|
emits: ['confirm', 'submit', 'validateFail', 'update:modelValue'],
|
||||||
|
props: {
|
||||||
|
formData: Object,
|
||||||
|
options: {
|
||||||
|
type: Object,
|
||||||
|
default: () => reactive(({
|
||||||
|
submitBtn: false,
|
||||||
|
resetBtn: false,
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
size: [Number, String],
|
||||||
|
title: String,
|
||||||
|
rule: Array,
|
||||||
|
autoClose: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
preview: Boolean,
|
||||||
|
modelValue: Object,
|
||||||
|
formCreateInject: Object,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
formOptions() {
|
||||||
|
const opt = {...this.options};
|
||||||
|
if(this.preview) {
|
||||||
|
opt.preview = this.preview;
|
||||||
|
}
|
||||||
|
return opt;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
visible: false,
|
||||||
|
max: this.size === '100%',
|
||||||
|
fapi: {},
|
||||||
|
value: {},
|
||||||
|
formRule: [],
|
||||||
|
Form: markRaw(this.formCreateInject.form.$form()),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
formChange() {
|
||||||
|
this.$emit('update:modelValue', this.fapi.formData());
|
||||||
|
},
|
||||||
|
open(formData) {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.visible = true;
|
||||||
|
this.value = deepCopy(formData || this.modelValue || this.formData || {});
|
||||||
|
this.formRule = deepCopy(this.rule || []);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
this.visible = false;
|
||||||
|
},
|
||||||
|
handleConfirm() {
|
||||||
|
this.$emit('confirm', this.fapi);
|
||||||
|
this.fapi.submit().then(formData => {
|
||||||
|
this.$emit('submit', formData, this.fapi, this.close);
|
||||||
|
if (this.autoClose) {
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
}).catch(e => {
|
||||||
|
this.$emit('validateFail', e, this.fapi);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.formCreateInject.api.top.bus.$on('fc.closeDialog', this.close);
|
||||||
|
onUnmounted(() => {
|
||||||
|
this.formCreateInject.api.top.bus.$off('fc.closeDialog', this.close);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fc-drawer .el-drawer__header {
|
||||||
|
border-bottom: 1px solid var(--fc-line-color-3);
|
||||||
|
padding: 14px 24px 14px 20px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-drawer .el-drawer__body {
|
||||||
|
padding: 10px 24px 50px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-drawer .el-drawer__close-btn {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #909399
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-drawer .el-drawer__footer {
|
||||||
|
z-index: 10;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 100%;
|
||||||
|
background: var(--fc-bg-color-1);
|
||||||
|
box-shadow: 0 -2px 4px 0 rgba(0, 0, 0, .05);
|
||||||
|
text-align: center;
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
97
src/components/drawer/DrawerView.vue
Normal file
97
src/components/drawer/DrawerView.vue
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
<template>
|
||||||
|
<div class="el-drawer _fd-drawer" :style="`--fc-drawer-height:${dragConHeight-23}px;`">
|
||||||
|
<span class="el-drawer__sr-focus" tabindex="-1"></span>
|
||||||
|
<header class="el-drawer__header">
|
||||||
|
<span>{{ title }}</span>
|
||||||
|
<button class="el-drawer__close-btn" type="button" v-if="size !== '100%'">
|
||||||
|
<i class="fc-icon icon-page-max"></i></button>
|
||||||
|
<button class="el-drawer__close-btn" type="button">
|
||||||
|
<i class="el-icon el-drawer__close">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
||||||
|
<path fill="currentColor"
|
||||||
|
d="M764.288 214.592 512 466.88 259.712 214.592a31.936 31.936 0 0 0-45.12 45.12L466.752 512 214.528 764.224a31.936 31.936 0 1 0 45.12 45.184L512 557.184l252.288 252.288a31.936 31.936 0 0 0 45.12-45.12L557.12 512.064l252.288-252.352a31.936 31.936 0 1 0-45.12-45.184z"></path>
|
||||||
|
</svg>
|
||||||
|
</i></button>
|
||||||
|
</header>
|
||||||
|
<div class="el-drawer__body">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
<div class="el-drawer__footer">
|
||||||
|
<template v-if="footer !== false">
|
||||||
|
<el-button>{{ t('props.close') }}</el-button>
|
||||||
|
<el-button type="primary">{{ t('props.ok') }}</el-button>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'FcDialog',
|
||||||
|
inject: ['designer'],
|
||||||
|
inheritAttrs: false,
|
||||||
|
props: {
|
||||||
|
title: String,
|
||||||
|
footer: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
size: String,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
dragConHeight() {
|
||||||
|
return this.designer.setupState.dragConHeight;
|
||||||
|
},
|
||||||
|
t() {
|
||||||
|
return this.designer.setupState.t;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fd-drawer.el-drawer {
|
||||||
|
width: 100%;
|
||||||
|
box-shadow: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-drawer .el-drawer__header {
|
||||||
|
border-bottom: 1px solid var(--fc-line-color-3);
|
||||||
|
padding: 14px 24px 14px 20px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-drawer .el-drawer__body {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-drawer .el-drawer__close-btn {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #909399
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-drawer .el-drawer__footer {
|
||||||
|
box-shadow: 0 -2px 4px 0 rgba(0, 0, 0, .05);
|
||||||
|
text-align: center;
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-drawer .el-drawer__body > ._fd-drag-box {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-content: flex-start;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: flex-start;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: relative;
|
||||||
|
overflow: auto;
|
||||||
|
height: calc(var(--fc-drawer-height) - 105px);
|
||||||
|
outline: 1px dashed var(--fc-tool-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
425
src/components/echarts/Echarts.vue
Normal file
425
src/components/echarts/Echarts.vue
Normal file
@ -0,0 +1,425 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_fc-echarts" ref="chart">
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import loadjs from '../../utils/loadjs/loadjs';
|
||||||
|
import {defineComponent, markRaw} from 'vue';
|
||||||
|
import debounce from '@form-create/utils/lib/debounce';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'FcEcharts',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chart: null,
|
||||||
|
debounceLoad: debounce(() => {
|
||||||
|
this.load();
|
||||||
|
}, 600),
|
||||||
|
debounceResize: debounce(() => {
|
||||||
|
this.chart && this.chart.resize();
|
||||||
|
}, 10),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
emits: ['beforeLoad', 'loaded', 'click'],
|
||||||
|
props: {
|
||||||
|
title: String,
|
||||||
|
value: Number,
|
||||||
|
min: Number,
|
||||||
|
max: Number,
|
||||||
|
name: String,
|
||||||
|
valueFormat: String,
|
||||||
|
subtitle: String,
|
||||||
|
funnelSort: String,
|
||||||
|
config: Object,
|
||||||
|
data: Array,
|
||||||
|
indicator: Array,
|
||||||
|
smooth: Boolean,
|
||||||
|
stripe: Boolean,
|
||||||
|
showLegend: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
loadOptions: {
|
||||||
|
type: Function,
|
||||||
|
default: () => {
|
||||||
|
}
|
||||||
|
},
|
||||||
|
showSeriesLabel: Boolean,
|
||||||
|
type: String,
|
||||||
|
pieType: String,
|
||||||
|
stack: Boolean,
|
||||||
|
barBackgroundColor: String,
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
'$props': {
|
||||||
|
handler() {
|
||||||
|
this.debounceLoad();
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getSeries() {
|
||||||
|
const append = {
|
||||||
|
type: 'line',
|
||||||
|
stack: this.stack ? 'Total' : '',
|
||||||
|
smooth: this.smooth,
|
||||||
|
showBackground: false,
|
||||||
|
label: {
|
||||||
|
show: this.showSeriesLabel,
|
||||||
|
position: this.stripe ? 'inside' : 'top'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (this.type === 'area') {
|
||||||
|
append.areaStyle = {};
|
||||||
|
append.emphasis = {
|
||||||
|
focus: 'series'
|
||||||
|
};
|
||||||
|
} else if (this.type === 'bar') {
|
||||||
|
append.type = 'bar';
|
||||||
|
if (this.barBackgroundColor) {
|
||||||
|
append.showBackground = true;
|
||||||
|
append.backgroundStyle = {
|
||||||
|
color: this.barBackgroundColor
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let series = this.config?.series || [];
|
||||||
|
if (!series.length) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
if (typeof series[0] != 'object') {
|
||||||
|
series = [{
|
||||||
|
data: series,
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
series = series.map(item => {
|
||||||
|
return {
|
||||||
|
...append, ...item
|
||||||
|
};
|
||||||
|
})
|
||||||
|
return series;
|
||||||
|
},
|
||||||
|
getTooltip() {
|
||||||
|
const tooltip = {
|
||||||
|
trigger: 'axis',
|
||||||
|
valueFormat: undefined,
|
||||||
|
}
|
||||||
|
if (this.valueFormat) {
|
||||||
|
tooltip.valueFormatter = (value) => {
|
||||||
|
if (!this.valueFormat) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return this.valueFormat.replaceAll('{value}', value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.type === 'bar') {
|
||||||
|
tooltip.axisPointer = {
|
||||||
|
type: 'shadow'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tooltip;
|
||||||
|
},
|
||||||
|
getAxis() {
|
||||||
|
if (!this.stripe) {
|
||||||
|
return {
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: this.type === 'bar',
|
||||||
|
data: this.config?.category
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
yAxis: {
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: this.type === 'bar',
|
||||||
|
data: this.config?.category || []
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'value'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getDefOptions() {
|
||||||
|
return {
|
||||||
|
title: {
|
||||||
|
text: this.title,
|
||||||
|
subtext: this.subtitle,
|
||||||
|
},
|
||||||
|
tooltip: this.getTooltip(),
|
||||||
|
legend: {
|
||||||
|
left: 'right',
|
||||||
|
show: this.showLegend,
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: '20px',
|
||||||
|
right: '20px',
|
||||||
|
bottom: '20px',
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
...this.getAxis(),
|
||||||
|
series: this.getSeries()
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getPieOptions() {
|
||||||
|
const append = {
|
||||||
|
radius: '50%',
|
||||||
|
center: '50%',
|
||||||
|
startAngle: 0,
|
||||||
|
avoidLabelOverlap: true,
|
||||||
|
labelLine: {
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
emphasis: {
|
||||||
|
itemStyle: {
|
||||||
|
shadowBlur: 10,
|
||||||
|
shadowOffsetX: 0,
|
||||||
|
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (this.pieType === 'doughnut') {
|
||||||
|
append.radius = ['40%', '70%'];
|
||||||
|
append.avoidLabelOverlap = false;
|
||||||
|
} else if (this.pieType === 'half-doughnut') {
|
||||||
|
append.radius = ['40%', '70%'];
|
||||||
|
append.center = ['50%', '70%'];
|
||||||
|
append.startAngle = 180;
|
||||||
|
append.endAngle = 360;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
title: {
|
||||||
|
text: this.title,
|
||||||
|
subtext: this.subtitle,
|
||||||
|
left: 'left'
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'item'
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
left: 'right',
|
||||||
|
show: this.showLegend,
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
type: 'pie',
|
||||||
|
data: this.data,
|
||||||
|
...append,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getGaugeOptions() {
|
||||||
|
return {
|
||||||
|
title: {
|
||||||
|
text: this.title,
|
||||||
|
subtext: this.subtitle,
|
||||||
|
left: 'center'
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: 'Pressure',
|
||||||
|
type: 'gauge',
|
||||||
|
min: this.min || 0,
|
||||||
|
max: this.max || 60,
|
||||||
|
progress: {
|
||||||
|
show: true
|
||||||
|
},
|
||||||
|
detail: {
|
||||||
|
valueAnimation: true,
|
||||||
|
formatter: '{value}'
|
||||||
|
},
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
value: this.value,
|
||||||
|
name: this.name
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getRadarOptions() {
|
||||||
|
return {
|
||||||
|
title: {
|
||||||
|
text: this.title,
|
||||||
|
subtext: this.subtitle,
|
||||||
|
left: 'left'
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis'
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
left: 'right',
|
||||||
|
show: this.showLegend,
|
||||||
|
},
|
||||||
|
radar: {
|
||||||
|
indicator: this.indicator
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
type: 'radar',
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'item'
|
||||||
|
},
|
||||||
|
data: this.data
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getScatterOptions() {
|
||||||
|
return {
|
||||||
|
title: {
|
||||||
|
text: this.title,
|
||||||
|
subtext: this.subtitle,
|
||||||
|
left: 'left'
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis'
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
left: 'right',
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
scale: true
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
scale: true
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: '20px',
|
||||||
|
right: '20px',
|
||||||
|
bottom: '20px',
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
series: (this.data || []).map((data) => {
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
return {
|
||||||
|
type: 'scatter',
|
||||||
|
data: data
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {type: 'scatter', ...data}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getFunnelOptions() {
|
||||||
|
return {
|
||||||
|
title: {
|
||||||
|
text: this.title,
|
||||||
|
subtext: this.subtitle,
|
||||||
|
left: 'left'
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'item'
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
left: 'right',
|
||||||
|
show: this.showLegend,
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: 'Funnel',
|
||||||
|
type: 'funnel',
|
||||||
|
left: '10%',
|
||||||
|
top: '40px',
|
||||||
|
bottom: '20px',
|
||||||
|
width: '80%',
|
||||||
|
min: 0,
|
||||||
|
max: Math.max(...(this.data || []).map(v => v.value)),
|
||||||
|
minSize: '0%',
|
||||||
|
maxSize: '100%',
|
||||||
|
sort: this.funnelSort || 'descending',
|
||||||
|
gap: 2,
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'inside'
|
||||||
|
},
|
||||||
|
labelLine: {
|
||||||
|
length: 10,
|
||||||
|
lineStyle: {
|
||||||
|
width: 1,
|
||||||
|
type: 'solid'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
borderColor: '#fff',
|
||||||
|
borderWidth: 1
|
||||||
|
},
|
||||||
|
emphasis: {
|
||||||
|
label: {}
|
||||||
|
},
|
||||||
|
data: this.data,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
load() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
loadjs.ready('echarts', () => {
|
||||||
|
this.chart = markRaw(window.echarts.init(this.$refs.chart));
|
||||||
|
this.chart.on('click', (...args) => {
|
||||||
|
this.$emit('click', ...args);
|
||||||
|
});
|
||||||
|
let options;
|
||||||
|
if (this.type === 'pie') {
|
||||||
|
options = this.getPieOptions();
|
||||||
|
} else if (this.type === 'funnel') {
|
||||||
|
options = this.getFunnelOptions();
|
||||||
|
} else if (this.type === 'gauge') {
|
||||||
|
options = this.getGaugeOptions();
|
||||||
|
} else if (this.type === 'radar') {
|
||||||
|
options = this.getRadarOptions();
|
||||||
|
} else if (this.type === 'scatter') {
|
||||||
|
options = this.getScatterOptions();
|
||||||
|
} else if (this.type === 'custom') {
|
||||||
|
options = this.loadOptions(this.config, this.chart) || {};
|
||||||
|
if (typeof options.then === 'function') {
|
||||||
|
options.then(res => {
|
||||||
|
this.$emit('beforeLoad', this.chart, res);
|
||||||
|
this.chart.setOption(res);
|
||||||
|
this.$emit('loaded', this.chart, res);
|
||||||
|
})
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
options = this.getDefOptions();
|
||||||
|
}
|
||||||
|
this.$emit('beforeLoad', this.chart, options);
|
||||||
|
this.chart.setOption(options);
|
||||||
|
this.$emit('loaded', this.chart, options);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
if (window.echarts) {
|
||||||
|
loadjs.done('echarts');
|
||||||
|
} else if (!loadjs.isDefined('echarts')) {
|
||||||
|
loadjs.loadNpm('echarts@5.6.0/dist/echarts.min.js', 'echarts');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.load();
|
||||||
|
window.addEventListener('resize', this.debounceResize);
|
||||||
|
},
|
||||||
|
unmounted() {
|
||||||
|
window.removeEventListener('resize', this.debounceResize);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fc-echarts {
|
||||||
|
width: 100%;
|
||||||
|
height: 300px;
|
||||||
|
}
|
||||||
|
</style>
|
465
src/components/infiniteTableForm/InfiniteTableForm.vue
Normal file
465
src/components/infiniteTableForm/InfiniteTableForm.vue
Normal file
@ -0,0 +1,465 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_fc-infinite-table-form" :class="{'_fc-disabled': disabled}">
|
||||||
|
<component :is="Form" :option="options" :rule="rule" :extendOption="true"
|
||||||
|
@change="formChange"
|
||||||
|
:disabled="disabled"
|
||||||
|
v-model:api="fapi"
|
||||||
|
@emit-event="$emit"></component>
|
||||||
|
<el-button link type="primary" class="fc-clock" v-if="!max || max > this.trs.length"
|
||||||
|
@click="addRaw(true)"><i class="fc-icon icon-add-circle" style="font-weight: 700;"></i>
|
||||||
|
{{ formCreateInject.t('add') || '添加' }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {markRaw, reactive} from 'vue';
|
||||||
|
import {deepCopy} from '@form-create/utils/lib/deepextend';
|
||||||
|
import {hasProperty} from '@form-create/utils/lib/type';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'InfiniteTableForm',
|
||||||
|
emits: ['change', 'add', 'delete', 'update:modelValue'],
|
||||||
|
props: {
|
||||||
|
formCreateInject: Object,
|
||||||
|
modelValue: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
columns: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
type: Object,
|
||||||
|
default: () => reactive(({
|
||||||
|
submitBtn: false,
|
||||||
|
resetBtn: false,
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
max: Number,
|
||||||
|
layerMax: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
childrenField: String,
|
||||||
|
disabled: Boolean,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
preview() {
|
||||||
|
return this.formCreateInject.preview;
|
||||||
|
},
|
||||||
|
subField() {
|
||||||
|
return this.childrenField || 'children';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
modelValue() {
|
||||||
|
this.updateTable()
|
||||||
|
},
|
||||||
|
'formCreateInject.preview'(n) {
|
||||||
|
this.trs.forEach((tr, i) => {
|
||||||
|
if (tr.children[1]) {
|
||||||
|
tr.children[1].children[0].props.colspan = this.rule[0].children[0].children[0].children.length - (n ? 1 : 0);
|
||||||
|
}
|
||||||
|
tr.children[0].children[0].children[0].hidden = this.layerMax === 1 || n && !(this.modelValue && this.modelValue[i] && Array.isArray(this.modelValue[i][this.subField]) && this.modelValue[i][this.subField].length > 0);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
rule: [],
|
||||||
|
trs: [],
|
||||||
|
fapi: {},
|
||||||
|
Form: markRaw(this.formCreateInject.form.$form()),
|
||||||
|
copyTrs: '',
|
||||||
|
oldValue: ''
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
formChange(field, _, rule, api, flag) {
|
||||||
|
if (false === flag) {
|
||||||
|
this.updateValue();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateValue() {
|
||||||
|
const value = this.trs.map((tr, idx) => {
|
||||||
|
const formData = {
|
||||||
|
...(this.modelValue[idx] || {}),
|
||||||
|
...this.fapi.getChildrenFormData(tr)
|
||||||
|
}
|
||||||
|
if (!hasProperty(formData, this.subField) && this.modelValue[idx]) {
|
||||||
|
formData[this.subField] = this.modelValue[idx][this.subField];
|
||||||
|
}
|
||||||
|
if (formData[this.subField] == null) {
|
||||||
|
delete formData[this.subField];
|
||||||
|
}
|
||||||
|
return formData;
|
||||||
|
});
|
||||||
|
const str = JSON.stringify(value);
|
||||||
|
if (str !== this.oldValue) {
|
||||||
|
this.oldValue = str;
|
||||||
|
this.$emit('update:modelValue', value);
|
||||||
|
this.$emit('change', value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setRawData(idx, formData) {
|
||||||
|
const raw = this.trs[idx];
|
||||||
|
this.fapi.setChildrenFormData(raw, formData, true);
|
||||||
|
},
|
||||||
|
updateTable() {
|
||||||
|
const str = JSON.stringify(this.modelValue);
|
||||||
|
if (this.oldValue === str) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.oldValue = str;
|
||||||
|
this.trs = this.trs.splice(0, this.modelValue.length);
|
||||||
|
if (!this.modelValue.length) {
|
||||||
|
this.addRaw();
|
||||||
|
}
|
||||||
|
this.modelValue.forEach((data, idx) => {
|
||||||
|
if (!this.trs[idx]) {
|
||||||
|
this.addRaw();
|
||||||
|
}
|
||||||
|
this.setRawData(idx, data || {});
|
||||||
|
});
|
||||||
|
this.rule[0].children[1].children = this.trs;
|
||||||
|
},
|
||||||
|
delRaw(idx) {
|
||||||
|
if (this.disabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.trs.splice(idx, 1);
|
||||||
|
this.updateValue();
|
||||||
|
if (this.trs.length) {
|
||||||
|
this.trs.forEach(tr => this.updateRaw(tr));
|
||||||
|
} else {
|
||||||
|
this.addRaw();
|
||||||
|
}
|
||||||
|
this.$emit('delete', idx);
|
||||||
|
},
|
||||||
|
addRaw(flag) {
|
||||||
|
if (flag && this.disabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const tr = this.formCreateInject.form.parseJson(this.copyTrs)[0];
|
||||||
|
const template = {
|
||||||
|
type: 'template',
|
||||||
|
subRule: true,
|
||||||
|
children: []
|
||||||
|
};
|
||||||
|
template.children.push(tr);
|
||||||
|
this.trs.push(template);
|
||||||
|
this.trs.forEach(tr => this.updateRaw(tr));
|
||||||
|
flag && this.$emit('add', this.trs);
|
||||||
|
},
|
||||||
|
updateRaw(tr) {
|
||||||
|
const idx = this.trs.indexOf(tr);
|
||||||
|
const row = tr.children[0];
|
||||||
|
row.children[0].children[0].hidden = this.layerMax === 1 || this.preview && !(this.modelValue && this.modelValue[idx] && Array.isArray(this.modelValue[idx][this.subField]) && this.modelValue[idx][this.subField].length > 0);
|
||||||
|
row.children[0].children[0].props.onClick = (inject) => {
|
||||||
|
if (this.trs[idx].children.length === 1) {
|
||||||
|
if (this.disabled && !(this.modelValue && this.modelValue[idx] && Array.isArray(this.modelValue[idx][this.subField]) && this.modelValue[idx][this.subField].length > 0)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.trs[idx].children.push({
|
||||||
|
type: 'tr',
|
||||||
|
native: true,
|
||||||
|
display: true,
|
||||||
|
children: [{
|
||||||
|
type: 'td',
|
||||||
|
native: true,
|
||||||
|
props: {
|
||||||
|
colspan: this.rule[0].children[0].children[0].children.length - (this.preview ? 1 : 0),
|
||||||
|
},
|
||||||
|
class: '_fc-itf-sub',
|
||||||
|
children: [{
|
||||||
|
type: 'infiniteTableForm',
|
||||||
|
field: this.subField,
|
||||||
|
value: [...((this.modelValue[idx] && this.modelValue[idx][this.subField]) || [])],
|
||||||
|
props: {
|
||||||
|
disabled: this.disabled,
|
||||||
|
layerMax: this.layerMax === 0 ? 0 : (this.layerMax > 1 ? this.layerMax - 1 : 1),
|
||||||
|
max: this.max || 0,
|
||||||
|
columns: deepCopy(this.columns),
|
||||||
|
options: deepCopy(this.options),
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const icon = inject.self.children[0] === '-' ? '+' : '-';
|
||||||
|
inject.self.children = [icon];
|
||||||
|
this.trs[idx].children[1].display = icon === '-';
|
||||||
|
};
|
||||||
|
row.children[1].props.innerText = idx + 1;
|
||||||
|
row.children[row.children.length - 1].children[0].props.onClick = () => {
|
||||||
|
this.delRaw(idx);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
loadRule() {
|
||||||
|
const header = [{
|
||||||
|
type: 'th',
|
||||||
|
native: true,
|
||||||
|
class: '_fc-itf-sub-idx',
|
||||||
|
}, {
|
||||||
|
type: 'th',
|
||||||
|
native: true,
|
||||||
|
class: '_fc-itf-head-idx',
|
||||||
|
props: {
|
||||||
|
innerText: '#'
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
let body = [{
|
||||||
|
type: 'td',
|
||||||
|
class: '_fc-itf-idx',
|
||||||
|
native: true,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
type: 'div',
|
||||||
|
hidden: false,
|
||||||
|
children: ['+'],
|
||||||
|
inject: true,
|
||||||
|
props: {}
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}, {
|
||||||
|
type: 'td',
|
||||||
|
class: '_fc-itf-idx',
|
||||||
|
native: true,
|
||||||
|
props: {
|
||||||
|
innerText: '0'
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
this.columns.forEach((column) => {
|
||||||
|
header.push({
|
||||||
|
type: 'th',
|
||||||
|
native: true,
|
||||||
|
class: column.required ? '_fc-itf-head-required' : '',
|
||||||
|
style: column.style,
|
||||||
|
props: {
|
||||||
|
innerText: column.label || ''
|
||||||
|
}
|
||||||
|
});
|
||||||
|
body.push({
|
||||||
|
type: 'td',
|
||||||
|
native: true,
|
||||||
|
children: [...(column.rule || [])]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
header.push({
|
||||||
|
type: 'th',
|
||||||
|
native: true,
|
||||||
|
class: '_fc-itf-edit fc-clock',
|
||||||
|
props: {
|
||||||
|
innerText: this.formCreateInject.t('operation') || '操作'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
body.push({
|
||||||
|
type: 'td',
|
||||||
|
native: true,
|
||||||
|
class: '_fc-itf-btn fc-clock',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
type: 'i',
|
||||||
|
native: true,
|
||||||
|
class: 'fc-icon icon-delete',
|
||||||
|
props: {},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
});
|
||||||
|
this.copyTrs = this.formCreateInject.form.toJson([
|
||||||
|
{
|
||||||
|
type: 'tr',
|
||||||
|
native: true,
|
||||||
|
children: body
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
this.rule = [
|
||||||
|
{
|
||||||
|
type: 'table',
|
||||||
|
native: true,
|
||||||
|
class: '_fc-itf-table',
|
||||||
|
props: {
|
||||||
|
border: '1',
|
||||||
|
cellspacing: '0',
|
||||||
|
cellpadding: '0',
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
type: 'thead',
|
||||||
|
native: true,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
type: 'tr',
|
||||||
|
native: true,
|
||||||
|
children: header
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'tbody',
|
||||||
|
native: true,
|
||||||
|
children: this.trs
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.loadRule();
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.updateTable();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fc-infinite-table-form {
|
||||||
|
overflow: auto;
|
||||||
|
color: var(--fc-text-color-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-infinite-table-form .form-create .el-form-item {
|
||||||
|
margin-bottom: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-infinite-table-form .form-create .el-form-item.is-error {
|
||||||
|
margin-bottom: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-infinite-table-form .el-form-item__label, ._fc-infinite-table-form .van-field__label {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-infinite-table-form .el-form-item__content {
|
||||||
|
display: flex;
|
||||||
|
margin-left: 0px !important;
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-itf-table ._fc-itf-head-idx, ._fc-itf-table ._fc-itf-idx {
|
||||||
|
width: 40px;
|
||||||
|
min-width: 40px;
|
||||||
|
font-weight: 500;
|
||||||
|
text-align: center;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-itf-idx div {
|
||||||
|
display: inline-flex;
|
||||||
|
justify-content: center;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
line-height: 16px;
|
||||||
|
border: 1px solid #bfbfbf;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-itf-sub-idx {
|
||||||
|
width: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-itf-edit, ._fc-itf-btn {
|
||||||
|
width: 70px;
|
||||||
|
min-width: 70px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-itf-btn .fc-icon {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-infinite-table-form._fc-disabled ._fc-itf-btn .fc-icon, ._fc-infinite-table-form._fc-disabled > .el-button {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-itf-table {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
table-layout: fixed;
|
||||||
|
border: 1px solid #EBEEF5;
|
||||||
|
border-bottom: 0 none;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-itf-table > thead > tr > th {
|
||||||
|
border: 0 none;
|
||||||
|
border-bottom: 1px solid #EBEEF5;
|
||||||
|
height: 40px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-itf-table ._fc-itf-table > thead {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-itf-table ._fc-itf-table {
|
||||||
|
border-right: 0 none;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-itf-table > thead > tr > th + th {
|
||||||
|
border-left: 1px solid #EBEEF5;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-itf-table tr {
|
||||||
|
min-height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-itf-table ._fc-read-view {
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-itf-table td {
|
||||||
|
padding: 10px;
|
||||||
|
min-height: 50px;
|
||||||
|
min-width: 80px;
|
||||||
|
position: relative;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
/*white-space: nowrap;*/
|
||||||
|
overflow: hidden;
|
||||||
|
border: 0 none;
|
||||||
|
border-bottom: 1px solid #EBEEF5;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-itf-table td + td {
|
||||||
|
border-left: 1px solid #EBEEF5;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-itf-table .el-input-number, ._fc-itf-table .el-select, ._fc-itf-table .el-slider, ._fc-itf-table .el-cascader, ._fc-itf-table .el-date-editor {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-infinite-table-form ._fc-itf-sub {
|
||||||
|
padding: 5px 0 5px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-itf-sub ._fc-table-form {
|
||||||
|
background-color: var(--fc-bg-color-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-itf-sub ._fc-tf-table {
|
||||||
|
border: 0 none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*._fc-itf-sub ._fc-tf-table thead {
|
||||||
|
background: #fafafa;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
._fc-itf-sub-idx + ._fc-itf-head-idx, ._fc-itf-idx + ._fc-itf-idx {
|
||||||
|
border-left: 0 none;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-itf-head-required:before {
|
||||||
|
content: '*';
|
||||||
|
color: #f56c6c;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
</style>
|
50
src/components/infiniteTableForm/InfiniteTableFormView.vue
Normal file
50
src/components/infiniteTableForm/InfiniteTableFormView.vue
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_fd-itable-form">
|
||||||
|
<div class="_fd-itf-wrap" v-if="$slots.default">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
<div class="_fc-child-empty" v-else></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'InfiniteTableFormView',
|
||||||
|
inject: ['designer'],
|
||||||
|
data() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
t() {
|
||||||
|
return this.designer.setupState.t;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fd-itable-form {
|
||||||
|
min-height: 130px;
|
||||||
|
width: 100%;
|
||||||
|
border: 1px solid var(--fc-line-color-3);
|
||||||
|
background: var(--fc-bg-color-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-itable-form ._fc-child-empty {
|
||||||
|
min-height: 130px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-itf-wrap {
|
||||||
|
display: flex;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-itf-wrap > ._fd-drag-tool {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
margin: 2px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
</style>
|
128
src/components/jsonComponent/JsonComponent.vue
Normal file
128
src/components/jsonComponent/JsonComponent.vue
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
<script>
|
||||||
|
import {createVNode, defineComponent, Fragment, shallowRef} from 'vue';
|
||||||
|
import {deepCopy} from '@form-create/utils/lib/deepextend';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'FcJson',
|
||||||
|
inheritAttrs: false,
|
||||||
|
props: {
|
||||||
|
rule: [Array, String, Object],
|
||||||
|
type: String,
|
||||||
|
disabled: Boolean,
|
||||||
|
expand: Number,
|
||||||
|
button: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
max: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
min: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
sortBtn: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
modelValue: [Object, Array],
|
||||||
|
formCreateInject: Object,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
fcSubForm: shallowRef(this.formCreateInject.form.component('fcSubForm')),
|
||||||
|
fcGroup: shallowRef(this.formCreateInject.form.component('fcGroup')),
|
||||||
|
uni: 0,
|
||||||
|
formRule: [],
|
||||||
|
formOptions: {
|
||||||
|
submitBtn: false,
|
||||||
|
resetBtn: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
rule() {
|
||||||
|
this.uni++;
|
||||||
|
this.loadRule();
|
||||||
|
},
|
||||||
|
type() {
|
||||||
|
this.loadRule();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
if (this.rule) {
|
||||||
|
if (this.type === 'object') {
|
||||||
|
return createVNode(this.fcSubForm, {
|
||||||
|
key: 2,
|
||||||
|
...this.$attrs,
|
||||||
|
modelValue: this.modelValue,
|
||||||
|
'onUpdate:modelValue': (val) => {
|
||||||
|
this.$emit('update:modelValue', val);
|
||||||
|
},
|
||||||
|
disabled: this.disabled,
|
||||||
|
formCreateInject: this.formCreateInject,
|
||||||
|
rule: this.formRule,
|
||||||
|
options: this.formOptions,
|
||||||
|
})
|
||||||
|
} else if (this.type === 'array') {
|
||||||
|
return createVNode(this.fcGroup, {
|
||||||
|
key: 3,
|
||||||
|
...this.$attrs,
|
||||||
|
modelValue: this.modelValue,
|
||||||
|
'onUpdate:modelValue': (val) => {
|
||||||
|
this.$emit('update:modelValue', val);
|
||||||
|
},
|
||||||
|
sortBtn: this.sortBtn,
|
||||||
|
min: this.min,
|
||||||
|
max: this.max,
|
||||||
|
expand: this.expand,
|
||||||
|
button: this.button,
|
||||||
|
disabled: this.disabled,
|
||||||
|
formCreateInject: this.formCreateInject,
|
||||||
|
rule: this.formRule,
|
||||||
|
options: this.formOptions,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return createVNode(Fragment, {
|
||||||
|
key: this.uni,
|
||||||
|
}, [this.$slots?.default?.()]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
loadRule() {
|
||||||
|
let rule = deepCopy(this.rule);
|
||||||
|
if (typeof rule === 'string') {
|
||||||
|
rule = this.formCreateInject.form.parseJson(rule);
|
||||||
|
}
|
||||||
|
if (Array.isArray(rule)) {
|
||||||
|
this.formRule = rule;
|
||||||
|
} else if (typeof rule === 'object') {
|
||||||
|
this.formRule = rule.rule || [];
|
||||||
|
this.formOptions = {
|
||||||
|
...{
|
||||||
|
submitBtn: false,
|
||||||
|
resetBtn: false,
|
||||||
|
}, ...rule.options || {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (rule != null) {
|
||||||
|
if (['array', 'object'].indexOf(this.type) === -1) {
|
||||||
|
this.formCreateInject.rule.children = [
|
||||||
|
{
|
||||||
|
type: 'template',
|
||||||
|
_fc_drag_skip: true,
|
||||||
|
children: this.formRule,
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.formCreateInject.rule.children = [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.rule && this.loadRule();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
156
src/components/jsonComponent/JsonComponentView.vue
Normal file
156
src/components/jsonComponent/JsonComponentView.vue
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
<script>
|
||||||
|
import {createElementVNode, createVNode, defineComponent, Fragment, shallowRef} from 'vue';
|
||||||
|
import {deepCopy} from '@form-create/utils/lib/deepextend';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'FcJsonView',
|
||||||
|
inheritAttrs: false,
|
||||||
|
inject: ['designer'],
|
||||||
|
props: {
|
||||||
|
rule: [Array, String, Object],
|
||||||
|
type: String,
|
||||||
|
disabled: Boolean,
|
||||||
|
expand: Number,
|
||||||
|
button: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
max: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
min: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
sortBtn: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
modelValue: [Object, Array],
|
||||||
|
formCreateInject: Object,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
fcSubForm: shallowRef(this.formCreateInject.form.component('fcSubForm')),
|
||||||
|
fcGroup: shallowRef(this.formCreateInject.form.component('fcGroup')),
|
||||||
|
uni: 0,
|
||||||
|
formRule: [],
|
||||||
|
formOptions: {
|
||||||
|
submitBtn: false,
|
||||||
|
resetBtn: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
rule() {
|
||||||
|
this.uni++;
|
||||||
|
this.loadRule();
|
||||||
|
},
|
||||||
|
type() {
|
||||||
|
this.loadRule();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
if (this.rule) {
|
||||||
|
let child = null;
|
||||||
|
if (this.type === 'object') {
|
||||||
|
child = createVNode(this.fcSubForm, {
|
||||||
|
key: 2,
|
||||||
|
...this.$attrs,
|
||||||
|
modelValue: this.modelValue,
|
||||||
|
'onUpdate:modelValue': (val) => {
|
||||||
|
this.$emit('update:modelValue', val);
|
||||||
|
},
|
||||||
|
disabled: this.disabled,
|
||||||
|
formCreateInject: this.formCreateInject,
|
||||||
|
rule: this.formRule,
|
||||||
|
options: this.formOptions,
|
||||||
|
})
|
||||||
|
} else if (this.type === 'array') {
|
||||||
|
child = createVNode(this.fcGroup, {
|
||||||
|
key: 3,
|
||||||
|
...this.$attrs,
|
||||||
|
modelValue: this.modelValue,
|
||||||
|
'onUpdate:modelValue': (val) => {
|
||||||
|
this.$emit('update:modelValue', val);
|
||||||
|
},
|
||||||
|
sortBtn: this.sortBtn,
|
||||||
|
expand: 1,
|
||||||
|
button: this.button,
|
||||||
|
disabled: this.disabled,
|
||||||
|
formCreateInject: this.formCreateInject,
|
||||||
|
rule: this.formRule,
|
||||||
|
options: this.formOptions,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
child = createVNode(Fragment, {
|
||||||
|
key: 1,
|
||||||
|
}, [this.$slots?.default?.()]);
|
||||||
|
}
|
||||||
|
return createElementVNode('div', {
|
||||||
|
key: this.uni,
|
||||||
|
style: {'--fc-json-mask': `'${this.designer.setupState.t('com.fcJson.name')}'`},
|
||||||
|
class: '_fd-json-container',
|
||||||
|
}, [child])
|
||||||
|
} else {
|
||||||
|
return createElementVNode('div', {
|
||||||
|
class: '_fd-slot-empty',
|
||||||
|
innerHTML: this.designer.setupState.t('com.fcJson.empty', {tag: '<span>JSON</span>'})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
loadRule() {
|
||||||
|
let rule = deepCopy(this.rule);
|
||||||
|
if (typeof rule === 'string') {
|
||||||
|
rule = this.formCreateInject.form.parseJson(rule);
|
||||||
|
}
|
||||||
|
if (Array.isArray(rule)) {
|
||||||
|
this.formRule = rule;
|
||||||
|
} else if (typeof rule === 'object') {
|
||||||
|
this.formRule = rule.rule || [];
|
||||||
|
this.formOptions = {
|
||||||
|
...{
|
||||||
|
submitBtn: false,
|
||||||
|
resetBtn: false,
|
||||||
|
}, ...rule.options || {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (rule != null) {
|
||||||
|
if (['array', 'object'].indexOf(this.type) === -1) {
|
||||||
|
this.formCreateInject.rule.children = [
|
||||||
|
{
|
||||||
|
type: 'template',
|
||||||
|
_fc_drag_skip: true,
|
||||||
|
children: this.formRule,
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.formCreateInject.rule.children = [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.rule && this.loadRule();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fd-json-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 20px;
|
||||||
|
position: relative;
|
||||||
|
--fc-json-mask: ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-json-container ._fd-dialog, ._fd-json-container ._fd-drawer, ._fd-json-container ._fd-popup {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
165
src/components/language/LanguageConfig.vue
Normal file
165
src/components/language/LanguageConfig.vue
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_fd-language-config">
|
||||||
|
<div class="_fc-l-label">{{ t('language.name') }}</div>
|
||||||
|
<div class="_fc-l-info">
|
||||||
|
{{ t('warning.language') }}
|
||||||
|
</div>
|
||||||
|
<div class="_fd-lc-header">
|
||||||
|
<el-button size="small" @click="addColumn">{{ t('language.add') }}</el-button>
|
||||||
|
<el-button size="small" type="danger" plain :disabled="!selected.length" @click="batchRmColumn">
|
||||||
|
{{ t('language.batchRemove') }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
<div class="_fd-lc-body">
|
||||||
|
<el-table :data="column" size="small" ref="table"
|
||||||
|
@selection-change="selectionChange" row-key="key">
|
||||||
|
<el-table-column type="selection" width="30px"></el-table-column>
|
||||||
|
<el-table-column prop="key" label="Key" width="90px"></el-table-column>
|
||||||
|
<template v-for="item in localeOptions" :key="item.value">
|
||||||
|
<el-table-column :prop="item.value" :label="item.label" min-width="100px">
|
||||||
|
<template #default="scope">
|
||||||
|
<template v-if="scope.row.input">
|
||||||
|
<el-input size="small" v-model="scope.row[item.value]" @blur="saveColumn(scope.row, true)"></el-input>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
{{ scope.row[item.value] || '-' }}
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</template>
|
||||||
|
<el-table-column width="75px" :label="t('tableOptions.handle')" fixed="right">
|
||||||
|
<template #default="scope">
|
||||||
|
<div class="_fd-lc-handle">
|
||||||
|
<i class="fc-icon icon-edit" v-if="!scope.row.input" @click="scope.row.input = true"></i>
|
||||||
|
<i class="fc-icon icon-check" v-else @click="saveColumn(scope.row)"></i>
|
||||||
|
<i class="fc-icon icon-group" @click="copy(scope.row.key)"></i>
|
||||||
|
<i class="fc-icon icon-delete-circle" @click="rmColumn(scope.$index)"></i>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
import {copyTextToClipboard} from '../../utils';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'LanguageConfig',
|
||||||
|
inject: ['designer'],
|
||||||
|
computed: {
|
||||||
|
localeOptions() {
|
||||||
|
return this.designer.setupState.getConfig('localeOptions', [
|
||||||
|
{value: 'zh-cn', label: '简体中文'},
|
||||||
|
{value: 'en', label: 'English'},
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
t() {
|
||||||
|
return this.designer.setupState.t;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
column: [],
|
||||||
|
uni: 0,
|
||||||
|
selected: [],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
copy(key) {
|
||||||
|
copyTextToClipboard(key);
|
||||||
|
},
|
||||||
|
addColumn() {
|
||||||
|
this.column.unshift({
|
||||||
|
key: this.randomString(),
|
||||||
|
input: true,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
saveColumn(row, input) {
|
||||||
|
row.input = input || false;
|
||||||
|
const language = this.designer.setupState.formOptions.language;
|
||||||
|
this.localeOptions.forEach(item => {
|
||||||
|
if (!language[item.value]) {
|
||||||
|
language[item.value] = {};
|
||||||
|
}
|
||||||
|
language[item.value][row.key] = row[item.value];
|
||||||
|
})
|
||||||
|
},
|
||||||
|
rmColumn(idx) {
|
||||||
|
const row = this.column[idx];
|
||||||
|
this.column.splice(idx, 1);
|
||||||
|
const language = this.designer.setupState.formOptions.language;
|
||||||
|
this.localeOptions.forEach(item => {
|
||||||
|
if (language[item.value]) {
|
||||||
|
delete language[item.value][row.key]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
batchRmColumn() {
|
||||||
|
this.selected.forEach(item => {
|
||||||
|
this.rmColumn(this.column.indexOf(item));
|
||||||
|
});
|
||||||
|
this.selected = [];
|
||||||
|
},
|
||||||
|
selectionChange(list) {
|
||||||
|
this.selected = list;
|
||||||
|
},
|
||||||
|
randomString() {
|
||||||
|
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||||
|
let result = '';
|
||||||
|
const charactersLength = characters.length;
|
||||||
|
|
||||||
|
for (let i = 0; i < 7; i++) {
|
||||||
|
result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
||||||
|
}
|
||||||
|
return characters.charAt((this.uni++) % 26) + result;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
const language = this.designer.setupState.formOptions.language || {};
|
||||||
|
const column = {};
|
||||||
|
Object.keys(language).forEach(lang => {
|
||||||
|
Object.keys(language[lang]).forEach(key => {
|
||||||
|
if (!column[key]) {
|
||||||
|
column[key] = {
|
||||||
|
key: key,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
column[key][lang] = language[lang][key];
|
||||||
|
})
|
||||||
|
});
|
||||||
|
this.column = Object.values(column);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fd-lc-body, ._fd-lc-header {
|
||||||
|
padding: 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-lc-body {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-lc-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-language-config .el-table__cell {
|
||||||
|
height: 34px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-lc-handle {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
192
src/components/language/LanguageInput.vue
Normal file
192
src/components/language/LanguageInput.vue
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
<template>
|
||||||
|
<el-input class="_fd-language-input" :class="{'is-variable': isVar}" :placeholder="placeholder" :disabled="disabled"
|
||||||
|
:modelValue="modelValue"
|
||||||
|
@update:modelValue="onInput"
|
||||||
|
@blur="$emit('blur')"
|
||||||
|
:size="size || 'small'">
|
||||||
|
<template #append v-if="showLanguage !== false">
|
||||||
|
<el-popover placement="bottom-end" :width="300" :hide-after="0" trigger="click" ref="pop"
|
||||||
|
popper-class="_fd-language-popover">
|
||||||
|
<template #reference>
|
||||||
|
<i class="fc-icon icon-language"></i>
|
||||||
|
</template>
|
||||||
|
<div class="_fd-language-list">
|
||||||
|
<div class="_fd-language-header">
|
||||||
|
<div class="_fd-language-title">
|
||||||
|
{{ t('language.select') }}<i class="fc-icon icon-setting" @click="openConfig"></i>
|
||||||
|
</div>
|
||||||
|
<div class="_fd-language-name">
|
||||||
|
<template v-for="item in localeList" :key="item.value">
|
||||||
|
<div>{{ item.label }}</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<template v-for="lang in language" :key="lang.key">
|
||||||
|
<div class="_fd-language-item" @click="clickLang(lang.key)">
|
||||||
|
<template v-for="item in localeList" :key="item.value">
|
||||||
|
<div>{{ lang[item.value] || '-' }}</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</el-popover>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent} from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'LanguageInput',
|
||||||
|
inject: ['designer'],
|
||||||
|
emits: ['update:modelValue', 'blur', 'change'],
|
||||||
|
props: {
|
||||||
|
size: String,
|
||||||
|
placeholder: String,
|
||||||
|
modelValue: String,
|
||||||
|
disabled: Boolean,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isVar() {
|
||||||
|
return !!(this.modelValue || '').match(/^\{\{\s*\$t\.(.+)\s*\}\}$/);
|
||||||
|
},
|
||||||
|
t() {
|
||||||
|
return this.designer.setupState.t;
|
||||||
|
},
|
||||||
|
showLanguage() {
|
||||||
|
return this.designer.setupState.getConfig('showLanguage');
|
||||||
|
},
|
||||||
|
localeList() {
|
||||||
|
const localeOptions = this.designer.setupState.getConfig('localeOptions', [
|
||||||
|
{value: 'zh-cn', label: '简体中文'},
|
||||||
|
{value: 'en', label: 'English'},
|
||||||
|
]);
|
||||||
|
const localeList = [];
|
||||||
|
const locale = this.designer.props?.locale?.name || 'zh-cn';
|
||||||
|
localeOptions.forEach((item) => {
|
||||||
|
if (item.value === locale) {
|
||||||
|
localeList.unshift(item);
|
||||||
|
} else if (localeList.length < 2) {
|
||||||
|
localeList.push(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (localeList.length > 2) {
|
||||||
|
localeList.pop();
|
||||||
|
}
|
||||||
|
return localeList;
|
||||||
|
},
|
||||||
|
language() {
|
||||||
|
const language = this.designer.setupState.formOptions.language || {};
|
||||||
|
const column = {};
|
||||||
|
Object.keys(language).forEach(lang => {
|
||||||
|
Object.keys(language[lang]).forEach(key => {
|
||||||
|
if (!column[key]) {
|
||||||
|
column[key] = {
|
||||||
|
key: key,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
column[key][lang] = language[lang][key];
|
||||||
|
})
|
||||||
|
});
|
||||||
|
return Object.values(column);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
openConfig() {
|
||||||
|
this.designer.setupState.activeModule = 'language';
|
||||||
|
},
|
||||||
|
clickLang(key) {
|
||||||
|
this.onInput(`{{$t.${key}}}`);
|
||||||
|
this.$refs.pop.hide();
|
||||||
|
},
|
||||||
|
onInput(val) {
|
||||||
|
this.$emit('update:modelValue', val);
|
||||||
|
this.$emit('change', val);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fd-language-list {
|
||||||
|
max-height: 320px;
|
||||||
|
padding-top: 70px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-language-input .el-input-group__append {
|
||||||
|
width: 25px;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
color: var(--fc-text-color-3);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-language-input.is-variable input {
|
||||||
|
color: var(--fc-style-color-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-language-header, ._fd-language-item {
|
||||||
|
display: flex;
|
||||||
|
border-bottom: 1px solid var(--fc-line-color-3);
|
||||||
|
padding: 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-language-header {
|
||||||
|
font-weight: 500;
|
||||||
|
padding-top: 10px;
|
||||||
|
overflow: auto;
|
||||||
|
color: var(--fc-text-color-1);
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background-color: var(--fc-bg-color-1);
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-language-name > div, ._fd-language-item > div {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 5px;
|
||||||
|
min-width: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-language-title {
|
||||||
|
margin: 6px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-language-title .fc-icon {
|
||||||
|
color: var(--fc-style-color-1);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-language-name {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-language-name > div {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
color: var(--fc-text-color-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-language-item {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-language-item:hover {
|
||||||
|
color: var(--fc-style-color-1);
|
||||||
|
background-color: var(--fc-style-bg-color-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fd-language-popover {
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
</style>
|
141
src/components/mobile/City.vue
Normal file
141
src/components/mobile/City.vue
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_fc-city-m">
|
||||||
|
<van-field ref="el" :placeholder="placeholder" readonly :disabled="disabled"
|
||||||
|
@click="open"
|
||||||
|
:model-value="inputValue" :border="false" isLink>
|
||||||
|
<template #right-icon v-if="clearable && inputValue">
|
||||||
|
<i class="van-badge__wrapper van-icon van-icon-clear van-field__clear"
|
||||||
|
@click="clear"></i>
|
||||||
|
</template>
|
||||||
|
</van-field>
|
||||||
|
<van-popup :show="show" @update:show="(v) => show = v" round position="bottom">
|
||||||
|
<van-cascader
|
||||||
|
:modelValue="(modelValue && modelValue[modelValue.length-1]) || ''"
|
||||||
|
:options="province"
|
||||||
|
@close="show = false"
|
||||||
|
@finish="confirm"
|
||||||
|
/>
|
||||||
|
</van-popup>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent, markRaw} from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'FcCity',
|
||||||
|
props: {
|
||||||
|
modelValue: Array,
|
||||||
|
clearable: Boolean,
|
||||||
|
placeholder: String,
|
||||||
|
disabled: Boolean,
|
||||||
|
filter: Function,
|
||||||
|
level: {
|
||||||
|
type: Number,
|
||||||
|
default: 3
|
||||||
|
},
|
||||||
|
api: String,
|
||||||
|
},
|
||||||
|
emits: ['update:modelValue', 'change'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
inputValue: '',
|
||||||
|
show: false,
|
||||||
|
province: [],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
modelValue: {
|
||||||
|
handler(val) {
|
||||||
|
this.inputValue = (val || []).join(' / ');
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
city() {
|
||||||
|
if (this.value.p) {
|
||||||
|
for (let i = 0; i < this.province.length; i++) {
|
||||||
|
if (this.province[i].n === this.value.p) {
|
||||||
|
return this.province[i].d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
area() {
|
||||||
|
if (this.value.c) {
|
||||||
|
for (let i = 0; i < this.city.length; i++) {
|
||||||
|
if (this.city[i].n === this.value.c) {
|
||||||
|
return this.city[i]?.d || [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
open() {
|
||||||
|
if (this.disabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.show = true;
|
||||||
|
},
|
||||||
|
confirm({selectedOptions}) {
|
||||||
|
this.inputValue = selectedOptions.map((option) => option.text).join(' / ');
|
||||||
|
this.show = false;
|
||||||
|
const value = selectedOptions.map((option) => option.value);
|
||||||
|
this.$emit('update:modelValue', value);
|
||||||
|
this.$emit('change', value);
|
||||||
|
},
|
||||||
|
clear(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
this.inputValue = '';
|
||||||
|
this.$emit('update:modelValue', []);
|
||||||
|
this.$emit('change', []);
|
||||||
|
},
|
||||||
|
loadData(uri) {
|
||||||
|
return fetch(uri).then((res) => {
|
||||||
|
return res.json();
|
||||||
|
}).then((res) => {
|
||||||
|
this.province = markRaw(this.tidyOptions(this.filter ? this.filter(res) || [] : res, 0));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
tidyOptions(options, level) {
|
||||||
|
return options.map(opt => {
|
||||||
|
const item = {
|
||||||
|
text: opt.text || opt.n,
|
||||||
|
value: opt.value || opt.text || opt.n
|
||||||
|
};
|
||||||
|
if ((opt.children || opt.d) && level + 1 < this.level) {
|
||||||
|
item.children = this.tidyOptions(opt.children || opt.d, level + 1);
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
if (this.api) {
|
||||||
|
this.loadData(this.api);
|
||||||
|
} else {
|
||||||
|
this.loadData('https://unpkg.com/@province-city-china/level/level.min.json').catch(() => {
|
||||||
|
this.loadData('https://cdn.jsdelivr.net/npm/@province-city-china/level/level.min.json').catch(() => {
|
||||||
|
this.loadData('https://npm.onmicrosoft.cn/@province-city-china/level/level.min.json');
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fc-city-m {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-city-m .van-cell {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
152
src/components/mobile/SignaturePad.vue
Normal file
152
src/components/mobile/SignaturePad.vue
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_fc-m-signature">
|
||||||
|
<template v-if="modelValue">
|
||||||
|
<div class="_fc-m-signature-preview">
|
||||||
|
<i class="fc-icon icon-delete2" @click="remove"></i>
|
||||||
|
<img :src="modelValue" alt="signature">
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<div class="_fc-m-signature-btn" @click="visible = true">
|
||||||
|
<i class="fc-icon icon-edit2"></i> {{ formCreateInject.t('signaturePadTip') || '点击添加手写签名' }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<van-dialog v-model:show="visible" class="_fc-m-signature-dialog"
|
||||||
|
@confirm="submit" @cancel="clear" :confirm-button-text="formCreateInject.t('ok') || '确定'"
|
||||||
|
:cancel-button-text="formCreateInject.t('reset') || '重置'"
|
||||||
|
:confirm-button-disabled="isEmpty">
|
||||||
|
<template #title>
|
||||||
|
{{ formCreateInject.t('signaturePadTitle') || '请在虚线框内书写' }}
|
||||||
|
<i class="fc-icon icon-add2" @click="visible=false"></i>
|
||||||
|
</template>
|
||||||
|
<canvas class="_fc-m-signature-pad" ref="pad" width="320px" height="145px"></canvas>
|
||||||
|
</van-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent, markRaw} from 'vue';
|
||||||
|
import SignaturePad from 'signature_pad';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'SignaturePad',
|
||||||
|
emits: ['update:modelValue', 'change', 'remove'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
visible: false,
|
||||||
|
isEmpty: true,
|
||||||
|
signaturePad: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
modelValue: String,
|
||||||
|
penColor: String,
|
||||||
|
formCreateInject: Object,
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
visible(val) {
|
||||||
|
if (val) {
|
||||||
|
this.isEmpty = true;
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.signaturePad = markRaw(new SignaturePad(this.$refs.pad, {
|
||||||
|
penColor: this.penColor,
|
||||||
|
}));
|
||||||
|
this.signaturePad.addEventListener('endStroke', () => {
|
||||||
|
this.isEmpty = this.signaturePad.isEmpty();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.signaturePad.off();
|
||||||
|
this.signaturePad = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
clear() {
|
||||||
|
this.signaturePad.clear();
|
||||||
|
this.isEmpty = true;
|
||||||
|
},
|
||||||
|
submit() {
|
||||||
|
const res = this.signaturePad.toDataURL();
|
||||||
|
this.updateValue(res);
|
||||||
|
this.visible = false;
|
||||||
|
},
|
||||||
|
updateValue(val) {
|
||||||
|
this.$emit('update:modelValue', val);
|
||||||
|
this.$emit('change', val);
|
||||||
|
},
|
||||||
|
remove() {
|
||||||
|
this.updateValue('');
|
||||||
|
this.$emit('remove');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
._fc-m-signature {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-m-signature-btn, ._fc-m-signature-preview {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 160px;
|
||||||
|
height: 88px;
|
||||||
|
line-height: 88px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgb(201, 204, 216);
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px dashed rgb(212, 215, 224);
|
||||||
|
text-align: center;
|
||||||
|
background: rgb(255, 255, 255);
|
||||||
|
position: relative;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-m-signature-btn {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-m-signature-preview > img {
|
||||||
|
display: inline-block;
|
||||||
|
height: 88px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-m-signature-preview .icon-delete2 {
|
||||||
|
position: absolute;
|
||||||
|
top: 9px;
|
||||||
|
right: 9px;
|
||||||
|
display: inline-block;
|
||||||
|
line-height: 14px;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-m-signature-btn i {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-m-signature-dialog .van-dialog__header {
|
||||||
|
padding: 15px 0;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-m-signature-dialog .icon-add2 {
|
||||||
|
position: absolute;
|
||||||
|
right: 18px;
|
||||||
|
display: inline-block;
|
||||||
|
color: var(--fc-text-color-3);
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-m-signature-pad {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px dashed #D4D7E0;
|
||||||
|
background-image: linear-gradient(#FFFFFF 14px, transparent 0), linear-gradient(90deg, #FFFFFF 14px, #D4D7E0 0);
|
||||||
|
background-size: 15px 15px;
|
||||||
|
}
|
||||||
|
</style>
|
135
src/components/mobile/popup/Popup.vue
Normal file
135
src/components/mobile/popup/Popup.vue
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
<template>
|
||||||
|
<van-popup class="_fc-popup" closeable v-bind="$attrs" v-model:show="visible">
|
||||||
|
<div class="_fc-popup-title">{{ title }}</div>
|
||||||
|
<div class="_fc-popup-content">
|
||||||
|
<component :is="Form" :option="formOptions" :rule="formRule" :extendOption="true"
|
||||||
|
v-model:api="fapi"
|
||||||
|
:model-value="value"
|
||||||
|
:subForm="false"
|
||||||
|
@change="formChange"
|
||||||
|
@emit-event="$emit"></component>
|
||||||
|
</div>
|
||||||
|
<div class="_fc-popup-footer">
|
||||||
|
<template v-if="footer !== false">
|
||||||
|
<van-button block size="small" type="primary" class="fc-clock" @click="handleConfirm">{{formCreateInject.t('ok') || '确定'}}</van-button>
|
||||||
|
<van-button block size="small" class="fc-clock" style="margin-top: 10px" @click="close">{{formCreateInject.t('close') || '关闭'}}</van-button>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</van-popup>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent, markRaw, onUnmounted, reactive} from 'vue';
|
||||||
|
import {deepCopy} from '@form-create/utils/lib/deepextend';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'FcPopup',
|
||||||
|
emits: ['confirm', 'submit', 'validateFail', 'update:modelValue'],
|
||||||
|
props: {
|
||||||
|
formData: Object,
|
||||||
|
options: {
|
||||||
|
type: Object,
|
||||||
|
default: () => reactive(({
|
||||||
|
submitBtn: false,
|
||||||
|
resetBtn: false,
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
rule: Array,
|
||||||
|
autoClose: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
preview: Boolean,
|
||||||
|
modelValue: Object,
|
||||||
|
formCreateInject: Object,
|
||||||
|
title: String
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
formOptions() {
|
||||||
|
const opt = {...this.options};
|
||||||
|
if(this.preview) {
|
||||||
|
opt.preview = this.preview;
|
||||||
|
}
|
||||||
|
return opt;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
visible: false,
|
||||||
|
fapi: {},
|
||||||
|
value: {},
|
||||||
|
formRule: [],
|
||||||
|
Form: markRaw(this.formCreateInject.form.$form()),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
formChange() {
|
||||||
|
this.$emit('update:modelValue', this.fapi.formData());
|
||||||
|
},
|
||||||
|
open(formData) {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.visible = true;
|
||||||
|
this.value = deepCopy(formData || this.modelValue || this.formData || {});
|
||||||
|
this.formRule = deepCopy(this.rule || []);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
this.visible = false;
|
||||||
|
},
|
||||||
|
handleConfirm() {
|
||||||
|
this.$emit('confirm', this.fapi);
|
||||||
|
this.fapi.submit().then(formData => {
|
||||||
|
this.$emit('submit', formData, this.fapi, this.close);
|
||||||
|
if (this.autoClose) {
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
}).catch(e => {
|
||||||
|
this.$emit('validateFail', e, this.fapi);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.formCreateInject.api.top.bus.$on('fc.closeDialog', this.close);
|
||||||
|
onUnmounted(() => {
|
||||||
|
this.formCreateInject.api.top.bus.$off('fc.closeDialog', this.close);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
._fc-popup.van-popup {
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
padding-top: 50px;
|
||||||
|
padding-bottom: 110px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-popup-title {
|
||||||
|
position: absolute;
|
||||||
|
top: 16px;
|
||||||
|
left: 0;
|
||||||
|
color: #333;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-popup-content {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
._fc-popup-footer {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user