提交 17e54a2d authored 作者: 刘守彩's avatar 刘守彩

feat: update pkg

上级 2c7d1917
......@@ -22,7 +22,8 @@
## 启动发布
node 版本在`16`,其他版本可能启动不成功
- node 版本在`16`,其他版本可能启动不成功
- 项目已经注入[`scss变量`](http://120.55.57.35:4874/#/ty-ui/component/theme),可以在组件直接使用
```bash
# 安装包,已配置好镜像源,不需要在命令中指定registry
......
......@@ -38,21 +38,17 @@
"axios": "0.27.2",
"clipboard": "2.0.11",
"core-js": "3.28.0",
"dayjs": "1.11.10",
"echarts": "5.5.0",
"element-ui": "2.15.14",
"file-saver": "2.0.5",
"fuse.js": "6.4.3",
"highlight.js": "9.18.5",
"js-beautify": "1.15.1",
"js-cookie": "3.0.5",
"jsencrypt": "3.3.2",
"lodash-es": "4.17.21",
"nprogress": "0.2.0",
"screenfull": "5.0.2",
"sortablejs": "1.10.2",
"tailwindcss": "1.9.6",
"tinymce": "6.8.3",
"vue": "2.7.16",
"vue-count-to": "1.0.13",
"vue-cropper": "0.6.4",
"vue-echarts": "6.6.9",
"vue-meta": "2.4.0",
......
<template>
<div id="app">
<router-view />
<theme-picker />
</div>
</template>
<script>
import ThemePicker from '@/components/ThemePicker';
export default {
name: 'App',
components: { ThemePicker },
metaInfo() {
return {
title:
......
svg,
img {
display: inline;
}
img {
max-width: initial;
}
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
}
input[type='number'] {
-moz-appearance: textfield;
}
button:focus {
outline: none;
}
html {
height: 100%;
box-sizing: border-box;
}
body {
height: 100%;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB,
Microsoft YaHei, Arial, sans-serif;
}
label {
font-weight: 700;
}
#app {
height: 100%;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
.no-padding {
padding: 0px !important;
}
.padding-content {
padding: 4px 0;
}
div:focus {
outline: none;
}
.fr {
float: right;
}
.fl {
float: left;
}
.pr-5 {
padding-right: 5px;
}
.pl-5 {
padding-left: 5px;
}
.block {
display: block;
}
.pointer {
cursor: pointer;
}
.inlineBlock {
display: block;
}
.clearfix {
&:after {
visibility: hidden;
display: block;
font-size: 0;
content: ' ';
clear: both;
height: 0;
}
}
aside {
background: #eef1f6;
padding: 8px 24px;
margin-bottom: 20px;
border-radius: 2px;
display: block;
line-height: 32px;
font-size: 16px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
color: #2c3e50;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
a {
color: #337ab7;
cursor: pointer;
&:hover {
color: rgb(32, 160, 255);
}
}
}
// main-container全局样式
.app-container {
padding: 15px;
&.is-gray-bg {
background-color: #e5e5e5;
}
.app-container-inner {
padding: 20px;
height: calc(100vh - 80px);
display: flex;
flex-direction: column;
}
// 设置页面唯一表格弹性高度
.app-page-table {
margin-top: 10px;
flex: 1;
overflow: hidden;
display: flex;
flex-direction: column;
}
}
.link-type,
.link-type:focus {
color: #337ab7;
cursor: pointer;
&:hover {
color: rgb(32, 160, 255);
}
}
.top-right-btn {
position: relative;
float: right;
}
// cover some element-ui styles
.el-breadcrumb__inner,
.el-breadcrumb__inner a {
font-weight: 400 !important;
}
.el-upload {
input[type='file'] {
display: none !important;
}
}
.el-upload__input {
display: none;
}
.cell {
.el-tag {
margin-right: 0px;
}
}
.small-padding {
.cell {
padding-left: 5px;
padding-right: 5px;
}
}
.fixed-width {
.el-button--mini {
padding: 7px 10px;
width: 60px;
}
}
.status-col {
.cell {
padding: 0 10px;
text-align: center;
.el-tag {
margin-right: 0px;
}
}
}
// to fixed https://github.com/ElemeFE/element/issues/2461
.el-dialog {
transform: none;
left: 0;
position: relative;
margin: 0 auto;
}
// refine element ui upload
.upload-container {
.el-upload {
width: 100%;
.el-upload-dragger {
width: 100%;
height: 200px;
}
}
}
// dropdown
.el-dropdown-menu {
a {
display: block;
}
}
// fix date-picker ui bug in filter-item
.el-range-editor.el-input__inner {
display: inline-flex !important;
}
// to fix el-date-picker css style
.el-range-separator {
box-sizing: content-box;
}
.el-menu--collapse
> div
> .el-submenu
> .el-submenu__title
.el-submenu__icon-arrow {
display: none;
}
@import './variables.scss';
@import './mixin.scss';
@import '~@taiyuan/ty-ui/packages/styles/var.scss';
/* 设置tailwindcss */
@tailwind base;
@tailwind components;
@tailwind utilities;
/* 引入原有若依css,暂时不做变动 */
@import './element-ui.scss';
@import './transition.scss';
@import './sidebar.scss';
@import './base.scss';
svg,
img {
display: inline;
}
img {
max-width: initial;
}
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
}
input[type='number'] {
-moz-appearance: textfield;
}
button:focus {
outline: none;
}
html {
height: 100%;
box-sizing: border-box;
}
body {
height: 100%;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB,
Microsoft YaHei, Arial, sans-serif;
}
label {
font-weight: 700;
}
#app {
height: 100%;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
.no-padding {
padding: 0px !important;
}
.padding-content {
padding: 4px 0;
}
div:focus {
outline: none;
}
.fr {
float: right;
}
.fl {
float: left;
}
.pr-5 {
padding-right: 5px;
}
.pl-5 {
padding-left: 5px;
}
.block {
display: block;
}
.pointer {
cursor: pointer;
}
.inlineBlock {
display: block;
}
.clearfix {
&:after {
visibility: hidden;
display: block;
font-size: 0;
content: ' ';
clear: both;
height: 0;
}
}
aside {
background: #eef1f6;
padding: 8px 24px;
margin-bottom: 20px;
border-radius: 2px;
display: block;
line-height: 32px;
font-size: 16px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
color: #2c3e50;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
a {
color: #337ab7;
cursor: pointer;
&:hover {
color: rgb(32, 160, 255);
}
}
}
//main-container全局样式
.app-container {
padding: 20px;
}
.link-type,
.link-type:focus {
color: #337ab7;
cursor: pointer;
&:hover {
color: rgb(32, 160, 255);
}
}
.top-right-btn {
position: relative;
float: right;
}
/* 全局样式css,以g-开头命名 */
@import './global.scss';
<template>
<div :class="{ show: show }" class="header-search">
<svg-icon
class-name="search-icon"
icon-class="search"
@click.stop="click"
/>
<el-select
ref="headerSearchSelect"
v-model="search"
:remote-method="querySearch"
filterable
default-first-option
remote
placeholder="Search"
class="header-search-select"
@change="change"
>
<el-option
v-for="option in options"
:key="option.item.path"
:value="option.item"
:label="option.item.title.join(' > ')"
/>
</el-select>
</div>
</template>
<script>
// fuse is a lightweight fuzzy-search module
// make search results more in line with expectations
import Fuse from 'fuse.js/dist/fuse.min.js';
import path from 'path';
export default {
name: 'HeaderSearch',
data() {
return {
search: '',
options: [],
searchPool: [],
show: false,
fuse: undefined
};
},
computed: {
routes() {
return this.$store.getters.permission_routes;
}
},
watch: {
routes() {
this.searchPool = this.generateRoutes(this.routes);
},
searchPool(list) {
this.initFuse(list);
},
show(value) {
if (value) {
document.body.addEventListener('click', this.close);
} else {
document.body.removeEventListener('click', this.close);
}
}
},
mounted() {
this.searchPool = this.generateRoutes(this.routes);
},
methods: {
click() {
this.show = !this.show;
if (this.show) {
this.$refs.headerSearchSelect && this.$refs.headerSearchSelect.focus();
}
},
close() {
this.$refs.headerSearchSelect && this.$refs.headerSearchSelect.blur();
this.options = [];
this.show = false;
},
change(val) {
const path = val.path;
const query = val.query;
if (this.ishttp(val.path)) {
// http(s):// 路径新窗口打开
const pindex = path.indexOf('http');
window.open(path.substr(pindex, path.length), '_blank');
} else {
if (query) {
this.$router.push({ path: path, query: JSON.parse(query) });
} else {
this.$router.push(path);
}
}
this.search = '';
this.options = [];
this.$nextTick(() => {
this.show = false;
});
},
initFuse(list) {
this.fuse = new Fuse(list, {
shouldSort: true,
threshold: 0.4,
location: 0,
distance: 100,
minMatchCharLength: 1,
keys: [
{
name: 'title',
weight: 0.7
},
{
name: 'path',
weight: 0.3
}
]
});
},
// Filter out the routes that can be displayed in the sidebar
// And generate the internationalized title
generateRoutes(routes, basePath = '/', prefixTitle = []) {
let res = [];
for (const router of routes) {
// skip hidden router
if (router.hidden) {
continue;
}
const data = {
path: !this.ishttp(router.path)
? path.resolve(basePath, router.path)
: router.path,
title: [...prefixTitle]
};
if (router.meta && router.meta.title) {
data.title = [...data.title, router.meta.title];
if (router.redirect !== 'noRedirect') {
// only push the routes with title
// special case: need to exclude parent router without redirect
res.push(data);
}
}
if (router.query) {
data.query = router.query;
}
// recursive child routes
if (router.children) {
const tempRoutes = this.generateRoutes(
router.children,
data.path,
data.title
);
if (tempRoutes.length >= 1) {
res = [...res, ...tempRoutes];
}
}
}
return res;
},
querySearch(query) {
if (query !== '') {
this.options = this.fuse.search(query);
} else {
this.options = [];
}
},
ishttp(url) {
return url.indexOf('http://') !== -1 || url.indexOf('https://') !== -1;
}
}
};
</script>
<style lang="scss" scoped>
.header-search {
font-size: 0 !important;
.search-icon {
cursor: pointer;
font-size: 18px;
vertical-align: middle;
}
.header-search-select {
font-size: 18px;
transition: width 0.2s;
width: 0;
overflow: hidden;
background: transparent;
border-radius: 0;
display: inline-block;
vertical-align: middle;
::v-deep .el-input__inner {
border-radius: 0;
border: 0;
padding-left: 0;
padding-right: 0;
box-shadow: none !important;
border-bottom: 1px solid #d9d9d9;
vertical-align: middle;
}
}
&.show {
.header-search-select {
width: 210px;
margin-left: 10px;
}
}
}
</style>
<template>
<div>
<svg-icon icon-class="question" @click="goto" />
</div>
</template>
<script>
export default {
name: 'RuoYiDoc',
data() {
return {
url: 'http://doc.ruoyi.vip/ruoyi-vue'
};
},
methods: {
goto() {
window.open(this.url);
}
}
};
</script>
<template>
<div>
<svg-icon icon-class="github" @click="goto" />
</div>
</template>
<script>
export default {
name: 'RuoYiGit',
data() {
return {
url: 'https://gitee.com/y_project/RuoYi-Vue'
};
},
methods: {
goto() {
window.open(this.url);
}
}
};
</script>
<template>
<div>
<svg-icon
:icon-class="isFullscreen ? 'exit-fullscreen' : 'fullscreen'"
@click="click"
/>
</div>
</template>
<script>
import screenfull from 'screenfull';
export default {
name: 'Screenfull',
data() {
return {
isFullscreen: false
};
},
mounted() {
this.init();
},
beforeDestroy() {
this.destroy();
},
methods: {
click() {
if (!screenfull.isEnabled) {
this.$message({ message: '你的浏览器不支持全屏', type: 'warning' });
return false;
}
screenfull.toggle();
},
change() {
this.isFullscreen = screenfull.isFullscreen;
},
init() {
if (screenfull.isEnabled) {
screenfull.on('change', this.change);
}
},
destroy() {
if (screenfull.isEnabled) {
screenfull.off('change', this.change);
}
}
}
};
</script>
<style scoped>
.screenfull-svg {
display: inline-block;
cursor: pointer;
fill: #5a5e66;
width: 20px;
height: 20px;
vertical-align: 10px;
}
</style>
<template>
<el-dropdown trigger="click" @command="handleSetSize">
<div>
<svg-icon class-name="size-icon" icon-class="size" />
</div>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item
v-for="item of sizeOptions"
:key="item.value"
:disabled="size === item.value"
:command="item.value"
>
{{ item.label }}
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
<script>
export default {
data() {
return {
sizeOptions: [
{ label: 'Default', value: 'default' },
{ label: 'Medium', value: 'medium' },
{ label: 'Small', value: 'small' },
{ label: 'Mini', value: 'mini' }
]
};
},
computed: {
size() {
return this.$store.getters.size;
}
},
methods: {
handleSetSize(size) {
this.$ELEMENT.size = size;
this.$store.dispatch('app/setSize', size);
this.refreshView();
this.$message({
message: 'Switch Size Success',
type: 'success'
});
},
refreshView() {
// In order to make the cached page re-rendered
this.$store.dispatch('tagsView/delAllCachedViews', this.$route);
const { fullPath } = this.$route;
this.$nextTick(() => {
this.$router.replace({
path: '/redirect' + fullPath
});
});
}
}
};
</script>
<template>
<el-color-picker
v-model="theme"
:predefine="[
'#409EFF',
'#1890ff',
'#304156',
'#212121',
'#11a983',
'#13c2c2',
'#6959CD',
'#f5222d'
]"
class="theme-picker"
popper-class="theme-picker-dropdown"
/>
</template>
<script>
const version = require('element-ui/package.json').version; // element-ui version from node_modules
const ORIGINAL_THEME = '#409EFF'; // default color
export default {
data() {
return {
chalk: '', // content of theme-chalk css
theme: ''
};
},
computed: {
defaultTheme() {
return this.$store.state.settings.theme;
}
},
watch: {
defaultTheme: {
handler: function (val, oldVal) {
this.theme = val;
},
immediate: true
},
async theme(val) {
await this.setTheme(val);
}
},
created() {
if (this.defaultTheme !== ORIGINAL_THEME) {
this.setTheme(this.defaultTheme);
}
},
methods: {
async setTheme(val) {
const oldVal = this.chalk ? this.theme : ORIGINAL_THEME;
if (typeof val !== 'string') return;
const themeCluster = this.getThemeCluster(val.replace('#', ''));
const originalCluster = this.getThemeCluster(oldVal.replace('#', ''));
const getHandler = (variable, id) => {
return () => {
const originalCluster = this.getThemeCluster(
ORIGINAL_THEME.replace('#', '')
);
const newStyle = this.updateStyle(
this[variable],
originalCluster,
themeCluster
);
let styleTag = document.getElementById(id);
if (!styleTag) {
styleTag = document.createElement('style');
styleTag.setAttribute('id', id);
document.head.appendChild(styleTag);
}
styleTag.innerText = newStyle;
};
};
if (!this.chalk) {
console.log(version);
// const url = `https://unpkg.com/element-ui@${version}/lib/theme-chalk/index.css`;
// await this.getCSSString(url, 'chalk');
}
const chalkHandler = getHandler('chalk', 'chalk-style');
chalkHandler();
const styles = [].slice
.call(document.querySelectorAll('style'))
.filter((style) => {
const text = style.innerText;
return (
new RegExp(oldVal, 'i').test(text) && !/Chalk Variables/.test(text)
);
});
styles.forEach((style) => {
const { innerText } = style;
if (typeof innerText !== 'string') return;
style.innerText = this.updateStyle(
innerText,
originalCluster,
themeCluster
);
});
this.$emit('change', val);
},
updateStyle(style, oldCluster, newCluster) {
let newStyle = style;
oldCluster.forEach((color, index) => {
newStyle = newStyle.replace(new RegExp(color, 'ig'), newCluster[index]);
});
return newStyle;
},
getCSSString(url, variable) {
return new Promise((resolve) => {
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
if (xhr.readyState === 4 && xhr.status === 200) {
this[variable] = xhr.responseText.replace(/@font-face{[^}]+}/, '');
resolve();
}
};
xhr.open('GET', url);
xhr.send();
});
},
getThemeCluster(theme) {
const tintColor = (color, tint) => {
let red = parseInt(color.slice(0, 2), 16);
let green = parseInt(color.slice(2, 4), 16);
let blue = parseInt(color.slice(4, 6), 16);
if (tint === 0) {
// when primary color is in its rgb space
return [red, green, blue].join(',');
} else {
red += Math.round(tint * (255 - red));
green += Math.round(tint * (255 - green));
blue += Math.round(tint * (255 - blue));
red = red.toString(16);
green = green.toString(16);
blue = blue.toString(16);
return `#${red}${green}${blue}`;
}
};
const shadeColor = (color, shade) => {
let red = parseInt(color.slice(0, 2), 16);
let green = parseInt(color.slice(2, 4), 16);
let blue = parseInt(color.slice(4, 6), 16);
red = Math.round((1 - shade) * red);
green = Math.round((1 - shade) * green);
blue = Math.round((1 - shade) * blue);
red = red.toString(16);
green = green.toString(16);
blue = blue.toString(16);
return `#${red}${green}${blue}`;
};
const clusters = [theme];
for (let i = 0; i <= 9; i++) {
clusters.push(tintColor(theme, Number((i / 10).toFixed(2))));
}
clusters.push(shadeColor(theme, 0.1));
return clusters;
}
}
};
</script>
<style>
.theme-message,
.theme-picker-dropdown {
z-index: 99999 !important;
}
.theme-picker .el-color-picker__trigger {
height: 26px !important;
width: 26px !important;
padding: 2px;
}
.theme-picker-dropdown .el-color-dropdown__link-btn {
display: none;
}
</style>
......@@ -62,22 +62,12 @@ import { mapGetters } from 'vuex';
import Breadcrumb from '@/components/Breadcrumb';
import TopNav from '@/components/TopNav';
import Hamburger from '@/components/Hamburger';
// import Screenfull from '@/components/Screenfull';
// import SizeSelect from '@/components/SizeSelect';
// import Search from '@/components/HeaderSearch';
// import RuoYiGit from '@/components/RuoYi/Git';
// import RuoYiDoc from '@/components/RuoYi/Doc';
export default {
components: {
Breadcrumb,
TopNav,
Hamburger
// Screenfull,
// SizeSelect,
// Search
// RuoYiGit,
// RuoYiDoc
},
computed: {
...mapGetters(['sidebar', 'avatar', 'device']),
......
......@@ -132,10 +132,7 @@
</template>
<script>
import ThemePicker from '@/components/ThemePicker';
export default {
components: { ThemePicker },
data() {
return {
theme: this.$store.state.settings.theme,
......
......@@ -147,20 +147,6 @@ export const dynamicRoutes = [
meta: { title: '调度日志', activeMenu: '/monitor/job' }
}
]
},
{
path: '/tool/gen-edit',
component: Layout,
hidden: true,
permissions: ['tool:gen:edit'],
children: [
{
path: 'index/:tableId(\\d+)',
component: () => import('@/views/tool/gen/editTable'),
name: 'GenEdit',
meta: { title: '修改生成配置', activeMenu: '/tool/gen' }
}
]
}
];
......
export const formConf = {
formRef: 'elForm',
formModel: 'formData',
size: 'medium',
labelPosition: 'right',
labelWidth: 100,
formRules: 'rules',
gutter: 15,
disabled: false,
span: 24,
formBtns: true
};
export const inputComponents = [
{
label: '单行文本',
tag: 'el-input',
tagIcon: 'input',
placeholder: '请输入',
defaultValue: undefined,
span: 24,
labelWidth: null,
style: { width: '100%' },
clearable: true,
prepend: '',
append: '',
'prefix-icon': '',
'suffix-icon': '',
maxlength: null,
'show-word-limit': false,
readonly: false,
disabled: false,
required: true,
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/input'
},
{
label: '多行文本',
tag: 'el-input',
tagIcon: 'textarea',
type: 'textarea',
placeholder: '请输入',
defaultValue: undefined,
span: 24,
labelWidth: null,
autosize: {
minRows: 4,
maxRows: 4
},
style: { width: '100%' },
maxlength: null,
'show-word-limit': false,
readonly: false,
disabled: false,
required: true,
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/input'
},
{
label: '密码',
tag: 'el-input',
tagIcon: 'password',
placeholder: '请输入',
defaultValue: undefined,
span: 24,
'show-password': true,
labelWidth: null,
style: { width: '100%' },
clearable: true,
prepend: '',
append: '',
'prefix-icon': '',
'suffix-icon': '',
maxlength: null,
'show-word-limit': false,
readonly: false,
disabled: false,
required: true,
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/input'
},
{
label: '计数器',
tag: 'el-input-number',
tagIcon: 'number',
placeholder: '',
defaultValue: undefined,
span: 24,
labelWidth: null,
min: undefined,
max: undefined,
step: undefined,
'step-strictly': false,
precision: undefined,
'controls-position': '',
disabled: false,
required: true,
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/input-number'
}
];
export const selectComponents = [
{
label: '下拉选择',
tag: 'el-select',
tagIcon: 'select',
placeholder: '请选择',
defaultValue: undefined,
span: 24,
labelWidth: null,
style: { width: '100%' },
clearable: true,
disabled: false,
required: true,
filterable: false,
multiple: false,
options: [
{
label: '选项一',
value: 1
},
{
label: '选项二',
value: 2
}
],
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/select'
},
{
label: '级联选择',
tag: 'el-cascader',
tagIcon: 'cascader',
placeholder: '请选择',
defaultValue: [],
span: 24,
labelWidth: null,
style: { width: '100%' },
props: {
props: {
multiple: false
}
},
'show-all-levels': true,
disabled: false,
clearable: true,
filterable: false,
required: true,
options: [
{
id: 1,
value: 1,
label: '选项1',
children: [
{
id: 2,
value: 2,
label: '选项1-1'
}
]
}
],
dataType: 'dynamic',
labelKey: 'label',
valueKey: 'value',
childrenKey: 'children',
separator: '/',
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/cascader'
},
{
label: '单选框组',
tag: 'el-radio-group',
tagIcon: 'radio',
defaultValue: undefined,
span: 24,
labelWidth: null,
style: {},
optionType: 'default',
border: false,
size: 'medium',
disabled: false,
required: true,
options: [
{
label: '选项一',
value: 1
},
{
label: '选项二',
value: 2
}
],
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/radio'
},
{
label: '多选框组',
tag: 'el-checkbox-group',
tagIcon: 'checkbox',
defaultValue: [],
span: 24,
labelWidth: null,
style: {},
optionType: 'default',
border: false,
size: 'medium',
disabled: false,
required: true,
options: [
{
label: '选项一',
value: 1
},
{
label: '选项二',
value: 2
}
],
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/checkbox'
},
{
label: '开关',
tag: 'el-switch',
tagIcon: 'switch',
defaultValue: false,
span: 24,
labelWidth: null,
style: {},
disabled: false,
required: true,
'active-text': '',
'inactive-text': '',
'active-color': null,
'inactive-color': null,
'active-value': true,
'inactive-value': false,
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/switch'
},
{
label: '滑块',
tag: 'el-slider',
tagIcon: 'slider',
defaultValue: null,
span: 24,
labelWidth: null,
disabled: false,
required: true,
min: 0,
max: 100,
step: 1,
'show-stops': false,
range: false,
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/slider'
},
{
label: '时间选择',
tag: 'el-time-picker',
tagIcon: 'time',
placeholder: '请选择',
defaultValue: null,
span: 24,
labelWidth: null,
style: { width: '100%' },
disabled: false,
clearable: true,
required: true,
'picker-options': {
selectableRange: '00:00:00-23:59:59'
},
format: 'HH:mm:ss',
'value-format': 'HH:mm:ss',
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/time-picker'
},
{
label: '时间范围',
tag: 'el-time-picker',
tagIcon: 'time-range',
defaultValue: null,
span: 24,
labelWidth: null,
style: { width: '100%' },
disabled: false,
clearable: true,
required: true,
'is-range': true,
'range-separator': '至',
'start-placeholder': '开始时间',
'end-placeholder': '结束时间',
format: 'HH:mm:ss',
'value-format': 'HH:mm:ss',
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/time-picker'
},
{
label: '日期选择',
tag: 'el-date-picker',
tagIcon: 'date',
placeholder: '请选择',
defaultValue: null,
type: 'date',
span: 24,
labelWidth: null,
style: { width: '100%' },
disabled: false,
clearable: true,
required: true,
format: 'yyyy-MM-dd',
'value-format': 'yyyy-MM-dd',
readonly: false,
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/date-picker'
},
{
label: '日期范围',
tag: 'el-date-picker',
tagIcon: 'date-range',
defaultValue: null,
span: 24,
labelWidth: null,
style: { width: '100%' },
type: 'daterange',
'range-separator': '至',
'start-placeholder': '开始日期',
'end-placeholder': '结束日期',
disabled: false,
clearable: true,
required: true,
format: 'yyyy-MM-dd',
'value-format': 'yyyy-MM-dd',
readonly: false,
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/date-picker'
},
{
label: '评分',
tag: 'el-rate',
tagIcon: 'rate',
defaultValue: 0,
span: 24,
labelWidth: null,
style: {},
max: 5,
'allow-half': false,
'show-text': false,
'show-score': false,
disabled: false,
required: true,
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/rate'
},
{
label: '颜色选择',
tag: 'el-color-picker',
tagIcon: 'color',
defaultValue: null,
labelWidth: null,
'show-alpha': false,
'color-format': '',
disabled: false,
required: true,
size: 'medium',
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/color-picker'
},
{
label: '上传',
tag: 'el-upload',
tagIcon: 'upload',
action: 'https://jsonplaceholder.typicode.com/posts/',
defaultValue: null,
labelWidth: null,
disabled: false,
required: true,
accept: '',
name: 'file',
'auto-upload': true,
showTip: false,
buttonText: '点击上传',
fileSize: 2,
sizeUnit: 'MB',
'list-type': 'text',
multiple: false,
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/upload'
}
];
export const layoutComponents = [
{
layout: 'rowFormItem',
tagIcon: 'row',
type: 'default',
justify: 'start',
align: 'top',
label: '行容器',
layoutTree: true,
children: [],
document: 'https://element.eleme.cn/#/zh-CN/component/layout'
},
{
layout: 'colFormItem',
label: '按钮',
changeTag: true,
labelWidth: null,
tag: 'el-button',
tagIcon: 'button',
span: 24,
default: '主要按钮',
type: 'primary',
icon: 'el-icon-search',
size: 'medium',
disabled: false,
document: 'https://element.eleme.cn/#/zh-CN/component/button'
}
];
// 组件rule的触发方式,无触发方式的组件不生成rule
export const trigger = {
'el-input': 'blur',
'el-input-number': 'blur',
'el-select': 'change',
'el-radio-group': 'change',
'el-checkbox-group': 'change',
'el-cascader': 'change',
'el-time-picker': 'change',
'el-date-picker': 'change',
'el-rate': 'change'
};
const styles = {
'el-rate': '.el-rate{display: inline-block; vertical-align: text-top;}',
'el-upload': '.el-upload__tip{line-height: 1.2;}'
};
function addCss(cssList, el) {
const css = styles[el.tag];
css && cssList.indexOf(css) === -1 && cssList.push(css);
if (el.children) {
el.children.forEach((el2) => addCss(cssList, el2));
}
}
export function makeUpCss(conf) {
const cssList = [];
conf.fields.forEach((el) => addCss(cssList, el));
return cssList.join('\n');
}
export default [
{
layout: 'colFormItem',
tagIcon: 'input',
label: '手机号',
vModel: 'mobile',
formId: 6,
tag: 'el-input',
placeholder: '请输入手机号',
defaultValue: '',
span: 24,
style: { width: '100%' },
clearable: true,
prepend: '',
append: '',
'prefix-icon': 'el-icon-mobile',
'suffix-icon': '',
maxlength: 11,
'show-word-limit': true,
readonly: false,
disabled: false,
required: true,
changeTag: true,
regList: [
{
pattern: '/^1(3|4|5|7|8|9)\\d{9}$/',
message: '手机号格式错误'
}
]
}
];
/* eslint-disable max-len */
import { trigger } from './config';
let confGlobal;
let someSpanIsNot24;
export function dialogWrapper(str) {
return `<el-dialog v-bind="$attrs" v-on="$listeners" @open="onOpen" @close="onClose" title="Dialog Title">
${str}
<div slot="footer">
<el-button @click="close">取消</el-button>
<el-button type="primary" @click="handleConfirm">确定</el-button>
</div>
</el-dialog>`;
}
export function vueTemplate(str) {
return `<template>
<div>
${str}
</div>
</template>`;
}
export function vueScript(str) {
return `<script>
${str}
</script>`;
}
export function cssStyle(cssStr) {
return `<style>
${cssStr}
</style>`;
}
function buildFormTemplate(conf, child, type) {
let labelPosition = '';
if (conf.labelPosition !== 'right') {
labelPosition = `label-position="${conf.labelPosition}"`;
}
const disabled = conf.disabled ? `:disabled="${conf.disabled}"` : '';
let str = `<el-form ref="${conf.formRef}" :model="${
conf.formModel
}" :rules="${conf.formRules}" size="${conf.size}" ${disabled} label-width="${
conf.labelWidth
}px" ${labelPosition}>
${child}
${buildFromBtns(conf, type)}
</el-form>`;
if (someSpanIsNot24) {
str = `<el-row :gutter="${conf.gutter}">
${str}
</el-row>`;
}
return str;
}
function buildFromBtns(conf, type) {
let str = '';
if (conf.formBtns && type === 'file') {
str = `<el-form-item size="large">
<el-button type="primary" @click="submitForm">提交</el-button>
<el-button @click="resetForm">重置</el-button>
</el-form-item>`;
if (someSpanIsNot24) {
str = `<el-col :span="24">
${str}
</el-col>`;
}
}
return str;
}
// span不为24的用el-col包裹
function colWrapper(element, str) {
if (someSpanIsNot24 || element.span !== 24) {
return `<el-col :span="${element.span}">
${str}
</el-col>`;
}
return str;
}
const layouts = {
colFormItem(element) {
let labelWidth = '';
if (element.labelWidth && element.labelWidth !== confGlobal.labelWidth) {
labelWidth = `label-width="${element.labelWidth}px"`;
}
const required =
!trigger[element.tag] && element.required ? 'required' : '';
const tagDom = tags[element.tag] ? tags[element.tag](element) : null;
let str = `<el-form-item ${labelWidth} label="${element.label}" prop="${element.vModel}" ${required}>
${tagDom}
</el-form-item>`;
str = colWrapper(element, str);
return str;
},
rowFormItem(element) {
const type = element.type === 'default' ? '' : `type="${element.type}"`;
const justify =
element.type === 'default' ? '' : `justify="${element.justify}"`;
const align = element.type === 'default' ? '' : `align="${element.align}"`;
const gutter = element.gutter ? `gutter="${element.gutter}"` : '';
const children = element.children.map((el) => layouts[el.layout](el));
let str = `<el-row ${type} ${justify} ${align} ${gutter}>
${children.join('\n')}
</el-row>`;
str = colWrapper(element, str);
return str;
}
};
const tags = {
'el-button': (el) => {
const { disabled } = attrBuilder(el);
const type = el.type ? `type="${el.type}"` : '';
const icon = el.icon ? `icon="${el.icon}"` : '';
const size = el.size ? `size="${el.size}"` : '';
let child = buildElButtonChild(el);
if (child) child = `\n${child}\n`; // 换行
return `<${el.tag} ${type} ${icon} ${size} ${disabled}>${child}</${el.tag}>`;
},
'el-input': (el) => {
const { disabled, vModel, clearable, placeholder, width } = attrBuilder(el);
const maxlength = el.maxlength ? `:maxlength="${el.maxlength}"` : '';
const showWordLimit = el['show-word-limit'] ? 'show-word-limit' : '';
const readonly = el.readonly ? 'readonly' : '';
const prefixIcon = el['prefix-icon']
? `prefix-icon='${el['prefix-icon']}'`
: '';
const suffixIcon = el['suffix-icon']
? `suffix-icon='${el['suffix-icon']}'`
: '';
const showPassword = el['show-password'] ? 'show-password' : '';
const type = el.type ? `type="${el.type}"` : '';
const autosize =
el.autosize && el.autosize.minRows
? `:autosize="{minRows: ${el.autosize.minRows}, maxRows: ${el.autosize.maxRows}}"`
: '';
let child = buildElInputChild(el);
if (child) child = `\n${child}\n`; // 换行
return `<${el.tag} ${vModel} ${type} ${placeholder} ${maxlength} ${showWordLimit} ${readonly} ${disabled} ${clearable} ${prefixIcon} ${suffixIcon} ${showPassword} ${autosize} ${width}>${child}</${el.tag}>`;
},
'el-input-number': (el) => {
const { disabled, vModel, placeholder } = attrBuilder(el);
const controlsPosition = el['controls-position']
? `controls-position=${el['controls-position']}`
: '';
const min = el.min ? `:min='${el.min}'` : '';
const max = el.max ? `:max='${el.max}'` : '';
const step = el.step ? `:step='${el.step}'` : '';
const stepStrictly = el['step-strictly'] ? 'step-strictly' : '';
const precision = el.precision ? `:precision='${el.precision}'` : '';
return `<${el.tag} ${vModel} ${placeholder} ${step} ${stepStrictly} ${precision} ${controlsPosition} ${min} ${max} ${disabled}></${el.tag}>`;
},
'el-select': (el) => {
const { disabled, vModel, clearable, placeholder, width } = attrBuilder(el);
const filterable = el.filterable ? 'filterable' : '';
const multiple = el.multiple ? 'multiple' : '';
let child = buildElSelectChild(el);
if (child) child = `\n${child}\n`; // 换行
return `<${el.tag} ${vModel} ${placeholder} ${disabled} ${multiple} ${filterable} ${clearable} ${width}>${child}</${el.tag}>`;
},
'el-radio-group': (el) => {
const { disabled, vModel } = attrBuilder(el);
const size = `size="${el.size}"`;
let child = buildElRadioGroupChild(el);
if (child) child = `\n${child}\n`; // 换行
return `<${el.tag} ${vModel} ${size} ${disabled}>${child}</${el.tag}>`;
},
'el-checkbox-group': (el) => {
const { disabled, vModel } = attrBuilder(el);
const size = `size="${el.size}"`;
const min = el.min ? `:min="${el.min}"` : '';
const max = el.max ? `:max="${el.max}"` : '';
let child = buildElCheckboxGroupChild(el);
if (child) child = `\n${child}\n`; // 换行
return `<${el.tag} ${vModel} ${min} ${max} ${size} ${disabled}>${child}</${el.tag}>`;
},
'el-switch': (el) => {
const { disabled, vModel } = attrBuilder(el);
const activeText = el['active-text']
? `active-text="${el['active-text']}"`
: '';
const inactiveText = el['inactive-text']
? `inactive-text="${el['inactive-text']}"`
: '';
const activeColor = el['active-color']
? `active-color="${el['active-color']}"`
: '';
const inactiveColor = el['inactive-color']
? `inactive-color="${el['inactive-color']}"`
: '';
const activeValue =
el['active-value'] !== true
? `:active-value='${JSON.stringify(el['active-value'])}'`
: '';
const inactiveValue =
el['inactive-value'] !== false
? `:inactive-value='${JSON.stringify(el['inactive-value'])}'`
: '';
return `<${el.tag} ${vModel} ${activeText} ${inactiveText} ${activeColor} ${inactiveColor} ${activeValue} ${inactiveValue} ${disabled}></${el.tag}>`;
},
'el-cascader': (el) => {
const { disabled, vModel, clearable, placeholder, width } = attrBuilder(el);
const options = el.options ? `:options="${el.vModel}Options"` : '';
const props = el.props ? `:props="${el.vModel}Props"` : '';
const showAllLevels = el['show-all-levels']
? ''
: ':show-all-levels="false"';
const filterable = el.filterable ? 'filterable' : '';
const separator = el.separator === '/' ? '' : `separator="${el.separator}"`;
return `<${el.tag} ${vModel} ${options} ${props} ${width} ${showAllLevels} ${placeholder} ${separator} ${filterable} ${clearable} ${disabled}></${el.tag}>`;
},
'el-slider': (el) => {
const { disabled, vModel } = attrBuilder(el);
const min = el.min ? `:min='${el.min}'` : '';
const max = el.max ? `:max='${el.max}'` : '';
const step = el.step ? `:step='${el.step}'` : '';
const range = el.range ? 'range' : '';
const showStops = el['show-stops']
? `:show-stops="${el['show-stops']}"`
: '';
return `<${el.tag} ${min} ${max} ${step} ${vModel} ${range} ${showStops} ${disabled}></${el.tag}>`;
},
'el-time-picker': (el) => {
const { disabled, vModel, clearable, placeholder, width } = attrBuilder(el);
const startPlaceholder = el['start-placeholder']
? `start-placeholder="${el['start-placeholder']}"`
: '';
const endPlaceholder = el['end-placeholder']
? `end-placeholder="${el['end-placeholder']}"`
: '';
const rangeSeparator = el['range-separator']
? `range-separator="${el['range-separator']}"`
: '';
const isRange = el['is-range'] ? 'is-range' : '';
const format = el.format ? `format="${el.format}"` : '';
const valueFormat = el['value-format']
? `value-format="${el['value-format']}"`
: '';
const pickerOptions = el['picker-options']
? `:picker-options='${JSON.stringify(el['picker-options'])}'`
: '';
return `<${el.tag} ${vModel} ${isRange} ${format} ${valueFormat} ${pickerOptions} ${width} ${placeholder} ${startPlaceholder} ${endPlaceholder} ${rangeSeparator} ${clearable} ${disabled}></${el.tag}>`;
},
'el-date-picker': (el) => {
const { disabled, vModel, clearable, placeholder, width } = attrBuilder(el);
const startPlaceholder = el['start-placeholder']
? `start-placeholder="${el['start-placeholder']}"`
: '';
const endPlaceholder = el['end-placeholder']
? `end-placeholder="${el['end-placeholder']}"`
: '';
const rangeSeparator = el['range-separator']
? `range-separator="${el['range-separator']}"`
: '';
const format = el.format ? `format="${el.format}"` : '';
const valueFormat = el['value-format']
? `value-format="${el['value-format']}"`
: '';
const type = el.type === 'date' ? '' : `type="${el.type}"`;
const readonly = el.readonly ? 'readonly' : '';
return `<${el.tag} ${type} ${vModel} ${format} ${valueFormat} ${width} ${placeholder} ${startPlaceholder} ${endPlaceholder} ${rangeSeparator} ${clearable} ${readonly} ${disabled}></${el.tag}>`;
},
'el-rate': (el) => {
const { disabled, vModel } = attrBuilder(el);
// const max = el.max ? `:max='${el.max}'` : '';
const allowHalf = el['allow-half'] ? 'allow-half' : '';
const showText = el['show-text'] ? 'show-text' : '';
const showScore = el['show-score'] ? 'show-score' : '';
return `<${el.tag} ${vModel} ${allowHalf} ${showText} ${showScore} ${disabled}></${el.tag}>`;
},
'el-color-picker': (el) => {
const { disabled, vModel } = attrBuilder(el);
const size = `size="${el.size}"`;
const showAlpha = el['show-alpha'] ? 'show-alpha' : '';
const colorFormat = el['color-format']
? `color-format="${el['color-format']}"`
: '';
return `<${el.tag} ${vModel} ${size} ${showAlpha} ${colorFormat} ${disabled}></${el.tag}>`;
},
'el-upload': (el) => {
const disabled = el.disabled ? ":disabled='true'" : '';
const action = el.action ? `:action="${el.vModel}Action"` : '';
const multiple = el.multiple ? 'multiple' : '';
const listType =
el['list-type'] !== 'text' ? `list-type="${el['list-type']}"` : '';
const accept = el.accept ? `accept="${el.accept}"` : '';
const name = el.name !== 'file' ? `name="${el.name}"` : '';
const autoUpload =
el['auto-upload'] === false ? ':auto-upload="false"' : '';
const beforeUpload = `:before-upload="${el.vModel}BeforeUpload"`;
const fileList = `:file-list="${el.vModel}fileList"`;
const ref = `ref="${el.vModel}"`;
let child = buildElUploadChild(el);
if (child) child = `\n${child}\n`; // 换行
return `<${el.tag} ${ref} ${fileList} ${action} ${autoUpload} ${multiple} ${beforeUpload} ${listType} ${accept} ${name} ${disabled}>${child}</${el.tag}>`;
}
};
function attrBuilder(el) {
return {
vModel: `v-model="${confGlobal.formModel}.${el.vModel}"`,
clearable: el.clearable ? 'clearable' : '',
placeholder: el.placeholder ? `placeholder="${el.placeholder}"` : '',
width: el.style && el.style.width ? ':style="{width: \'100%\'}"' : '',
disabled: el.disabled ? ":disabled='true'" : ''
};
}
// el-buttin 子级
function buildElButtonChild(conf) {
const children = [];
if (conf.default) {
children.push(conf.default);
}
return children.join('\n');
}
// el-input innerHTML
function buildElInputChild(conf) {
const children = [];
if (conf.prepend) {
children.push(`<template slot="prepend">${conf.prepend}</template>`);
}
if (conf.append) {
children.push(`<template slot="append">${conf.append}</template>`);
}
return children.join('\n');
}
function buildElSelectChild(conf) {
const children = [];
if (conf.options && conf.options.length) {
children.push(
`<el-option v-for="(item, index) in ${conf.vModel}Options" :key="index" :label="item.label" :value="item.value" :disabled="item.disabled"></el-option>`
);
}
return children.join('\n');
}
function buildElRadioGroupChild(conf) {
const children = [];
if (conf.options && conf.options.length) {
const tag = conf.optionType === 'button' ? 'el-radio-button' : 'el-radio';
const border = conf.border ? 'border' : '';
children.push(
`<${tag} v-for="(item, index) in ${conf.vModel}Options" :key="index" :label="item.value" :disabled="item.disabled" ${border}>{{item.label}}</${tag}>`
);
}
return children.join('\n');
}
function buildElCheckboxGroupChild(conf) {
const children = [];
if (conf.options && conf.options.length) {
const tag =
conf.optionType === 'button' ? 'el-checkbox-button' : 'el-checkbox';
const border = conf.border ? 'border' : '';
children.push(
`<${tag} v-for="(item, index) in ${conf.vModel}Options" :key="index" :label="item.value" :disabled="item.disabled" ${border}>{{item.label}}</${tag}>`
);
}
return children.join('\n');
}
function buildElUploadChild(conf) {
const list = [];
if (conf['list-type'] === 'picture-card') {
list.push('<i class="el-icon-plus"></i>');
} else {
list.push(
`<el-button size="small" type="primary" icon="el-icon-upload">${conf.buttonText}</el-button>`
);
}
if (conf.showTip) {
list.push(
`<div slot="tip" class="el-upload__tip">只能上传不超过 ${conf.fileSize}${conf.sizeUnit}${conf.accept}文件</div>`
);
}
return list.join('\n');
}
export function makeUpHtml(conf, type) {
const htmlList = [];
confGlobal = conf;
someSpanIsNot24 = conf.fields.some((item) => item.span !== 24);
conf.fields.forEach((el) => {
htmlList.push(layouts[el.layout](el));
});
const htmlStr = htmlList.join('\n');
let temp = buildFormTemplate(conf, htmlStr, type);
if (type === 'dialog') {
temp = dialogWrapper(temp);
}
confGlobal = null;
return temp;
}
[
"platform-eleme",
"eleme",
"delete-solid",
"delete",
"s-tools",
"setting",
"user-solid",
"user",
"phone",
"phone-outline",
"more",
"more-outline",
"star-on",
"star-off",
"s-goods",
"goods",
"warning",
"warning-outline",
"question",
"info",
"remove",
"circle-plus",
"success",
"error",
"zoom-in",
"zoom-out",
"remove-outline",
"circle-plus-outline",
"circle-check",
"circle-close",
"s-help",
"help",
"minus",
"plus",
"check",
"close",
"picture",
"picture-outline",
"picture-outline-round",
"upload",
"upload2",
"download",
"camera-solid",
"camera",
"video-camera-solid",
"video-camera",
"message-solid",
"bell",
"s-cooperation",
"s-order",
"s-platform",
"s-fold",
"s-unfold",
"s-operation",
"s-promotion",
"s-home",
"s-release",
"s-ticket",
"s-management",
"s-open",
"s-shop",
"s-marketing",
"s-flag",
"s-comment",
"s-finance",
"s-claim",
"s-custom",
"s-opportunity",
"s-data",
"s-check",
"s-grid",
"menu",
"share",
"d-caret",
"caret-left",
"caret-right",
"caret-bottom",
"caret-top",
"bottom-left",
"bottom-right",
"back",
"right",
"bottom",
"top",
"top-left",
"top-right",
"arrow-left",
"arrow-right",
"arrow-down",
"arrow-up",
"d-arrow-left",
"d-arrow-right",
"video-pause",
"video-play",
"refresh",
"refresh-right",
"refresh-left",
"finished",
"sort",
"sort-up",
"sort-down",
"rank",
"loading",
"view",
"c-scale-to-original",
"date",
"edit",
"edit-outline",
"folder",
"folder-opened",
"folder-add",
"folder-remove",
"folder-delete",
"folder-checked",
"tickets",
"document-remove",
"document-delete",
"document-copy",
"document-checked",
"document",
"document-add",
"printer",
"paperclip",
"takeaway-box",
"search",
"monitor",
"attract",
"mobile",
"scissors",
"umbrella",
"headset",
"brush",
"mouse",
"coordinate",
"magic-stick",
"reading",
"data-line",
"data-board",
"pie-chart",
"data-analysis",
"collection-tag",
"film",
"suitcase",
"suitcase-1",
"receiving",
"collection",
"files",
"notebook-1",
"notebook-2",
"toilet-paper",
"office-building",
"school",
"table-lamp",
"house",
"no-smoking",
"smoking",
"shopping-cart-full",
"shopping-cart-1",
"shopping-cart-2",
"shopping-bag-1",
"shopping-bag-2",
"sold-out",
"sell",
"present",
"box",
"bank-card",
"money",
"coin",
"wallet",
"discount",
"price-tag",
"news",
"guide",
"male",
"female",
"thumb",
"cpu",
"link",
"connection",
"open",
"turn-off",
"set-up",
"chat-round",
"chat-line-round",
"chat-square",
"chat-dot-round",
"chat-dot-square",
"chat-line-square",
"message",
"postcard",
"position",
"turn-off-microphone",
"microphone",
"close-notification",
"bangzhu",
"time",
"odometer",
"crop",
"aim",
"switch-button",
"full-screen",
"copy-document",
"mic",
"stopwatch",
"medal-1",
"medal",
"trophy",
"trophy-1",
"first-aid-kit",
"discover",
"place",
"location",
"location-outline",
"location-information",
"add-location",
"delete-location",
"map-location",
"alarm-clock",
"timer",
"watch-1",
"watch",
"lock",
"unlock",
"key",
"service",
"mobile-phone",
"bicycle",
"truck",
"ship",
"basketball",
"football",
"soccer",
"baseball",
"wind-power",
"light-rain",
"lightning",
"heavy-rain",
"sunrise",
"sunrise-1",
"sunset",
"sunny",
"cloudy",
"partly-cloudy",
"cloudy-and-sunny",
"moon",
"moon-night",
"dish",
"dish-1",
"food",
"chicken",
"fork-spoon",
"knife-fork",
"burger",
"tableware",
"sugar",
"dessert",
"ice-cream",
"hot-water",
"water-cup",
"coffee-cup",
"cold-drink",
"goblet",
"goblet-full",
"goblet-square",
"goblet-square-full",
"refrigerator",
"grape",
"watermelon",
"cherry",
"apple",
"pear",
"orange",
"coffee",
"ice-tea",
"ice-drink",
"milk-tea",
"potato-strips",
"lollipop",
"ice-cream-square",
"ice-cream-round"
]
import { exportDefault, titleCase } from '@/utils/index';
import { trigger } from './config';
const units = {
KB: '1024',
MB: '1024 / 1024',
GB: '1024 / 1024 / 1024'
};
let confGlobal;
const inheritAttrs = {
file: '',
dialog: 'inheritAttrs: false,'
};
export function makeUpJs(conf, type) {
confGlobal = conf = JSON.parse(JSON.stringify(conf));
const dataList = [];
const ruleList = [];
const optionsList = [];
const propsList = [];
const methodList = mixinMethod(type);
const uploadVarList = [];
conf.fields.forEach((el) => {
buildAttributes(
el,
dataList,
ruleList,
optionsList,
methodList,
propsList,
uploadVarList
);
});
const script = buildexport(
conf,
type,
dataList.join('\n'),
ruleList.join('\n'),
optionsList.join('\n'),
uploadVarList.join('\n'),
propsList.join('\n'),
methodList.join('\n')
);
confGlobal = null;
return script;
}
function buildAttributes(
el,
dataList,
ruleList,
optionsList,
methodList,
propsList,
uploadVarList
) {
buildData(el, dataList);
buildRules(el, ruleList);
if (el.options && el.options.length) {
buildOptions(el, optionsList);
if (el.dataType === 'dynamic') {
const model = `${el.vModel}Options`;
const options = titleCase(model);
buildOptionMethod(`get${options}`, model, methodList);
}
}
if (el.props && el.props.props) {
buildProps(el, propsList);
}
if (el.action && el.tag === 'el-upload') {
uploadVarList.push(
`${el.vModel}Action: '${el.action}',
${el.vModel}fileList: [],`
);
methodList.push(buildBeforeUpload(el));
if (!el['auto-upload']) {
methodList.push(buildSubmitUpload(el));
}
}
if (el.children) {
el.children.forEach((el2) => {
buildAttributes(
el2,
dataList,
ruleList,
optionsList,
methodList,
propsList,
uploadVarList
);
});
}
}
function mixinMethod(type) {
const list = [];
const minxins = {
file: confGlobal.formBtns
? {
submitForm: `submitForm() {
this.$refs['${confGlobal.formRef}'].validate(valid => {
if(!valid) return
// TODO 提交表单
})
},`,
resetForm: `resetForm() {
this.$refs['${confGlobal.formRef}'].resetFields()
},`
}
: null,
dialog: {
onOpen: 'onOpen() {},',
onClose: `onClose() {
this.$refs['${confGlobal.formRef}'].resetFields()
},`,
close: `close() {
this.$emit('update:visible', false)
},`,
handleConfirm: `handleConfirm() {
this.$refs['${confGlobal.formRef}'].validate(valid => {
if(!valid) return
this.close()
})
},`
}
};
const methods = minxins[type];
if (methods) {
Object.keys(methods).forEach((key) => {
list.push(methods[key]);
});
}
return list;
}
function buildData(conf, dataList) {
if (conf.vModel === undefined) return;
let defaultValue;
if (typeof conf.defaultValue === 'string' && !conf.multiple) {
defaultValue = `'${conf.defaultValue}'`;
} else {
defaultValue = `${JSON.stringify(conf.defaultValue)}`;
}
dataList.push(`${conf.vModel}: ${defaultValue},`);
}
function buildRules(conf, ruleList) {
if (conf.vModel === undefined) return;
const rules = [];
if (trigger[conf.tag]) {
if (conf.required) {
const type = Array.isArray(conf.defaultValue) ? "type: 'array'," : '';
let message = Array.isArray(conf.defaultValue)
? `请至少选择一个${conf.vModel}`
: conf.placeholder;
if (message === undefined) message = `${conf.label}不能为空`;
rules.push(
`{ required: true, ${type} message: '${message}', trigger: '${
trigger[conf.tag]
}' }`
);
}
if (conf.regList && Array.isArray(conf.regList)) {
conf.regList.forEach((item) => {
if (item.pattern) {
rules.push(
// eslint-disable-next-line
`{ pattern: ${eval(item.pattern)}, message: '${
item.message
}', trigger: '${trigger[conf.tag]}' }`
);
}
});
}
ruleList.push(`${conf.vModel}: [${rules.join(',')}],`);
}
}
function buildOptions(conf, optionsList) {
if (conf.vModel === undefined) return;
if (conf.dataType === 'dynamic') {
conf.options = [];
}
const str = `${conf.vModel}Options: ${JSON.stringify(conf.options)},`;
optionsList.push(str);
}
function buildProps(conf, propsList) {
if (conf.dataType === 'dynamic') {
conf.valueKey !== 'value' && (conf.props.props.value = conf.valueKey);
conf.labelKey !== 'label' && (conf.props.props.label = conf.labelKey);
conf.childrenKey !== 'children' &&
(conf.props.props.children = conf.childrenKey);
}
const str = `${conf.vModel}Props: ${JSON.stringify(conf.props.props)},`;
propsList.push(str);
}
function buildBeforeUpload(conf) {
const unitNum = units[conf.sizeUnit];
let rightSizeCode = '';
let acceptCode = '';
const returnList = [];
if (conf.fileSize) {
rightSizeCode = `let isRightSize = file.size / ${unitNum} < ${conf.fileSize}
if(!isRightSize){
this.$message.error('文件大小超过 ${conf.fileSize}${conf.sizeUnit}')
}`;
returnList.push('isRightSize');
}
if (conf.accept) {
acceptCode = `let isAccept = new RegExp('${conf.accept}').test(file.type)
if(!isAccept){
this.$message.error('应该选择${conf.accept}类型的文件')
}`;
returnList.push('isAccept');
}
const str = `${conf.vModel}BeforeUpload(file) {
${rightSizeCode}
${acceptCode}
return ${returnList.join('&&')}
},`;
return returnList.length ? str : '';
}
function buildSubmitUpload(conf) {
const str = `submitUpload() {
this.$refs['${conf.vModel}'].submit()
},`;
return str;
}
function buildOptionMethod(methodName, model, methodList) {
const str = `${methodName}() {
// TODO 发起请求获取数据
this.${model}
},`;
methodList.push(str);
}
function buildexport(
conf,
type,
data,
rules,
selectOptions,
uploadVar,
props,
methods
) {
const str = `${exportDefault}{
${inheritAttrs[type]}
components: {},
props: [],
data () {
return {
${conf.formModel}: {
${data}
},
${conf.formRules}: {
${rules}
},
${uploadVar}
${selectOptions}
${props}
}
},
computed: {},
watch: {},
created () {},
mounted () {},
methods: {
${methods}
}
}`;
return str;
}
import { makeMap } from '@/utils/index';
// 参考https://github.com/vuejs/vue/blob/v2.6.10/src/platforms/web/server/util.js
const isAttr = makeMap(
'accept,accept-charset,accesskey,action,align,alt,async,autocomplete,' +
'autofocus,autoplay,autosave,bgcolor,border,buffered,challenge,charset,' +
'checked,cite,class,code,codebase,color,cols,colspan,content,http-equiv,' +
'name,contenteditable,contextmenu,controls,coords,data,datetime,default,' +
'defer,dir,dirname,disabled,download,draggable,dropzone,enctype,method,for,' +
'form,formaction,headers,height,hidden,high,href,hreflang,http-equiv,' +
'icon,id,ismap,itemprop,keytype,kind,label,lang,language,list,loop,low,' +
'manifest,max,maxlength,media,method,GET,POST,min,multiple,email,file,' +
'muted,name,novalidate,open,optimum,pattern,ping,placeholder,poster,' +
'preload,radiogroup,readonly,rel,required,reversed,rows,rowspan,sandbox,' +
'scope,scoped,seamless,selected,shape,size,type,text,password,sizes,span,' +
'spellcheck,src,srcdoc,srclang,srcset,start,step,style,summary,tabindex,' +
'target,title,type,usemap,value,width,wrap'
);
function vModel(self, dataObject, defaultValue) {
dataObject.props.value = defaultValue;
dataObject.on.input = (val) => {
self.$emit('input', val);
};
}
const componentChild = {
'el-button': {
default(h, conf, key) {
return conf[key];
}
},
'el-input': {
prepend(h, conf, key) {
return <template slot="prepend">{conf[key]}</template>;
},
append(h, conf, key) {
return <template slot="append">{conf[key]}</template>;
}
},
'el-select': {
options(h, conf, key) {
const list = [];
conf.options.forEach((item) => {
list.push(
<el-option
label={item.label}
value={item.value}
disabled={item.disabled}
></el-option>
);
});
return list;
}
},
'el-radio-group': {
options(h, conf, key) {
const list = [];
conf.options.forEach((item) => {
if (conf.optionType === 'button') {
list.push(
<el-radio-button label={item.value}>{item.label}</el-radio-button>
);
} else {
list.push(
<el-radio label={item.value} border={conf.border}>
{item.label}
</el-radio>
);
}
});
return list;
}
},
'el-checkbox-group': {
options(h, conf, key) {
const list = [];
conf.options.forEach((item) => {
if (conf.optionType === 'button') {
list.push(
<el-checkbox-button label={item.value}>
{item.label}
</el-checkbox-button>
);
} else {
list.push(
<el-checkbox label={item.value} border={conf.border}>
{item.label}
</el-checkbox>
);
}
});
return list;
}
},
'el-upload': {
'list-type': (h, conf, key) => {
const list = [];
if (conf['list-type'] === 'picture-card') {
list.push(<i class="el-icon-plus"></i>);
} else {
list.push(
<el-button size="small" type="primary" icon="el-icon-upload">
{conf.buttonText}
</el-button>
);
}
if (conf.showTip) {
list.push(
<div slot="tip" class="el-upload__tip">
只能上传不超过 {conf.fileSize}
{conf.sizeUnit} {conf.accept}文件
</div>
);
}
return list;
}
}
};
export default {
render(h) {
const dataObject = {
attrs: {},
props: {},
on: {},
style: {}
};
const confClone = JSON.parse(JSON.stringify(this.conf));
const children = [];
const childObjs = componentChild[confClone.tag];
if (childObjs) {
Object.keys(childObjs).forEach((key) => {
const childFunc = childObjs[key];
if (confClone[key]) {
children.push(childFunc(h, confClone, key));
}
});
}
Object.keys(confClone).forEach((key) => {
const val = confClone[key];
if (key === 'vModel') {
vModel(this, dataObject, confClone.defaultValue);
} else if (dataObject[key]) {
dataObject[key] = val;
} else if (!isAttr(key)) {
dataObject.props[key] = val;
} else {
dataObject.attrs[key] = val;
}
});
return h(this.conf.tag, dataObject, children);
},
props: ['conf']
};
import { parseTime } from './ruoyi';
import dayjs from 'dayjs';
/**
* 表格时间格式化
*/
export function formatDate(cellValue) {
if (cellValue == null || cellValue == '') return '';
var date = new Date(cellValue);
var year = date.getFullYear();
var month =
date.getMonth() + 1 < 10
? '0' + (date.getMonth() + 1)
: date.getMonth() + 1;
var day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate();
var hours = date.getHours() < 10 ? '0' + date.getHours() : date.getHours();
var minutes =
date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes();
var seconds =
date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds();
return (
year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds
);
}
/**
* @param {number} time
* @param {string} option
* @returns {string}
*/
export function formatTime(time, option) {
if (('' + time).length === 10) {
time = parseInt(time) * 1000;
} else {
time = +time;
}
const d = new Date(time);
const now = Date.now();
const diff = (now - d) / 1000;
if (diff < 30) {
return '刚刚';
} else if (diff < 3600) {
// less 1 hour
return Math.ceil(diff / 60) + '分钟前';
} else if (diff < 3600 * 24) {
return Math.ceil(diff / 3600) + '小时前';
} else if (diff < 3600 * 24 * 2) {
return '1天前';
}
if (option) {
return parseTime(time, option);
} else {
return (
d.getMonth() +
1 +
'月' +
d.getDate() +
'日' +
d.getHours() +
'时' +
d.getMinutes() +
'分'
);
}
}
/**
* @param {string} url
* @returns {Object}
*/
export function getQueryObject(url) {
url = url == null ? window.location.href : url;
const search = url.substring(url.lastIndexOf('?') + 1);
const obj = {};
const reg = /([^?&=]+)=([^?&=]*)/g;
search.replace(reg, (rs, $1, $2) => {
const name = decodeURIComponent($1);
let val = decodeURIComponent($2);
val = String(val);
obj[name] = val;
return rs;
});
return obj;
}
/**
* @param {string} input value
* @returns {number} output value
*/
export function byteLength(str) {
// returns the byte length of an utf8 string
let s = str.length;
for (var i = str.length - 1; i >= 0; i--) {
const code = str.charCodeAt(i);
if (code > 0x7f && code <= 0x7ff) s++;
else if (code > 0x7ff && code <= 0xffff) s += 2;
if (code >= 0xdc00 && code <= 0xdfff) i--;
}
return s;
}
/**
* @param {Array} actual
* @returns {Array}
*/
export function cleanArray(actual) {
const newArray = [];
for (let i = 0; i < actual.length; i++) {
if (actual[i]) {
newArray.push(actual[i]);
}
}
return newArray;
}
/**
* @param {Object} json
* @returns {Array}
*/
export function param(json) {
if (!json) return '';
return cleanArray(
Object.keys(json).map((key) => {
if (json[key] === undefined) return '';
return encodeURIComponent(key) + '=' + encodeURIComponent(json[key]);
})
).join('&');
}
/**
* @param {string} url
* @returns {Object}
*/
export function param2Obj(url) {
const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ');
if (!search) {
return {};
}
const obj = {};
const searchArr = search.split('&');
searchArr.forEach((v) => {
const index = v.indexOf('=');
if (index !== -1) {
const name = v.substring(0, index);
const val = v.substring(index + 1, v.length);
obj[name] = val;
}
});
return obj;
}
/**
* @param {string} val
* @returns {string}
*/
export function html2Text(val) {
const div = document.createElement('div');
div.innerHTML = val;
return div.textContent || div.innerText;
}
/**
* Merges two objects, giving the last one precedence
* @param {Object} target
* @param {(Object|Array)} source
* @returns {Object}
*/
export function objectMerge(target, source) {
if (typeof target !== 'object') {
target = {};
}
if (Array.isArray(source)) {
return source.slice();
export { debounce } from 'lodash-es';
export { throttle } from 'lodash-es';
export { cloneDeep } from 'lodash-es';
export function formatDate(date, fmt = 'YYYY-MM-DD') {
if (!date) {
return '';
}
Object.keys(source).forEach((property) => {
const sourceProperty = source[property];
if (typeof sourceProperty === 'object') {
target[property] = objectMerge(target[property], sourceProperty);
} else {
target[property] = sourceProperty;
}
});
return target;
}
/**
* @param {HTMLElement} element
* @param {string} className
*/
export function toggleClass(element, className) {
if (!element || !className) {
return;
}
let classString = element.className;
const nameIndex = classString.indexOf(className);
if (nameIndex === -1) {
classString += '' + className;
} else {
classString =
classString.substr(0, nameIndex) +
classString.substr(nameIndex + className.length);
}
element.className = classString;
}
/**
* @param {string} type
* @returns {Date}
*/
export function getTime(type) {
if (type === 'start') {
return new Date().getTime() - 3600 * 1000 * 24 * 90;
} else {
return new Date(new Date().toDateString());
}
}
/**
* @param {Function} func
* @param {number} wait
* @param {boolean} immediate
* @return {*}
*/
export function debounce(func, wait, immediate) {
let timeout, args, context, timestamp, result;
const later = function () {
// 据上一次触发时间间隔
const last = +new Date() - timestamp;
// 上次被包装函数被调用时间间隔 last 小于设定时间间隔 wait
if (last < wait && last > 0) {
timeout = setTimeout(later, wait - last);
} else {
timeout = null;
// 如果设定为immediate===true,因为开始边界已经调用过了此处无需调用
if (!immediate) {
result = func.apply(context, args);
if (!timeout) context = args = null;
}
}
};
return function (...args) {
context = this;
timestamp = +new Date();
const callNow = immediate && !timeout;
// 如果延时不存在,重新设定延时
if (!timeout) timeout = setTimeout(later, wait);
if (callNow) {
result = func.apply(context, args);
context = args = null;
}
return result;
};
}
/**
* This is just a simple version of deep copy
* Has a lot of edge cases bug
* If you want to use a perfect deep copy, use lodash's _.cloneDeep
* @param {Object} source
* @returns {Object}
*/
export function deepClone(source) {
if (!source && typeof source !== 'object') {
throw new Error('error arguments', 'deepClone');
}
const targetObj = source.constructor === Array ? [] : {};
Object.keys(source).forEach((keys) => {
if (source[keys] && typeof source[keys] === 'object') {
targetObj[keys] = deepClone(source[keys]);
} else {
targetObj[keys] = source[keys];
}
});
return targetObj;
}
/**
* @param {Array} arr
* @returns {Array}
*/
export function uniqueArr(arr) {
return Array.from(new Set(arr));
}
/**
* @returns {string}
*/
export function createUniqueString() {
const timestamp = +new Date() + '';
const randomNum = parseInt((1 + Math.random()) * 65536) + '';
return (+(randomNum + timestamp)).toString(32);
}
/**
* Check if an element has a class
* @param {HTMLElement} elm
* @param {string} cls
* @returns {boolean}
*/
export function hasClass(ele, cls) {
return !!ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)'));
}
/**
* Add class to element
* @param {HTMLElement} elm
* @param {string} cls
*/
export function addClass(ele, cls) {
if (!hasClass(ele, cls)) ele.className += ' ' + cls;
}
/**
* Remove class from element
* @param {HTMLElement} elm
* @param {string} cls
*/
export function removeClass(ele, cls) {
if (hasClass(ele, cls)) {
const reg = new RegExp('(\\s|^)' + cls + '(\\s|$)');
ele.className = ele.className.replace(reg, ' ');
}
}
export function makeMap(str, expectsLowerCase) {
const map = Object.create(null);
const list = str.split(',');
for (let i = 0; i < list.length; i++) {
map[list[i]] = true;
}
return expectsLowerCase ? (val) => map[val.toLowerCase()] : (val) => map[val];
}
export const exportDefault = 'export default ';
export const beautifierConf = {
html: {
indent_size: '2',
indent_char: ' ',
max_preserve_newlines: '-1',
preserve_newlines: false,
keep_array_indentation: false,
break_chained_methods: false,
indent_scripts: 'separate',
brace_style: 'end-expand',
space_before_conditional: true,
unescape_strings: false,
jslint_happy: false,
end_with_newline: true,
wrap_line_length: '110',
indent_inner_html: true,
comma_first: false,
e4x: true,
indent_empty_lines: true
},
js: {
indent_size: '2',
indent_char: ' ',
max_preserve_newlines: '-1',
preserve_newlines: false,
keep_array_indentation: false,
break_chained_methods: false,
indent_scripts: 'normal',
brace_style: 'end-expand',
space_before_conditional: true,
unescape_strings: false,
jslint_happy: true,
end_with_newline: true,
wrap_line_length: '110',
indent_inner_html: true,
comma_first: false,
e4x: true,
indent_empty_lines: true
}
};
// 首字母大小
export function titleCase(str) {
return str.replace(/( |^)[a-z]/g, (L) => L.toUpperCase());
}
// 下划转驼峰
export function camelCase(str) {
return str.replace(/_[a-z]/g, (str1) => str1.substr(-1).toUpperCase());
}
export function isNumberStr(str) {
return /^[+-]?(0|([1-9]\d*))(\.\d+)?$/g.test(str);
return dayjs(date).format(fmt);
}
<template>
<div>
<Tinymce v-model="TinymceContent" />
</div>
</template>
<script>
import Tinymce from '@/components/Tinymce';
export default {
name: 'MenuEditor',
components: {
Tinymce
},
data() {
return {
TinymceContent: `<h1 style="text-align: center;">如果我是孙悟空</h1>
<p class="">如果我是孙悟空穿越到现代,我会尽量保持低调并适应现代生活。以下是我可能会采取的一些策略:</p>
<ol>
<li><strong>学习现代知识</strong>:首先,我会努力学习现代的语言、文化、科技和生活方式。这可以通过阅读书籍、观看电视节目和互联网资源来实现。</li>
<li><strong>运用隐身术</strong>:如果可能的话,我会使用我的隐身术来避免引起不必要的注意。这样,我就可以在不被人发现的情况下观察和学习现代社会的运作方式。</li>
<li><strong>利用智慧和技能</strong>:虽然现代科技与我所在的时代大不相同,但我的智慧和技能仍然可以派上用场。例如,我可以利用我的变化能力来帮助人们解决一些难题,或者利用我的快速移动能力来帮助救援人员迅速到达现场。</li>
<li><strong>保护隐私</strong>:为了避免被现代社会的监控设备发现,我会尽量避免使用电子设备,如手机、电脑等。同时,我也会注意保护自己的隐私,避免暴露自己的身份。</li>
<li><strong>融入社区</strong>:我会尝试融入当地社区,与人们建立联系。我可以扮演一个普通的市民,参与社区活动,了解当地的文化和生活方式。</li>
<li><strong>保持谦逊</strong>:虽然我有强大的能力,但我会保持谦逊和低调,避免引起不必要的注意。我会尊重现代社会的规则和习惯,尽量不干涉他人的生活。</li>
</ol>
<p class="">总之,如果我是孙悟空穿越到现代,我会尽力适应现代生活,保持低调并尊重当地的文化和习惯。同时,我也会利用我的智慧和技能来帮助人们解决问题,为社会做出贡献。</p>`
};
},
mounted() {},
methods: {}
};
</script>
<template>
<div>
<el-dialog
v-bind="$attrs"
width="500px"
:close-on-click-modal="false"
:modal-append-to-body="false"
v-on="$listeners"
@open="onOpen"
@close="onClose"
>
<el-row :gutter="15">
<el-form
ref="elForm"
:model="formData"
:rules="rules"
size="medium"
label-width="100px"
>
<el-col :span="24">
<el-form-item label="生成类型" prop="type">
<el-radio-group v-model="formData.type">
<el-radio-button
v-for="(item, index) in typeOptions"
:key="index"
:label="item.value"
:disabled="item.disabled"
>
{{ item.label }}
</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item v-if="showFileName" label="文件名" prop="fileName">
<el-input
v-model="formData.fileName"
placeholder="请输入文件名"
clearable
/>
</el-form-item>
</el-col>
</el-form>
</el-row>
<div slot="footer">
<el-button @click="close"> 取消 </el-button>
<el-button type="primary" @click="handleConfirm"> 确定 </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
export default {
inheritAttrs: false,
props: ['showFileName'],
data() {
return {
formData: {
fileName: undefined,
type: 'file'
},
rules: {
fileName: [
{
required: true,
message: '请输入文件名',
trigger: 'blur'
}
],
type: [
{
required: true,
message: '生成类型不能为空',
trigger: 'change'
}
]
},
typeOptions: [
{
label: '页面',
value: 'file'
},
{
label: '弹窗',
value: 'dialog'
}
]
};
},
computed: {},
watch: {},
mounted() {},
methods: {
onOpen() {
if (this.showFileName) {
this.formData.fileName = `${+new Date()}.vue`;
}
},
onClose() {},
close(e) {
this.$emit('update:visible', false);
},
handleConfirm() {
this.$refs.elForm.validate((valid) => {
if (!valid) return;
this.$emit('confirm', { ...this.formData });
this.close();
});
}
}
};
</script>
<script>
import draggable from 'vuedraggable';
import render from '@/utils/generator/render';
const components = {
itemBtns(h, element, index, parent) {
const { copyItem, deleteItem } = this.$listeners;
return [
<span
class="drawing-item-copy"
title="复制"
onClick={(event) => {
copyItem(element, parent);
event.stopPropagation();
}}
>
<i class="el-icon-copy-document" />
</span>,
<span
class="drawing-item-delete"
title="删除"
onClick={(event) => {
deleteItem(index, parent);
event.stopPropagation();
}}
>
<i class="el-icon-delete" />
</span>
];
}
};
const layouts = {
colFormItem(h, element, index, parent) {
const { activeItem } = this.$listeners;
let className =
this.activeId === element.formId
? 'drawing-item active-from-item'
: 'drawing-item';
if (this.formConf.unFocusedComponentBorder) { className += ' unfocus-bordered'; }
return (
<el-col
span={element.span}
class={className}
nativeOnClick={(event) => {
activeItem(element);
event.stopPropagation();
}}
>
<el-form-item
label-width={element.labelWidth ? `${element.labelWidth}px` : null}
label={element.label}
required={element.required}
>
<render
key={element.renderKey}
conf={element}
onInput={(event) => {
this.$set(element, 'defaultValue', event);
}}
/>
</el-form-item>
{components.itemBtns.apply(this, arguments)}
</el-col>
);
},
rowFormItem(h, element, index, parent) {
const { activeItem } = this.$listeners;
const className =
this.activeId === element.formId
? 'drawing-row-item active-from-item'
: 'drawing-row-item';
let child = renderChildren.apply(this, arguments);
if (element.type === 'flex') {
child = (
<el-row
type={element.type}
justify={element.justify}
align={element.align}
>
{child}
</el-row>
);
}
return (
<el-col span={element.span}>
<el-row
gutter={element.gutter}
class={className}
nativeOnClick={(event) => {
activeItem(element);
event.stopPropagation();
}}
>
<span class="component-name">{element.componentName}</span>
<draggable
list={element.children}
animation={340}
group="componentsGroup"
class="drag-wrapper"
>
{child}
</draggable>
{components.itemBtns.apply(this, arguments)}
</el-row>
</el-col>
);
}
};
function renderChildren(h, element, index, parent) {
if (!Array.isArray(element.children)) return null;
return element.children.map((el, i) => {
const layout = layouts[el.layout];
if (layout) {
return layout.call(this, h, el, i, element.children);
}
return layoutIsNotFound();
});
}
function layoutIsNotFound() {
throw new Error(`没有与${this.element.layout}匹配的layout`);
}
export default {
components: {
render,
draggable
},
props: ['element', 'index', 'drawingList', 'activeId', 'formConf'],
render(h) {
const layout = layouts[this.element.layout];
if (layout) {
return layout.call(this, h, this.element, this.index, this.drawingList);
}
return layoutIsNotFound();
}
};
</script>
<template>
<div class="icon-dialog">
<el-dialog
v-bind="$attrs"
width="980px"
:modal-append-to-body="false"
v-on="$listeners"
@open="onOpen"
@close="onClose"
>
<div slot="title">
选择图标
<el-input
v-model="key"
:style="{ width: '260px' }"
placeholder="请输入图标名称"
prefix-icon="el-icon-search"
clearable
/>
</div>
<ul class="icon-ul">
<li
v-for="icon in iconList"
:key="icon"
:class="active === icon ? 'active-item' : ''"
@click="onSelect(icon)"
>
<i :class="icon" />
<div>{{ icon }}</div>
</li>
</ul>
</el-dialog>
</div>
</template>
<script>
import iconList from '@/utils/generator/icon.json';
const originList = iconList.map((name) => `el-icon-${name}`);
export default {
inheritAttrs: false,
props: ['current'],
data() {
return {
iconList: originList,
active: null,
key: ''
};
},
watch: {
key(val) {
if (val) {
this.iconList = originList.filter((name) => name.indexOf(val) > -1);
} else {
this.iconList = originList;
}
}
},
methods: {
onOpen() {
this.active = this.current;
this.key = '';
},
onClose() {},
onSelect(icon) {
this.active = icon;
this.$emit('select', icon);
this.$emit('update:visible', false);
}
}
};
</script>
<style lang="scss" scoped>
.icon-ul {
margin: 0;
padding: 0;
font-size: 0;
li {
list-style-type: none;
text-align: center;
font-size: 14px;
display: inline-block;
width: 16.66%;
box-sizing: border-box;
height: 108px;
padding: 15px 6px 6px 6px;
cursor: pointer;
overflow: hidden;
&:hover {
background: #f2f2f2;
}
&.active-item {
background: #e1f3fb;
color: #7a6df0;
}
> i {
font-size: 30px;
line-height: 50px;
}
}
}
.icon-dialog {
::v-deep .el-dialog {
border-radius: 8px;
margin-bottom: 0;
margin-top: 4vh !important;
display: flex;
flex-direction: column;
max-height: 92vh;
overflow: hidden;
box-sizing: border-box;
.el-dialog__header {
padding-top: 14px;
}
.el-dialog__body {
margin: 0 20px 20px 20px;
padding: 0;
overflow: auto;
}
}
}
</style>
<template>
<div class="right-board">
<el-tabs v-model="currentTab" class="center-tabs">
<el-tab-pane label="组件属性" name="field" />
<el-tab-pane label="表单属性" name="form" />
</el-tabs>
<div class="field-box">
<a
class="document-link"
target="_blank"
:href="documentLink"
title="查看组件文档"
>
<i class="el-icon-link" />
</a>
<el-scrollbar class="right-scrollbar">
<!-- 组件属性 -->
<el-form
v-show="currentTab === 'field' && showField"
size="small"
label-width="90px"
>
<el-form-item v-if="activeData.changeTag" label="组件类型">
<el-select
v-model="activeData.tagIcon"
placeholder="请选择组件类型"
:style="{ width: '100%' }"
@change="tagChange"
>
<el-option-group
v-for="group in tagList"
:key="group.label"
:label="group.label"
>
<el-option
v-for="item in group.options"
:key="item.label"
:label="item.label"
:value="item.tagIcon"
>
<svg-icon class="node-icon" :icon-class="item.tagIcon" />
<span> {{ item.label }}</span>
</el-option>
</el-option-group>
</el-select>
</el-form-item>
<el-form-item v-if="activeData.vModel !== undefined" label="字段名">
<el-input
v-model="activeData.vModel"
placeholder="请输入字段名(v-model)"
/>
</el-form-item>
<el-form-item
v-if="activeData.componentName !== undefined"
label="组件名"
>
{{ activeData.componentName }}
</el-form-item>
<el-form-item v-if="activeData.label !== undefined" label="标题">
<el-input v-model="activeData.label" placeholder="请输入标题" />
</el-form-item>
<el-form-item
v-if="activeData.placeholder !== undefined"
label="占位提示"
>
<el-input
v-model="activeData.placeholder"
placeholder="请输入占位提示"
/>
</el-form-item>
<el-form-item
v-if="activeData['start-placeholder'] !== undefined"
label="开始占位"
>
<el-input
v-model="activeData['start-placeholder']"
placeholder="请输入占位提示"
/>
</el-form-item>
<el-form-item
v-if="activeData['end-placeholder'] !== undefined"
label="结束占位"
>
<el-input
v-model="activeData['end-placeholder']"
placeholder="请输入占位提示"
/>
</el-form-item>
<el-form-item v-if="activeData.span !== undefined" label="表单栅格">
<el-slider
v-model="activeData.span"
:max="24"
:min="1"
:marks="{ 12: '' }"
@change="spanChange"
/>
</el-form-item>
<el-form-item
v-if="activeData.layout === 'rowFormItem'"
label="栅格间隔"
>
<el-input-number
v-model="activeData.gutter"
:min="0"
placeholder="栅格间隔"
/>
</el-form-item>
<el-form-item
v-if="activeData.layout === 'rowFormItem'"
label="布局模式"
>
<el-radio-group v-model="activeData.type">
<el-radio-button label="default" />
<el-radio-button label="flex" />
</el-radio-group>
</el-form-item>
<el-form-item
v-if="
activeData.justify !== undefined && activeData.type === 'flex'
"
label="水平排列"
>
<el-select
v-model="activeData.justify"
placeholder="请选择水平排列"
:style="{ width: '100%' }"
>
<el-option
v-for="(item, index) in justifyOptions"
:key="index"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item
v-if="activeData.align !== undefined && activeData.type === 'flex'"
label="垂直排列"
>
<el-radio-group v-model="activeData.align">
<el-radio-button label="top" />
<el-radio-button label="middle" />
<el-radio-button label="bottom" />
</el-radio-group>
</el-form-item>
<el-form-item
v-if="activeData.labelWidth !== undefined"
label="标签宽度"
>
<el-input
v-model.number="activeData.labelWidth"
type="number"
placeholder="请输入标签宽度"
/>
</el-form-item>
<el-form-item
v-if="activeData.style && activeData.style.width !== undefined"
label="组件宽度"
>
<el-input
v-model="activeData.style.width"
placeholder="请输入组件宽度"
clearable
/>
</el-form-item>
<el-form-item v-if="activeData.vModel !== undefined" label="默认值">
<el-input
:value="setDefaultValue(activeData.defaultValue)"
placeholder="请输入默认值"
@input="onDefaultValueInput"
/>
</el-form-item>
<el-form-item
v-if="activeData.tag === 'el-checkbox-group'"
label="至少应选"
>
<el-input-number
:value="activeData.min"
:min="0"
placeholder="至少应选"
@input="$set(activeData, 'min', $event ? $event : undefined)"
/>
</el-form-item>
<el-form-item
v-if="activeData.tag === 'el-checkbox-group'"
label="最多可选"
>
<el-input-number
:value="activeData.max"
:min="0"
placeholder="最多可选"
@input="$set(activeData, 'max', $event ? $event : undefined)"
/>
</el-form-item>
<el-form-item v-if="activeData.prepend !== undefined" label="前缀">
<el-input v-model="activeData.prepend" placeholder="请输入前缀" />
</el-form-item>
<el-form-item v-if="activeData.append !== undefined" label="后缀">
<el-input v-model="activeData.append" placeholder="请输入后缀" />
</el-form-item>
<el-form-item
v-if="activeData['prefix-icon'] !== undefined"
label="前图标"
>
<el-input
v-model="activeData['prefix-icon']"
placeholder="请输入前图标名称"
>
<el-button
slot="append"
icon="el-icon-thumb"
@click="openIconsDialog('prefix-icon')"
>
选择
</el-button>
</el-input>
</el-form-item>
<el-form-item
v-if="activeData['suffix-icon'] !== undefined"
label="后图标"
>
<el-input
v-model="activeData['suffix-icon']"
placeholder="请输入后图标名称"
>
<el-button
slot="append"
icon="el-icon-thumb"
@click="openIconsDialog('suffix-icon')"
>
选择
</el-button>
</el-input>
</el-form-item>
<el-form-item
v-if="activeData.tag === 'el-cascader'"
label="选项分隔符"
>
<el-input
v-model="activeData.separator"
placeholder="请输入选项分隔符"
/>
</el-form-item>
<el-form-item
v-if="activeData.autosize !== undefined"
label="最小行数"
>
<el-input-number
v-model="activeData.autosize.minRows"
:min="1"
placeholder="最小行数"
/>
</el-form-item>
<el-form-item
v-if="activeData.autosize !== undefined"
label="最大行数"
>
<el-input-number
v-model="activeData.autosize.maxRows"
:min="1"
placeholder="最大行数"
/>
</el-form-item>
<el-form-item v-if="activeData.min !== undefined" label="最小值">
<el-input-number v-model="activeData.min" placeholder="最小值" />
</el-form-item>
<el-form-item v-if="activeData.max !== undefined" label="最大值">
<el-input-number v-model="activeData.max" placeholder="最大值" />
</el-form-item>
<el-form-item v-if="activeData.step !== undefined" label="步长">
<el-input-number v-model="activeData.step" placeholder="步数" />
</el-form-item>
<el-form-item
v-if="activeData.tag === 'el-input-number'"
label="精度"
>
<el-input-number
v-model="activeData.precision"
:min="0"
placeholder="精度"
/>
</el-form-item>
<el-form-item
v-if="activeData.tag === 'el-input-number'"
label="按钮位置"
>
<el-radio-group v-model="activeData['controls-position']">
<el-radio-button label=""> 默认 </el-radio-button>
<el-radio-button label="right"> 右侧 </el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item
v-if="activeData.maxlength !== undefined"
label="最多输入"
>
<el-input
v-model="activeData.maxlength"
placeholder="请输入字符长度"
>
<template slot="append"> 个字符 </template>
</el-input>
</el-form-item>
<el-form-item
v-if="activeData['active-text'] !== undefined"
label="开启提示"
>
<el-input
v-model="activeData['active-text']"
placeholder="请输入开启提示"
/>
</el-form-item>
<el-form-item
v-if="activeData['inactive-text'] !== undefined"
label="关闭提示"
>
<el-input
v-model="activeData['inactive-text']"
placeholder="请输入关闭提示"
/>
</el-form-item>
<el-form-item
v-if="activeData['active-value'] !== undefined"
label="开启值"
>
<el-input
:value="setDefaultValue(activeData['active-value'])"
placeholder="请输入开启值"
@input="onSwitchValueInput($event, 'active-value')"
/>
</el-form-item>
<el-form-item
v-if="activeData['inactive-value'] !== undefined"
label="关闭值"
>
<el-input
:value="setDefaultValue(activeData['inactive-value'])"
placeholder="请输入关闭值"
@input="onSwitchValueInput($event, 'inactive-value')"
/>
</el-form-item>
<el-form-item
v-if="
activeData.type !== undefined &&
'el-date-picker' === activeData.tag
"
label="时间类型"
>
<el-select
v-model="activeData.type"
placeholder="请选择时间类型"
:style="{ width: '100%' }"
@change="dateTypeChange"
>
<el-option
v-for="(item, index) in dateOptions"
:key="index"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item v-if="activeData.name !== undefined" label="文件字段名">
<el-input
v-model="activeData.name"
placeholder="请输入上传文件字段名"
/>
</el-form-item>
<el-form-item v-if="activeData.accept !== undefined" label="文件类型">
<el-select
v-model="activeData.accept"
placeholder="请选择文件类型"
:style="{ width: '100%' }"
clearable
>
<el-option label="图片" value="image/*" />
<el-option label="视频" value="video/*" />
<el-option label="音频" value="audio/*" />
<el-option label="excel" value=".xls,.xlsx" />
<el-option label="word" value=".doc,.docx" />
<el-option label="pdf" value=".pdf" />
<el-option label="txt" value=".txt" />
</el-select>
</el-form-item>
<el-form-item
v-if="activeData.fileSize !== undefined"
label="文件大小"
>
<el-input
v-model.number="activeData.fileSize"
placeholder="请输入文件大小"
>
<el-select
slot="append"
v-model="activeData.sizeUnit"
:style="{ width: '66px' }"
>
<el-option label="KB" value="KB" />
<el-option label="MB" value="MB" />
<el-option label="GB" value="GB" />
</el-select>
</el-input>
</el-form-item>
<el-form-item v-if="activeData.action !== undefined" label="上传地址">
<el-input
v-model="activeData.action"
placeholder="请输入上传地址"
clearable
/>
</el-form-item>
<el-form-item
v-if="activeData['list-type'] !== undefined"
label="列表类型"
>
<el-radio-group v-model="activeData['list-type']" size="small">
<el-radio-button label="text"> text </el-radio-button>
<el-radio-button label="picture"> picture </el-radio-button>
<el-radio-button label="picture-card">
picture-card
</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item
v-if="activeData.buttonText !== undefined"
v-show="'picture-card' !== activeData['list-type']"
label="按钮文字"
>
<el-input
v-model="activeData.buttonText"
placeholder="请输入按钮文字"
/>
</el-form-item>
<el-form-item
v-if="activeData['range-separator'] !== undefined"
label="分隔符"
>
<el-input
v-model="activeData['range-separator']"
placeholder="请输入分隔符"
/>
</el-form-item>
<el-form-item
v-if="activeData['picker-options'] !== undefined"
label="时间段"
>
<el-input
v-model="activeData['picker-options'].selectableRange"
placeholder="请输入时间段"
/>
</el-form-item>
<el-form-item v-if="activeData.format !== undefined" label="时间格式">
<el-input
:value="activeData.format"
placeholder="请输入时间格式"
@input="setTimeValue($event)"
/>
</el-form-item>
<template
v-if="
['el-checkbox-group', 'el-radio-group', 'el-select'].indexOf(
activeData.tag
) > -1
"
>
<el-divider>选项</el-divider>
<draggable
:list="activeData.options"
:animation="340"
group="selectItem"
handle=".option-drag"
>
<div
v-for="(item, index) in activeData.options"
:key="index"
class="select-item"
>
<div class="select-line-icon option-drag">
<i class="el-icon-s-operation" />
</div>
<el-input
v-model="item.label"
placeholder="选项名"
size="small"
/>
<el-input
placeholder="选项值"
size="small"
:value="item.value"
@input="setOptionValue(item, $event)"
/>
<div
class="close-btn select-line-icon"
@click="activeData.options.splice(index, 1)"
>
<i class="el-icon-remove-outline" />
</div>
</div>
</draggable>
<div style="margin-left: 20px">
<el-button
style="padding-bottom: 0"
icon="el-icon-circle-plus-outline"
type="text"
@click="addSelectItem"
>
添加选项
</el-button>
</div>
<el-divider />
</template>
<template v-if="['el-cascader'].indexOf(activeData.tag) > -1">
<el-divider>选项</el-divider>
<el-form-item label="数据类型">
<el-radio-group v-model="activeData.dataType" size="small">
<el-radio-button label="dynamic"> 动态数据 </el-radio-button>
<el-radio-button label="static"> 静态数据 </el-radio-button>
</el-radio-group>
</el-form-item>
<template v-if="activeData.dataType === 'dynamic'">
<el-form-item label="标签键名">
<el-input
v-model="activeData.labelKey"
placeholder="请输入标签键名"
/>
</el-form-item>
<el-form-item label="值键名">
<el-input
v-model="activeData.valueKey"
placeholder="请输入值键名"
/>
</el-form-item>
<el-form-item label="子级键名">
<el-input
v-model="activeData.childrenKey"
placeholder="请输入子级键名"
/>
</el-form-item>
</template>
<el-tree
v-if="activeData.dataType === 'static'"
draggable
:data="activeData.options"
node-key="id"
:expand-on-click-node="false"
:render-content="renderContent"
/>
<div
v-if="activeData.dataType === 'static'"
style="margin-left: 20px"
>
<el-button
style="padding-bottom: 0"
icon="el-icon-circle-plus-outline"
type="text"
@click="addTreeItem"
>
添加父级
</el-button>
</div>
<el-divider />
</template>
<el-form-item
v-if="activeData.optionType !== undefined"
label="选项样式"
>
<el-radio-group v-model="activeData.optionType">
<el-radio-button label="default"> 默认 </el-radio-button>
<el-radio-button label="button"> 按钮 </el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item
v-if="activeData['active-color'] !== undefined"
label="开启颜色"
>
<el-color-picker v-model="activeData['active-color']" />
</el-form-item>
<el-form-item
v-if="activeData['inactive-color'] !== undefined"
label="关闭颜色"
>
<el-color-picker v-model="activeData['inactive-color']" />
</el-form-item>
<el-form-item
v-if="activeData['allow-half'] !== undefined"
label="允许半选"
>
<el-switch v-model="activeData['allow-half']" />
</el-form-item>
<el-form-item
v-if="activeData['show-text'] !== undefined"
label="辅助文字"
>
<el-switch
v-model="activeData['show-text']"
@change="rateTextChange"
/>
</el-form-item>
<el-form-item
v-if="activeData['show-score'] !== undefined"
label="显示分数"
>
<el-switch
v-model="activeData['show-score']"
@change="rateScoreChange"
/>
</el-form-item>
<el-form-item
v-if="activeData['show-stops'] !== undefined"
label="显示间断点"
>
<el-switch v-model="activeData['show-stops']" />
</el-form-item>
<el-form-item v-if="activeData.range !== undefined" label="范围选择">
<el-switch v-model="activeData.range" @change="rangeChange" />
</el-form-item>
<el-form-item
v-if="
activeData.border !== undefined &&
activeData.optionType === 'default'
"
label="是否带边框"
>
<el-switch v-model="activeData.border" />
</el-form-item>
<el-form-item
v-if="activeData.tag === 'el-color-picker'"
label="颜色格式"
>
<el-select
v-model="activeData['color-format']"
placeholder="请选择颜色格式"
:style="{ width: '100%' }"
@change="colorFormatChange"
>
<el-option
v-for="(item, index) in colorFormatOptions"
:key="index"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item
v-if="
activeData.size !== undefined &&
(activeData.optionType === 'button' ||
activeData.border ||
activeData.tag === 'el-color-picker')
"
label="选项尺寸"
>
<el-radio-group v-model="activeData.size">
<el-radio-button label="medium"> 中等 </el-radio-button>
<el-radio-button label="small"> 较小 </el-radio-button>
<el-radio-button label="mini"> 迷你 </el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item
v-if="activeData['show-word-limit'] !== undefined"
label="输入统计"
>
<el-switch v-model="activeData['show-word-limit']" />
</el-form-item>
<el-form-item
v-if="activeData.tag === 'el-input-number'"
label="严格步数"
>
<el-switch v-model="activeData['step-strictly']" />
</el-form-item>
<el-form-item
v-if="activeData.tag === 'el-cascader'"
label="是否多选"
>
<el-switch v-model="activeData.props.props.multiple" />
</el-form-item>
<el-form-item
v-if="activeData.tag === 'el-cascader'"
label="展示全路径"
>
<el-switch v-model="activeData['show-all-levels']" />
</el-form-item>
<el-form-item
v-if="activeData.tag === 'el-cascader'"
label="可否筛选"
>
<el-switch v-model="activeData.filterable" />
</el-form-item>
<el-form-item
v-if="activeData.clearable !== undefined"
label="能否清空"
>
<el-switch v-model="activeData.clearable" />
</el-form-item>
<el-form-item
v-if="activeData.showTip !== undefined"
label="显示提示"
>
<el-switch v-model="activeData.showTip" />
</el-form-item>
<el-form-item
v-if="activeData.multiple !== undefined"
label="多选文件"
>
<el-switch v-model="activeData.multiple" />
</el-form-item>
<el-form-item
v-if="activeData['auto-upload'] !== undefined"
label="自动上传"
>
<el-switch v-model="activeData['auto-upload']" />
</el-form-item>
<el-form-item
v-if="activeData.readonly !== undefined"
label="是否只读"
>
<el-switch v-model="activeData.readonly" />
</el-form-item>
<el-form-item
v-if="activeData.disabled !== undefined"
label="是否禁用"
>
<el-switch v-model="activeData.disabled" />
</el-form-item>
<el-form-item
v-if="activeData.tag === 'el-select'"
label="是否可搜索"
>
<el-switch v-model="activeData.filterable" />
</el-form-item>
<el-form-item v-if="activeData.tag === 'el-select'" label="是否多选">
<el-switch v-model="activeData.multiple" @change="multipleChange" />
</el-form-item>
<el-form-item
v-if="activeData.required !== undefined"
label="是否必填"
>
<el-switch v-model="activeData.required" />
</el-form-item>
<template v-if="activeData.layoutTree">
<el-divider>布局结构树</el-divider>
<el-tree
:data="[activeData]"
:props="layoutTreeProps"
node-key="renderKey"
default-expand-all
draggable
>
<span slot-scope="{ node, data }">
<span class="node-label">
<svg-icon class="node-icon" :icon-class="data.tagIcon" />
{{ node.label }}
</span>
</span>
</el-tree>
</template>
<template
v-if="
activeData.layout === 'colFormItem' &&
activeData.tag !== 'el-button'
"
>
<el-divider>正则校验</el-divider>
<div
v-for="(item, index) in activeData.regList"
:key="index"
class="reg-item"
>
<span
class="close-btn"
@click="activeData.regList.splice(index, 1)"
>
<i class="el-icon-close" />
</span>
<el-form-item label="表达式">
<el-input v-model="item.pattern" placeholder="请输入正则" />
</el-form-item>
<el-form-item label="错误提示" style="margin-bottom: 0">
<el-input v-model="item.message" placeholder="请输入错误提示" />
</el-form-item>
</div>
<div style="margin-left: 20px">
<el-button
icon="el-icon-circle-plus-outline"
type="text"
@click="addReg"
>
添加规则
</el-button>
</div>
</template>
</el-form>
<!-- 表单属性 -->
<el-form v-show="currentTab === 'form'" size="small" label-width="90px">
<el-form-item label="表单名">
<el-input
v-model="formConf.formRef"
placeholder="请输入表单名(ref)"
/>
</el-form-item>
<el-form-item label="表单模型">
<el-input
v-model="formConf.formModel"
placeholder="请输入数据模型"
/>
</el-form-item>
<el-form-item label="校验模型">
<el-input
v-model="formConf.formRules"
placeholder="请输入校验模型"
/>
</el-form-item>
<el-form-item label="表单尺寸">
<el-radio-group v-model="formConf.size">
<el-radio-button label="medium"> 中等 </el-radio-button>
<el-radio-button label="small"> 较小 </el-radio-button>
<el-radio-button label="mini"> 迷你 </el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="标签对齐">
<el-radio-group v-model="formConf.labelPosition">
<el-radio-button label="left"> 左对齐 </el-radio-button>
<el-radio-button label="right"> 右对齐 </el-radio-button>
<el-radio-button label="top"> 顶部对齐 </el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="标签宽度">
<el-input-number
v-model="formConf.labelWidth"
placeholder="标签宽度"
/>
</el-form-item>
<el-form-item label="栅格间隔">
<el-input-number
v-model="formConf.gutter"
:min="0"
placeholder="栅格间隔"
/>
</el-form-item>
<el-form-item label="禁用表单">
<el-switch v-model="formConf.disabled" />
</el-form-item>
<el-form-item label="表单按钮">
<el-switch v-model="formConf.formBtns" />
</el-form-item>
<el-form-item label="显示未选中组件边框">
<el-switch v-model="formConf.unFocusedComponentBorder" />
</el-form-item>
</el-form>
</el-scrollbar>
</div>
<treeNode-dialog
:visible.sync="dialogVisible"
title="添加选项"
@commit="addNode"
/>
<icons-dialog
:visible.sync="iconsVisible"
:current="activeData[currentIconModel]"
@select="setIcon"
/>
</div>
</template>
<script>
import draggable from 'vuedraggable';
import TreeNodeDialog from './TreeNodeDialog';
import { isNumberStr } from '@/utils/index';
import IconsDialog from './IconsDialog';
import { inputComponents, selectComponents } from '@/utils/generator/config';
const dateTimeFormat = {
date: 'yyyy-MM-dd',
week: 'yyyy 第 WW 周',
month: 'yyyy-MM',
year: 'yyyy',
datetime: 'yyyy-MM-dd HH:mm:ss',
daterange: 'yyyy-MM-dd',
monthrange: 'yyyy-MM',
datetimerange: 'yyyy-MM-dd HH:mm:ss'
};
export default {
components: {
draggable,
TreeNodeDialog,
IconsDialog
},
props: ['showField', 'activeData', 'formConf'],
data() {
return {
currentTab: 'field',
currentNode: null,
dialogVisible: false,
iconsVisible: false,
currentIconModel: null,
dateTypeOptions: [
{
label: '日(date)',
value: 'date'
},
{
label: '周(week)',
value: 'week'
},
{
label: '月(month)',
value: 'month'
},
{
label: '年(year)',
value: 'year'
},
{
label: '日期时间(datetime)',
value: 'datetime'
}
],
dateRangeTypeOptions: [
{
label: '日期范围(daterange)',
value: 'daterange'
},
{
label: '月范围(monthrange)',
value: 'monthrange'
},
{
label: '日期时间范围(datetimerange)',
value: 'datetimerange'
}
],
colorFormatOptions: [
{
label: 'hex',
value: 'hex'
},
{
label: 'rgb',
value: 'rgb'
},
{
label: 'rgba',
value: 'rgba'
},
{
label: 'hsv',
value: 'hsv'
},
{
label: 'hsl',
value: 'hsl'
}
],
justifyOptions: [
{
label: 'start',
value: 'start'
},
{
label: 'end',
value: 'end'
},
{
label: 'center',
value: 'center'
},
{
label: 'space-around',
value: 'space-around'
},
{
label: 'space-between',
value: 'space-between'
}
],
layoutTreeProps: {
label(data, node) {
return data.componentName || `${data.label}: ${data.vModel}`;
}
}
};
},
computed: {
documentLink() {
return (
this.activeData.document ||
'https://element.eleme.cn/#/zh-CN/component/installation'
);
},
dateOptions() {
if (
this.activeData.type !== undefined &&
this.activeData.tag === 'el-date-picker'
) {
if (this.activeData['start-placeholder'] === undefined) {
return this.dateTypeOptions;
}
return this.dateRangeTypeOptions;
}
return [];
},
tagList() {
return [
{
label: '输入型组件',
options: inputComponents
},
{
label: '选择型组件',
options: selectComponents
}
];
}
},
methods: {
addReg() {
this.activeData.regList.push({
pattern: '',
message: ''
});
},
addSelectItem() {
this.activeData.options.push({
label: '',
value: ''
});
},
addTreeItem() {
++this.idGlobal;
this.dialogVisible = true;
this.currentNode = this.activeData.options;
},
renderContent(h, { node, data, store }) {
return (
<div class="custom-tree-node">
<span>{node.label}</span>
<span class="node-operation">
<i
on-click={() => this.append(data)}
class="el-icon-plus"
title="添加"
></i>
<i
on-click={() => this.remove(node, data)}
class="el-icon-delete"
title="删除"
></i>
</span>
</div>
);
},
append(data) {
if (!data.children) {
this.$set(data, 'children', []);
}
this.dialogVisible = true;
this.currentNode = data.children;
},
remove(node, data) {
const { parent } = node;
const children = parent.data.children || parent.data;
const index = children.findIndex((d) => d.id === data.id);
children.splice(index, 1);
},
addNode(data) {
this.currentNode.push(data);
},
setOptionValue(item, val) {
item.value = isNumberStr(val) ? +val : val;
},
setDefaultValue(val) {
if (Array.isArray(val)) {
return val.join(',');
}
if (['string', 'number'].indexOf(val) > -1) {
return val;
}
if (typeof val === 'boolean') {
return `${val}`;
}
return val;
},
onDefaultValueInput(str) {
if (Array.isArray(this.activeData.defaultValue)) {
// 数组
this.$set(
this.activeData,
'defaultValue',
str.split(',').map((val) => (isNumberStr(val) ? +val : val))
);
} else if (['true', 'false'].indexOf(str) > -1) {
// 布尔
this.$set(this.activeData, 'defaultValue', JSON.parse(str));
} else {
// 字符串和数字
this.$set(
this.activeData,
'defaultValue',
isNumberStr(str) ? +str : str
);
}
},
onSwitchValueInput(val, name) {
if (['true', 'false'].indexOf(val) > -1) {
this.$set(this.activeData, name, JSON.parse(val));
} else {
this.$set(this.activeData, name, isNumberStr(val) ? +val : val);
}
},
setTimeValue(val, type) {
const valueFormat = type === 'week' ? dateTimeFormat.date : val;
this.$set(this.activeData, 'defaultValue', null);
this.$set(this.activeData, 'value-format', valueFormat);
this.$set(this.activeData, 'format', val);
},
spanChange(val) {
this.formConf.span = val;
},
multipleChange(val) {
this.$set(this.activeData, 'defaultValue', val ? [] : '');
},
dateTypeChange(val) {
this.setTimeValue(dateTimeFormat[val], val);
},
rangeChange(val) {
this.$set(
this.activeData,
'defaultValue',
val ? [this.activeData.min, this.activeData.max] : this.activeData.min
);
},
rateTextChange(val) {
if (val) this.activeData['show-score'] = false;
},
rateScoreChange(val) {
if (val) this.activeData['show-text'] = false;
},
colorFormatChange(val) {
this.activeData.defaultValue = null;
this.activeData['show-alpha'] = val.indexOf('a') > -1;
this.activeData.renderKey = +new Date(); // 更新renderKey,重新渲染该组件
},
openIconsDialog(model) {
this.iconsVisible = true;
this.currentIconModel = model;
},
setIcon(val) {
this.activeData[this.currentIconModel] = val;
},
tagChange(tagIcon) {
let target = inputComponents.find((item) => item.tagIcon === tagIcon);
if (!target) {
target = selectComponents.find((item) => item.tagIcon === tagIcon);
}
this.$emit('tag-change', target);
}
}
};
</script>
<style lang="scss" scoped>
.right-board {
width: 350px;
position: absolute;
right: 0;
top: 0;
padding-top: 3px;
.field-box {
position: relative;
height: calc(100vh - 42px);
box-sizing: border-box;
overflow: hidden;
}
.el-scrollbar {
height: 100%;
}
}
.select-item {
display: flex;
border: 1px dashed #fff;
box-sizing: border-box;
& .close-btn {
cursor: pointer;
color: #f56c6c;
}
& .el-input + .el-input {
margin-left: 4px;
}
}
.select-item + .select-item {
margin-top: 4px;
}
.select-item.sortable-chosen {
border: 1px dashed #409eff;
}
.select-line-icon {
line-height: 32px;
font-size: 22px;
padding: 0 4px;
color: #777;
}
.option-drag {
cursor: move;
}
.time-range {
.el-date-editor {
width: 227px;
}
::v-deep .el-icon-time {
display: none;
}
}
.document-link {
position: absolute;
display: block;
width: 26px;
height: 26px;
top: 0;
left: 0;
cursor: pointer;
background: #409eff;
z-index: 1;
border-radius: 0 0 6px 0;
text-align: center;
line-height: 26px;
color: #fff;
font-size: 18px;
}
.node-label {
font-size: 14px;
}
.node-icon {
color: #bebfc3;
}
</style>
<template>
<div>
<el-dialog
v-bind="$attrs"
:close-on-click-modal="false"
:modal-append-to-body="false"
v-on="$listeners"
@open="onOpen"
@close="onClose"
>
<el-row :gutter="0">
<el-form
ref="elForm"
:model="formData"
:rules="rules"
size="small"
label-width="100px"
>
<el-col :span="24">
<el-form-item label="选项名" prop="label">
<el-input
v-model="formData.label"
placeholder="请输入选项名"
clearable
/>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="选项值" prop="value">
<el-input
v-model="formData.value"
placeholder="请输入选项值"
clearable
>
<el-select
slot="append"
v-model="dataType"
:style="{ width: '100px' }"
>
<el-option
v-for="(item, index) in dataTypeOptions"
:key="index"
:label="item.label"
:value="item.value"
:disabled="item.disabled"
/>
</el-select>
</el-input>
</el-form-item>
</el-col>
</el-form>
</el-row>
<div slot="footer">
<el-button type="primary" @click="handleConfirm"> 确定 </el-button>
<el-button @click="close"> 取消 </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { isNumberStr } from '@/utils/index';
export default {
components: {},
inheritAttrs: false,
props: [],
data() {
return {
id: 100,
formData: {
label: undefined,
value: undefined
},
rules: {
label: [
{
required: true,
message: '请输入选项名',
trigger: 'blur'
}
],
value: [
{
required: true,
message: '请输入选项值',
trigger: 'blur'
}
]
},
dataType: 'string',
dataTypeOptions: [
{
label: '字符串',
value: 'string'
},
{
label: '数字',
value: 'number'
}
]
};
},
computed: {},
watch: {
// eslint-disable-next-line func-names
'formData.value': function (val) {
this.dataType = isNumberStr(val) ? 'number' : 'string';
}
},
created() {},
mounted() {},
methods: {
onOpen() {
this.formData = {
label: undefined,
value: undefined
};
},
onClose() {},
close() {
this.$emit('update:visible', false);
},
handleConfirm() {
this.$refs.elForm.validate((valid) => {
if (!valid) return;
if (this.dataType === 'number') {
this.formData.value = parseFloat(this.formData.value);
}
this.formData.id = this.id++;
this.$emit('commit', this.formData);
this.close();
});
}
}
};
</script>
<template>
<div class="container">
<div class="left-board">
<div class="logo-wrapper">
<div class="logo"><img :src="logo" alt="logo" /> Form Generator</div>
</div>
<el-scrollbar class="left-scrollbar">
<div class="components-list">
<div class="components-title">
<svg-icon icon-class="component" />输入型组件
</div>
<draggable
class="components-draggable"
:list="inputComponents"
:group="{ name: 'componentsGroup', pull: 'clone', put: false }"
:clone="cloneComponent"
draggable=".components-item"
:sort="false"
@end="onEnd"
>
<div
v-for="(element, index) in inputComponents"
:key="index"
class="components-item"
@click="addComponent(element)"
>
<div class="components-body">
<svg-icon :icon-class="element.tagIcon" />
{{ element.label }}
</div>
</div>
</draggable>
<div class="components-title">
<svg-icon icon-class="component" />选择型组件
</div>
<draggable
class="components-draggable"
:list="selectComponents"
:group="{ name: 'componentsGroup', pull: 'clone', put: false }"
:clone="cloneComponent"
draggable=".components-item"
:sort="false"
@end="onEnd"
>
<div
v-for="(element, index) in selectComponents"
:key="index"
class="components-item"
@click="addComponent(element)"
>
<div class="components-body">
<svg-icon :icon-class="element.tagIcon" />
{{ element.label }}
</div>
</div>
</draggable>
<div class="components-title">
<svg-icon icon-class="component" /> 布局型组件
</div>
<draggable
class="components-draggable"
:list="layoutComponents"
:group="{ name: 'componentsGroup', pull: 'clone', put: false }"
:clone="cloneComponent"
draggable=".components-item"
:sort="false"
@end="onEnd"
>
<div
v-for="(element, index) in layoutComponents"
:key="index"
class="components-item"
@click="addComponent(element)"
>
<div class="components-body">
<svg-icon :icon-class="element.tagIcon" />
{{ element.label }}
</div>
</div>
</draggable>
</div>
</el-scrollbar>
</div>
<div class="center-board">
<div class="action-bar">
<el-button icon="el-icon-download" type="text" @click="download">
导出vue文件
</el-button>
<el-button
class="copy-btn-main"
icon="el-icon-document-copy"
type="text"
@click="copy"
>
复制代码
</el-button>
<el-button
class="delete-btn"
icon="el-icon-delete"
type="text"
@click="empty"
>
清空
</el-button>
</div>
<el-scrollbar class="center-scrollbar">
<el-row class="center-board-row" :gutter="formConf.gutter">
<el-form
:size="formConf.size"
:label-position="formConf.labelPosition"
:disabled="formConf.disabled"
:label-width="formConf.labelWidth + 'px'"
>
<draggable
class="drawing-board"
:list="drawingList"
:animation="340"
group="componentsGroup"
>
<draggable-item
v-for="(element, index) in drawingList"
:key="element.renderKey"
:drawing-list="drawingList"
:element="element"
:index="index"
:active-id="activeId"
:form-conf="formConf"
@activeItem="activeFormItem"
@copyItem="drawingItemCopy"
@deleteItem="drawingItemDelete"
/>
</draggable>
<div v-show="!drawingList.length" class="empty-info">
从左侧拖入或点选组件进行表单设计
</div>
</el-form>
</el-row>
</el-scrollbar>
</div>
<right-panel
:active-data="activeData"
:form-conf="formConf"
:show-field="!!drawingList.length"
@tag-change="tagChange"
/>
<code-type-dialog
:visible.sync="dialogVisible"
title="选择生成类型"
:show-file-name="showFileName"
@confirm="generate"
/>
<input id="copyNode" type="hidden" />
</div>
</template>
<script>
import draggable from 'vuedraggable';
import beautifier from 'js-beautify';
import ClipboardJS from 'clipboard';
import render from '@/utils/generator/render';
import RightPanel from './RightPanel';
import {
inputComponents,
selectComponents,
layoutComponents,
formConf
} from '@/utils/generator/config';
import { beautifierConf, titleCase } from '@/utils/index';
import {
makeUpHtml,
vueTemplate,
vueScript,
cssStyle
} from '@/utils/generator/html';
import { makeUpJs } from '@/utils/generator/js';
import { makeUpCss } from '@/utils/generator/css';
import drawingDefault from '@/utils/generator/drawingDefault';
import logo from '@/assets/logo/logo.png';
import CodeTypeDialog from './CodeTypeDialog';
import DraggableItem from './DraggableItem';
let oldActiveId;
let tempActiveData;
export default {
components: {
draggable,
// eslint-disable-next-line
render,
RightPanel,
CodeTypeDialog,
DraggableItem
},
data() {
return {
logo,
idGlobal: 100,
formConf,
inputComponents,
selectComponents,
layoutComponents,
labelWidth: 100,
drawingList: drawingDefault,
drawingData: {},
activeId: drawingDefault[0].formId,
drawerVisible: false,
formData: {},
dialogVisible: false,
generateConf: null,
showFileName: false,
activeData: drawingDefault[0]
};
},
watch: {
// eslint-disable-next-line func-names
'activeData.label': function (val, oldVal) {
if (
this.activeData.placeholder === undefined ||
!this.activeData.tag ||
oldActiveId !== this.activeId
) {
return;
}
this.activeData.placeholder =
this.activeData.placeholder.replace(oldVal, '') + val;
},
activeId: {
handler(val) {
oldActiveId = val;
},
immediate: true
}
},
created() {
// 防止 firefox 下 拖拽 会新打卡一个选项卡
document.body.ondrop = (event) => {
event.preventDefault();
event.stopPropagation();
};
},
mounted() {
const clipboard = new ClipboardJS('#copyNode', {
text: (trigger) => {
const codeStr = this.generateCode();
this.$notify({
title: '成功',
message: '代码已复制到剪切板,可粘贴。',
type: 'success'
});
return codeStr;
}
});
clipboard.on('error', (e) => {
this.$message.error('代码复制失败');
});
},
methods: {
activeFormItem(element) {
this.activeData = element;
this.activeId = element.formId;
},
onEnd(obj, a) {
if (obj.from !== obj.to) {
this.activeData = tempActiveData;
this.activeId = this.idGlobal;
}
},
addComponent(item) {
const clone = this.cloneComponent(item);
this.drawingList.push(clone);
this.activeFormItem(clone);
},
cloneComponent(origin) {
const clone = JSON.parse(JSON.stringify(origin));
clone.formId = ++this.idGlobal;
clone.span = formConf.span;
clone.renderKey = +new Date(); // 改变renderKey后可以实现强制更新组件
if (!clone.layout) clone.layout = 'colFormItem';
if (clone.layout === 'colFormItem') {
clone.vModel = `field${this.idGlobal}`;
clone.placeholder !== undefined && (clone.placeholder += clone.label);
tempActiveData = clone;
} else if (clone.layout === 'rowFormItem') {
delete clone.label;
clone.componentName = `row${this.idGlobal}`;
clone.gutter = this.formConf.gutter;
tempActiveData = clone;
}
return tempActiveData;
},
AssembleFormData() {
this.formData = {
fields: JSON.parse(JSON.stringify(this.drawingList)),
...this.formConf
};
},
generate(data) {
const func = this[`exec${titleCase(this.operationType)}`];
this.generateConf = data;
func && func(data);
},
execRun(data) {
this.AssembleFormData();
this.drawerVisible = true;
},
execDownload(data) {
const codeStr = this.generateCode();
const blob = new Blob([codeStr], { type: 'text/plain;charset=utf-8' });
this.$download.saveAs(blob, data.fileName);
},
execCopy(data) {
document.getElementById('copyNode').click();
},
empty() {
this.$confirm('确定要清空所有组件吗?', '提示', { type: 'warning' }).then(
() => {
this.drawingList = [];
}
);
},
drawingItemCopy(item, parent) {
let clone = JSON.parse(JSON.stringify(item));
clone = this.createIdAndKey(clone);
parent.push(clone);
this.activeFormItem(clone);
},
createIdAndKey(item) {
item.formId = ++this.idGlobal;
item.renderKey = +new Date();
if (item.layout === 'colFormItem') {
item.vModel = `field${this.idGlobal}`;
} else if (item.layout === 'rowFormItem') {
item.componentName = `row${this.idGlobal}`;
}
if (Array.isArray(item.children)) {
item.children = item.children.map((childItem) =>
this.createIdAndKey(childItem)
);
}
return item;
},
drawingItemDelete(index, parent) {
parent.splice(index, 1);
this.$nextTick(() => {
const len = this.drawingList.length;
if (len) {
this.activeFormItem(this.drawingList[len - 1]);
}
});
},
generateCode() {
const { type } = this.generateConf;
this.AssembleFormData();
const script = vueScript(makeUpJs(this.formData, type));
const html = vueTemplate(makeUpHtml(this.formData, type));
const css = cssStyle(makeUpCss(this.formData));
return beautifier.html(html + script + css, beautifierConf.html);
},
download() {
this.dialogVisible = true;
this.showFileName = true;
this.operationType = 'download';
},
run() {
this.dialogVisible = true;
this.showFileName = false;
this.operationType = 'run';
},
copy() {
this.dialogVisible = true;
this.showFileName = false;
this.operationType = 'copy';
},
tagChange(newTag) {
newTag = this.cloneComponent(newTag);
newTag.vModel = this.activeData.vModel;
newTag.formId = this.activeId;
newTag.span = this.activeData.span;
delete this.activeData.tag;
delete this.activeData.tagIcon;
delete this.activeData.document;
Object.keys(newTag).forEach((key) => {
if (
this.activeData[key] !== undefined &&
typeof this.activeData[key] === typeof newTag[key]
) {
newTag[key] = this.activeData[key];
}
});
this.activeData = newTag;
this.updateDrawingList(newTag, this.drawingList);
},
updateDrawingList(newTag, list) {
const index = list.findIndex((item) => item.formId === this.activeId);
if (index > -1) {
list.splice(index, 1, newTag);
} else {
list.forEach((item) => {
if (Array.isArray(item.children)) {
this.updateDrawingList(newTag, item.children);
}
});
}
}
}
};
</script>
<style lang="scss">
.editor-tabs {
background: #121315;
.el-tabs__header {
margin: 0;
border-bottom-color: #121315;
.el-tabs__nav {
border-color: #121315;
}
}
.el-tabs__item {
height: 32px;
line-height: 32px;
color: #888a8e;
border-left: 1px solid #121315 !important;
background: #363636;
margin-right: 5px;
user-select: none;
}
.el-tabs__item.is-active {
background: #1e1e1e;
border-bottom-color: #1e1e1e !important;
color: #fff;
}
.el-icon-edit {
color: #f1fa8c;
}
.el-icon-document {
color: #a95812;
}
}
// home
.right-scrollbar {
.el-scrollbar__view {
padding: 12px 18px 15px 15px;
}
}
.left-scrollbar .el-scrollbar__wrap {
box-sizing: border-box;
overflow-x: hidden !important;
margin-bottom: 0 !important;
}
.center-tabs {
.el-tabs__header {
margin-bottom: 0 !important;
}
.el-tabs__item {
width: 50%;
text-align: center;
}
.el-tabs__nav {
width: 100%;
}
}
.reg-item {
padding: 12px 6px;
background: #f8f8f8;
position: relative;
border-radius: 4px;
.close-btn {
position: absolute;
right: -6px;
top: -6px;
display: block;
width: 16px;
height: 16px;
line-height: 16px;
background: rgba(0, 0, 0, 0.2);
border-radius: 50%;
color: #fff;
text-align: center;
z-index: 1;
cursor: pointer;
font-size: 12px;
&:hover {
background: rgba(210, 23, 23, 0.5);
}
}
& + .reg-item {
margin-top: 18px;
}
}
.action-bar {
& .el-button + .el-button {
margin-left: 15px;
}
& i {
font-size: 20px;
vertical-align: middle;
position: relative;
top: -1px;
}
}
.custom-tree-node {
width: 100%;
font-size: 14px;
.node-operation {
float: right;
}
i[class*='el-icon'] + i[class*='el-icon'] {
margin-left: 6px;
}
.el-icon-plus {
color: #409eff;
}
.el-icon-delete {
color: #157a0c;
}
}
.left-scrollbar .el-scrollbar__view {
overflow-x: hidden;
}
.el-rate {
display: inline-block;
vertical-align: text-top;
}
.el-upload__tip {
line-height: 1.2;
}
$selectedColor: #f6f7ff;
$lighterBlue: #409eff;
.container {
position: relative;
width: 100%;
height: 100%;
}
.components-list {
padding: 8px;
box-sizing: border-box;
height: 100%;
.components-item {
display: inline-block;
width: 48%;
margin: 1%;
transition: transform 0ms !important;
}
}
.components-draggable {
padding-bottom: 20px;
}
.components-title {
font-size: 14px;
color: #222;
margin: 6px 2px;
.svg-icon {
color: #666;
font-size: 18px;
}
}
.components-body {
padding: 8px 10px;
background: $selectedColor;
font-size: 12px;
cursor: move;
border: 1px dashed $selectedColor;
border-radius: 3px;
.svg-icon {
color: #777;
font-size: 15px;
}
&:hover {
border: 1px dashed #787be8;
color: #787be8;
.svg-icon {
color: #787be8;
}
}
}
.left-board {
width: 260px;
position: absolute;
left: 0;
top: 0;
height: 100vh;
}
.left-scrollbar {
height: calc(100vh - 42px);
overflow: hidden;
}
.center-scrollbar {
height: calc(100vh - 42px);
overflow: hidden;
border-left: 1px solid #f1e8e8;
border-right: 1px solid #f1e8e8;
box-sizing: border-box;
}
.center-board {
height: 100vh;
width: auto;
margin: 0 350px 0 260px;
box-sizing: border-box;
}
.empty-info {
position: absolute;
top: 46%;
left: 0;
right: 0;
text-align: center;
font-size: 18px;
color: #ccb1ea;
letter-spacing: 4px;
}
.action-bar {
position: relative;
height: 42px;
text-align: right;
padding: 0 15px;
box-sizing: border-box;
border: 1px solid #f1e8e8;
border-top: none;
border-left: none;
.delete-btn {
color: #f56c6c;
}
}
.logo-wrapper {
position: relative;
height: 42px;
background: #fff;
border-bottom: 1px solid #f1e8e8;
box-sizing: border-box;
}
.logo {
position: absolute;
left: 12px;
top: 6px;
line-height: 30px;
color: #00afff;
font-weight: 600;
font-size: 17px;
white-space: nowrap;
> img {
width: 30px;
height: 30px;
vertical-align: top;
}
.github {
display: inline-block;
vertical-align: sub;
margin-left: 15px;
> img {
height: 22px;
}
}
}
.center-board-row {
padding: 12px 12px 15px 12px;
box-sizing: border-box;
& > .el-form {
// 69 = 12+15+42
height: calc(100vh - 69px);
}
}
.drawing-board {
height: 100%;
position: relative;
.components-body {
padding: 0;
margin: 0;
font-size: 0;
}
.sortable-ghost {
position: relative;
display: block;
overflow: hidden;
&::before {
content: ' ';
position: absolute;
left: 0;
right: 0;
top: 0;
height: 3px;
background: rgb(89, 89, 223);
z-index: 2;
}
}
.components-item.sortable-ghost {
width: 100%;
height: 60px;
background-color: $selectedColor;
}
.active-from-item {
& > .el-form-item {
background: $selectedColor;
border-radius: 6px;
}
& > .drawing-item-copy,
& > .drawing-item-delete {
display: initial;
}
& > .component-name {
color: $lighterBlue;
}
}
.el-form-item {
margin-bottom: 15px;
}
}
.drawing-item {
position: relative;
cursor: move;
&.unfocus-bordered:not(.activeFromItem) > div:first-child {
border: 1px dashed #ccc;
}
.el-form-item {
padding: 12px 10px;
}
}
.drawing-row-item {
position: relative;
cursor: move;
box-sizing: border-box;
border: 1px dashed #ccc;
border-radius: 3px;
padding: 0 2px;
margin-bottom: 15px;
.drawing-row-item {
margin-bottom: 2px;
}
.el-col {
margin-top: 22px;
}
.el-form-item {
margin-bottom: 0;
}
.drag-wrapper {
min-height: 80px;
}
&.active-from-item {
border: 1px dashed $lighterBlue;
}
.component-name {
position: absolute;
top: 0;
left: 0;
font-size: 12px;
color: #bbb;
display: inline-block;
padding: 0 6px;
}
}
.drawing-item,
.drawing-row-item {
&:hover {
& > .el-form-item {
background: $selectedColor;
border-radius: 6px;
}
& > .drawing-item-copy,
& > .drawing-item-delete {
display: initial;
}
}
& > .drawing-item-copy,
& > .drawing-item-delete {
display: none;
position: absolute;
top: -10px;
width: 22px;
height: 22px;
line-height: 22px;
text-align: center;
border-radius: 50%;
font-size: 12px;
border: 1px solid;
cursor: pointer;
z-index: 1;
}
& > .drawing-item-copy {
right: 56px;
border-color: $lighterBlue;
color: $lighterBlue;
background: #fff;
&:hover {
background: $lighterBlue;
color: #fff;
}
}
& > .drawing-item-delete {
right: 24px;
border-color: #f56c6c;
color: #f56c6c;
background: #fff;
&:hover {
background: #f56c6c;
color: #fff;
}
}
}
</style>
<template>
<el-form ref="basicInfoForm" :model="info" :rules="rules" label-width="150px">
<el-row>
<el-col :span="12">
<el-form-item label="表名称" prop="tableName">
<el-input v-model="info.tableName" placeholder="请输入仓库名称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="表描述" prop="tableComment">
<el-input v-model="info.tableComment" placeholder="请输入" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="实体类名称" prop="className">
<el-input v-model="info.className" placeholder="请输入" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="作者" prop="functionAuthor">
<el-input v-model="info.functionAuthor" placeholder="请输入" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="备注" prop="remark">
<el-input v-model="info.remark" type="textarea" :rows="3" />
</el-form-item>
</el-col>
</el-row>
</el-form>
</template>
<script>
export default {
props: {
info: {
type: Object,
default: null
}
},
data() {
return {
rules: {
tableName: [
{ required: true, message: '请输入表名称', trigger: 'blur' }
],
tableComment: [
{ required: true, message: '请输入表描述', trigger: 'blur' }
],
className: [
{ required: true, message: '请输入实体类名称', trigger: 'blur' }
],
functionAuthor: [
{ required: true, message: '请输入作者', trigger: 'blur' }
]
}
};
}
};
</script>
<template>
<el-card>
<el-tabs v-model="activeName">
<el-tab-pane label="基本信息" name="basic">
<basic-info-form ref="basicInfo" :info="info" />
</el-tab-pane>
<el-tab-pane label="字段信息" name="columnInfo">
<el-table
ref="dragTable"
:data="columns"
row-key="columnId"
:max-height="tableHeight"
>
<el-table-column
label="序号"
type="index"
min-width="5%"
class-name="allowDrag"
/>
<el-table-column
label="字段列名"
prop="columnName"
min-width="10%"
:show-overflow-tooltip="true"
/>
<el-table-column label="字段描述" min-width="10%">
<template slot-scope="scope">
<el-input v-model="scope.row.columnComment" />
</template>
</el-table-column>
<el-table-column
label="物理类型"
prop="columnType"
min-width="10%"
:show-overflow-tooltip="true"
/>
<el-table-column label="Java类型" min-width="11%">
<template slot-scope="scope">
<el-select v-model="scope.row.javaType">
<el-option label="Long" value="Long" />
<el-option label="String" value="String" />
<el-option label="Integer" value="Integer" />
<el-option label="Double" value="Double" />
<el-option label="BigDecimal" value="BigDecimal" />
<el-option label="Date" value="Date" />
<el-option label="Boolean" value="Boolean" />
</el-select>
</template>
</el-table-column>
<el-table-column label="java属性" min-width="10%">
<template slot-scope="scope">
<el-input v-model="scope.row.javaField" />
</template>
</el-table-column>
<el-table-column label="插入" min-width="5%">
<template slot-scope="scope">
<el-checkbox
v-model="scope.row.isInsert"
true-label="1"
false-label="0"
/>
</template>
</el-table-column>
<el-table-column label="编辑" min-width="5%">
<template slot-scope="scope">
<el-checkbox
v-model="scope.row.isEdit"
true-label="1"
false-label="0"
/>
</template>
</el-table-column>
<el-table-column label="列表" min-width="5%">
<template slot-scope="scope">
<el-checkbox
v-model="scope.row.isList"
true-label="1"
false-label="0"
/>
</template>
</el-table-column>
<el-table-column label="查询" min-width="5%">
<template slot-scope="scope">
<el-checkbox
v-model="scope.row.isQuery"
true-label="1"
false-label="0"
/>
</template>
</el-table-column>
<el-table-column label="查询方式" min-width="10%">
<template slot-scope="scope">
<el-select v-model="scope.row.queryType">
<el-option label="=" value="EQ" />
<el-option label="!=" value="NE" />
<el-option label=">" value="GT" />
<el-option label=">=" value="GTE" />
<el-option label="<" value="LT" />
<el-option label="<=" value="LTE" />
<el-option label="LIKE" value="LIKE" />
<el-option label="BETWEEN" value="BETWEEN" />
</el-select>
</template>
</el-table-column>
<el-table-column label="必填" min-width="5%">
<template slot-scope="scope">
<el-checkbox
v-model="scope.row.isRequired"
true-label="1"
false-label="0"
/>
</template>
</el-table-column>
<el-table-column label="显示类型" min-width="12%">
<template slot-scope="scope">
<el-select v-model="scope.row.htmlType">
<el-option label="文本框" value="input" />
<el-option label="文本域" value="textarea" />
<el-option label="下拉框" value="select" />
<el-option label="单选框" value="radio" />
<el-option label="复选框" value="checkbox" />
<el-option label="日期控件" value="datetime" />
<el-option label="图片上传" value="imageUpload" />
<el-option label="文件上传" value="fileUpload" />
<el-option label="富文本控件" value="editor" />
</el-select>
</template>
</el-table-column>
<el-table-column label="字典类型" min-width="12%">
<template slot-scope="scope">
<el-select
v-model="scope.row.dictType"
clearable
filterable
placeholder="请选择"
>
<el-option
v-for="dict in dictOptions"
:key="dict.dictType"
:label="dict.dictName"
:value="dict.dictType"
>
<span style="float: left">{{ dict.dictName }}</span>
<span style="float: right; color: #8492a6; font-size: 13px">{{
dict.dictType
}}</span>
</el-option>
</el-select>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="生成信息" name="genInfo">
<gen-info-form
ref="genInfo"
:info="info"
:tables="tables"
:menus="menus"
/>
</el-tab-pane>
</el-tabs>
<el-form label-width="100px">
<el-form-item
style="text-align: center; margin-left: -100px; margin-top: 10px"
>
<el-button type="primary" @click="submitForm()">提交</el-button>
<el-button @click="close()">返回</el-button>
</el-form-item>
</el-form>
</el-card>
</template>
<script>
import { getGenTable, updateGenTable } from '@/api/tool/gen';
import { optionselect as getDictOptionselect } from '@/api/system/dict/type';
import { listMenu as getMenuTreeselect } from '@/api/system/menu';
import basicInfoForm from './basicInfoForm';
import genInfoForm from './genInfoForm';
import Sortable from 'sortablejs';
export default {
name: 'GenEdit',
components: {
basicInfoForm,
genInfoForm
},
data() {
return {
// 选中选项卡的 name
activeName: 'columnInfo',
// 表格的高度
tableHeight: document.documentElement.scrollHeight - 245 + 'px',
// 表信息
tables: [],
// 表列信息
columns: [],
// 字典信息
dictOptions: [],
// 菜单信息
menus: [],
// 表详细信息
info: {}
};
},
created() {
const tableId = this.$route.params && this.$route.params.tableId;
if (tableId) {
// 获取表详细信息
getGenTable(tableId).then((res) => {
this.columns = res.data.rows;
this.info = res.data.info;
this.tables = res.data.tables;
});
/** 查询字典下拉列表 */
getDictOptionselect().then((response) => {
this.dictOptions = response.data;
});
/** 查询菜单下拉列表 */
getMenuTreeselect().then((response) => {
this.menus = this.handleTree(response.data, 'menuId');
});
}
},
mounted() {
const el = this.$refs.dragTable.$el.querySelectorAll(
'.el-table__body-wrapper > table > tbody'
)[0];
Sortable.create(el, {
handle: '.allowDrag',
onEnd: (evt) => {
const targetRow = this.columns.splice(evt.oldIndex, 1)[0];
this.columns.splice(evt.newIndex, 0, targetRow);
for (const index in this.columns) {
this.columns[index].sort = parseInt(index) + 1;
}
}
});
},
methods: {
/** 提交按钮 */
submitForm() {
const basicForm = this.$refs.basicInfo.$refs.basicInfoForm;
const genForm = this.$refs.genInfo.$refs.genInfoForm;
Promise.all([basicForm, genForm].map(this.getFormPromise)).then((res) => {
const validateResult = res.every((item) => !!item);
if (validateResult) {
const genTable = Object.assign({}, basicForm.model, genForm.model);
genTable.columns = this.columns;
genTable.params = {
treeCode: genTable.treeCode,
treeName: genTable.treeName,
treeParentCode: genTable.treeParentCode,
parentMenuId: genTable.parentMenuId
};
updateGenTable(genTable).then((res) => {
this.$modal.msgSuccess(res.msg);
if (res.code === 200) {
this.close();
}
});
} else {
this.$modal.msgError('表单校验未通过,请重新检查提交内容');
}
});
},
getFormPromise(form) {
return new Promise((resolve) => {
form.validate((res) => {
resolve(res);
});
});
},
/** 关闭按钮 */
close() {
const obj = {
path: '/tool/gen',
query: { t: Date.now(), pageNum: this.$route.query.pageNum }
};
this.$tab.closeOpenPage(obj);
}
}
};
</script>
<template>
<el-form ref="genInfoForm" :model="info" :rules="rules" label-width="150px">
<el-row>
<el-col :span="12">
<el-form-item prop="tplCategory">
<span slot="label">生成模板</span>
<el-select v-model="info.tplCategory" @change="tplSelectChange">
<el-option label="单表(增删改查)" value="crud" />
<el-option label="树表(增删改查)" value="tree" />
<el-option label="主子表(增删改查)" value="sub" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="tplWebType">
<span slot="label">前端类型</span>
<el-select v-model="info.tplWebType">
<el-option label="Vue2 Element UI 模版" value="element-ui" />
<el-option label="Vue3 Element Plus 模版" value="element-plus" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="packageName">
<span slot="label">
生成包路径
<el-tooltip
content="生成在哪个java包下,例如 com.ruoyi.system"
placement="top"
>
<i class="el-icon-question" />
</el-tooltip>
</span>
<el-input v-model="info.packageName" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="moduleName">
<span slot="label">
生成模块名
<el-tooltip content="可理解为子系统名,例如 system" placement="top">
<i class="el-icon-question" />
</el-tooltip>
</span>
<el-input v-model="info.moduleName" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="businessName">
<span slot="label">
生成业务名
<el-tooltip content="可理解为功能英文名,例如 user" placement="top">
<i class="el-icon-question" />
</el-tooltip>
</span>
<el-input v-model="info.businessName" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="functionName">
<span slot="label">
生成功能名
<el-tooltip content="用作类描述,例如 用户" placement="top">
<i class="el-icon-question" />
</el-tooltip>
</span>
<el-input v-model="info.functionName" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="genType">
<span slot="label">
生成代码方式
<el-tooltip
content="默认为zip压缩包下载,也可以自定义生成路径"
placement="top"
>
<i class="el-icon-question" />
</el-tooltip>
</span>
<el-radio v-model="info.genType" label="0">zip压缩包</el-radio>
<el-radio v-model="info.genType" label="1">自定义路径</el-radio>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item>
<span slot="label">
上级菜单
<el-tooltip
content="分配到指定菜单下,例如 系统管理"
placement="top"
>
<i class="el-icon-question" />
</el-tooltip>
</span>
<treeselect
v-model="info.parentMenuId"
:append-to-body="true"
:options="menus"
:normalizer="normalizer"
:show-count="true"
placeholder="请选择系统菜单"
/>
</el-form-item>
</el-col>
<el-col v-if="info.genType == '1'" :span="24">
<el-form-item prop="genPath">
<span slot="label">
自定义路径
<el-tooltip
content="填写磁盘绝对路径,若不填写,则生成到当前Web项目下"
placement="top"
>
<i class="el-icon-question" />
</el-tooltip>
</span>
<el-input v-model="info.genPath">
<el-dropdown slot="append">
<el-button type="primary">
最近路径快速选择
<i class="el-icon-arrow-down el-icon--right" />
</el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item @click.native="info.genPath = '/'"
>恢复默认的生成基础路径</el-dropdown-item
>
</el-dropdown-menu>
</el-dropdown>
</el-input>
</el-form-item>
</el-col>
</el-row>
<el-row v-show="info.tplCategory == 'tree'">
<h4 class="form-header">其他信息</h4>
<el-col :span="12">
<el-form-item>
<span slot="label">
树编码字段
<el-tooltip
content="树显示的编码字段名, 如:dept_id"
placement="top"
>
<i class="el-icon-question" />
</el-tooltip>
</span>
<el-select v-model="info.treeCode" placeholder="请选择">
<el-option
v-for="(column, index) in info.columns"
:key="index"
:label="column.columnName + ':' + column.columnComment"
:value="column.columnName"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item>
<span slot="label">
树父编码字段
<el-tooltip
content="树显示的父编码字段名, 如:parent_Id"
placement="top"
>
<i class="el-icon-question" />
</el-tooltip>
</span>
<el-select v-model="info.treeParentCode" placeholder="请选择">
<el-option
v-for="(column, index) in info.columns"
:key="index"
:label="column.columnName + ':' + column.columnComment"
:value="column.columnName"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item>
<span slot="label">
树名称字段
<el-tooltip
content="树节点的显示名称字段名, 如:dept_name"
placement="top"
>
<i class="el-icon-question" />
</el-tooltip>
</span>
<el-select v-model="info.treeName" placeholder="请选择">
<el-option
v-for="(column, index) in info.columns"
:key="index"
:label="column.columnName + ':' + column.columnComment"
:value="column.columnName"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row v-show="info.tplCategory == 'sub'">
<h4 class="form-header">关联信息</h4>
<el-col :span="12">
<el-form-item>
<span slot="label">
关联子表的表名
<el-tooltip content="关联子表的表名, 如:sys_user" placement="top">
<i class="el-icon-question" />
</el-tooltip>
</span>
<el-select
v-model="info.subTableName"
placeholder="请选择"
@change="subSelectChange"
>
<el-option
v-for="(table, index) in tables"
:key="index"
:label="table.tableName + ':' + table.tableComment"
:value="table.tableName"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item>
<span slot="label">
子表关联的外键名
<el-tooltip
content="子表关联的外键名, 如:user_id"
placement="top"
>
<i class="el-icon-question" />
</el-tooltip>
</span>
<el-select v-model="info.subTableFkName" placeholder="请选择">
<el-option
v-for="(column, index) in subColumns"
:key="index"
:label="column.columnName + ':' + column.columnComment"
:value="column.columnName"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-form>
</template>
<script>
import Treeselect from '@riophae/vue-treeselect';
import '@riophae/vue-treeselect/dist/vue-treeselect.css';
export default {
components: { Treeselect },
props: {
info: {
type: Object,
default: null
},
tables: {
type: Array,
default: null
},
menus: {
type: Array,
default: () => []
}
},
data() {
return {
subColumns: [],
rules: {
tplCategory: [
{ required: true, message: '请选择生成模板', trigger: 'blur' }
],
packageName: [
{ required: true, message: '请输入生成包路径', trigger: 'blur' }
],
moduleName: [
{ required: true, message: '请输入生成模块名', trigger: 'blur' }
],
businessName: [
{ required: true, message: '请输入生成业务名', trigger: 'blur' }
],
functionName: [
{ required: true, message: '请输入生成功能名', trigger: 'blur' }
]
}
};
},
watch: {
'info.subTableName': function (val) {
this.setSubTableColumns(val);
},
'info.tplWebType': function (val) {
if (val === '') {
this.info.tplWebType = 'element-ui';
}
}
},
methods: {
/** 转换菜单数据结构 */
normalizer(node) {
if (node.children && !node.children.length) {
delete node.children;
}
return {
id: node.menuId,
label: node.menuName,
children: node.children
};
},
/** 选择子表名触发 */
subSelectChange(value) {
this.info.subTableFkName = '';
},
/** 选择生成模板触发 */
tplSelectChange(value) {
if (value !== 'sub') {
this.info.subTableName = '';
this.info.subTableFkName = '';
}
},
/** 设置关联外键 */
setSubTableColumns(value) {
for (var item in this.tables) {
const name = this.tables[item].tableName;
if (value === name) {
this.subColumns = this.tables[item].columns;
break;
}
}
}
}
};
</script>
<template>
<!-- 导入表 -->
<el-dialog
title="导入表"
:visible.sync="visible"
width="800px"
top="5vh"
append-to-body
>
<el-form ref="queryForm" :model="queryParams" size="small" :inline="true">
<el-form-item label="表名称" prop="tableName">
<el-input
v-model="queryParams.tableName"
placeholder="请输入表名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="表描述" prop="tableComment">
<el-input
v-model="queryParams.tableComment"
placeholder="请输入表描述"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery"
>搜索</el-button
>
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row>
<el-table
ref="table"
:data="dbTableList"
height="260px"
@row-click="clickRow"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" />
<el-table-column
prop="tableName"
label="表名称"
:show-overflow-tooltip="true"
/>
<el-table-column
prop="tableComment"
label="表描述"
:show-overflow-tooltip="true"
/>
<el-table-column prop="createTime" label="创建时间" />
<el-table-column prop="updateTime" label="更新时间" />
</el-table>
<pagination
v-show="total > 0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
</el-row>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="handleImportTable">确 定</el-button>
<el-button @click="visible = false">取 消</el-button>
</div>
</el-dialog>
</template>
<script>
import { listDbTable, importTable } from '@/api/tool/gen';
export default {
data() {
return {
// 遮罩层
visible: false,
// 选中数组值
tables: [],
// 总条数
total: 0,
// 表数据
dbTableList: [],
// 查询参数
queryParams: {
pageNum: 1,
pageSize: 10,
tableName: undefined,
tableComment: undefined
}
};
},
methods: {
// 显示弹框
show() {
this.getList();
this.visible = true;
},
clickRow(row) {
this.$refs.table.toggleRowSelection(row);
},
// 多选框选中数据
handleSelectionChange(selection) {
this.tables = selection.map((item) => item.tableName);
},
// 查询表数据
getList() {
listDbTable(this.queryParams).then((res) => {
if (res.code === 200) {
this.dbTableList = res.rows;
this.total = res.total;
}
});
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm('queryForm');
this.handleQuery();
},
/** 导入按钮操作 */
handleImportTable() {
const tableNames = this.tables.join(',');
if (tableNames == '') {
this.$modal.msgError('请选择要导入的表');
return;
}
importTable({ tables: tableNames }).then((res) => {
this.$modal.msgSuccess(res.msg);
if (res.code === 200) {
this.visible = false;
this.$emit('ok');
}
});
}
}
};
</script>
<template>
<div class="app-container">
<el-form
v-show="showSearch"
ref="queryForm"
:model="queryParams"
size="small"
:inline="true"
label-width="68px"
>
<el-form-item label="表名称" prop="tableName">
<el-input
v-model="queryParams.tableName"
placeholder="请输入表名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="表描述" prop="tableComment">
<el-input
v-model="queryParams.tableComment"
placeholder="请输入表描述"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="创建时间">
<el-date-picker
v-model="dateRange"
style="width: 240px"
value-format="yyyy-MM-dd"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery"
>搜索</el-button
>
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
v-hasPermi="['tool:gen:code']"
type="primary"
plain
icon="el-icon-download"
@click="handleGenTable"
>生成</el-button
>
</el-col>
<el-col :span="1.5">
<el-button
v-hasPermi="['tool:gen:import']"
type="info"
plain
icon="el-icon-upload"
@click="openImportTable"
>导入</el-button
>
</el-col>
<el-col :span="1.5">
<el-button
v-hasPermi="['tool:gen:edit']"
type="success"
plain
icon="el-icon-edit"
:disabled="single"
@click="handleEditTable"
>修改</el-button
>
</el-col>
<el-col :span="1.5">
<el-button
v-hasPermi="['tool:gen:remove']"
type="danger"
plain
icon="el-icon-delete"
:disabled="multiple"
@click="handleDelete"
>删除</el-button
>
</el-col>
<right-toolbar :show-search.sync="showSearch" @queryTable="getList" />
</el-row>
<el-table
v-loading="loading"
:data="tableList"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" align="center" width="55" />
<el-table-column label="序号" type="index" width="50" align="center">
<template slot-scope="scope">
<span>{{
(queryParams.pageNum - 1) * queryParams.pageSize + scope.$index + 1
}}</span>
</template>
</el-table-column>
<el-table-column
label="表名称"
align="center"
prop="tableName"
:show-overflow-tooltip="true"
width="120"
/>
<el-table-column
label="表描述"
align="center"
prop="tableComment"
:show-overflow-tooltip="true"
width="120"
/>
<el-table-column
label="实体"
align="center"
prop="className"
:show-overflow-tooltip="true"
width="120"
/>
<el-table-column
label="创建时间"
align="center"
prop="createTime"
width="160"
/>
<el-table-column
label="更新时间"
align="center"
prop="updateTime"
width="160"
/>
<el-table-column
label="操作"
align="center"
class-name="small-padding fixed-width"
>
<template slot-scope="scope">
<el-button
v-hasPermi="['tool:gen:preview']"
type="text"
size="small"
icon="el-icon-view"
@click="handlePreview(scope.row)"
>预览</el-button
>
<el-button
v-hasPermi="['tool:gen:edit']"
type="text"
size="small"
icon="el-icon-edit"
@click="handleEditTable(scope.row)"
>编辑</el-button
>
<el-button
v-hasPermi="['tool:gen:remove']"
type="text"
size="small"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
>删除</el-button
>
<el-button
v-hasPermi="['tool:gen:edit']"
type="text"
size="small"
icon="el-icon-refresh"
@click="handleSynchDb(scope.row)"
>同步</el-button
>
<el-button
v-hasPermi="['tool:gen:code']"
type="text"
size="small"
icon="el-icon-download"
@click="handleGenTable(scope.row)"
>生成代码</el-button
>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 预览界面 -->
<el-dialog
:title="preview.title"
:visible.sync="preview.open"
width="80%"
top="5vh"
append-to-body
class="scrollbar"
>
<el-tabs v-model="preview.activeName">
<el-tab-pane
v-for="(value, key) in preview.data"
:key="key"
:label="key.substring(key.lastIndexOf('/') + 1, key.indexOf('.vm'))"
:name="key.substring(key.lastIndexOf('/') + 1, key.indexOf('.vm'))"
>
<el-link
v-clipboard:copy="value"
v-clipboard:success="clipboardSuccess"
:underline="false"
icon="el-icon-document-copy"
style="float: right"
>复制</el-link
>
<pre><code class="hljs" v-html="highlightedCode(value, key)" /></pre>
</el-tab-pane>
</el-tabs>
</el-dialog>
<import-table ref="import" @ok="handleQuery" />
</div>
</template>
<script>
import {
listTable,
previewTable,
delTable,
genCode,
synchDb
} from '@/api/tool/gen';
import importTable from './importTable';
import hljs from 'highlight.js/lib/highlight';
import 'highlight.js/styles/github-gist.css';
hljs.registerLanguage('java', require('highlight.js/lib/languages/java'));
hljs.registerLanguage('xml', require('highlight.js/lib/languages/xml'));
hljs.registerLanguage('html', require('highlight.js/lib/languages/xml'));
hljs.registerLanguage('vue', require('highlight.js/lib/languages/xml'));
hljs.registerLanguage(
'javascript',
require('highlight.js/lib/languages/javascript')
);
hljs.registerLanguage('sql', require('highlight.js/lib/languages/sql'));
export default {
name: 'Gen',
components: { importTable },
data() {
return {
// 遮罩层
loading: true,
// 唯一标识符
uniqueId: '',
// 选中数组
ids: [],
// 选中表数组
tableNames: [],
// 非单个禁用
single: true,
// 非多个禁用
multiple: true,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 表数据
tableList: [],
// 日期范围
dateRange: '',
// 查询参数
queryParams: {
pageNum: 1,
pageSize: 10,
tableName: undefined,
tableComment: undefined
},
// 预览参数
preview: {
open: false,
title: '代码预览',
data: {},
activeName: 'domain.java'
}
};
},
created() {
this.getList();
},
activated() {
const time = this.$route.query.t;
if (time != null && time != this.uniqueId) {
this.uniqueId = time;
this.queryParams.pageNum = Number(this.$route.query.pageNum);
this.getList();
}
},
methods: {
/** 查询表集合 */
getList() {
this.loading = true;
listTable(this.addDateRange(this.queryParams, this.dateRange)).then(
(response) => {
this.tableList = response.rows;
this.total = response.total;
this.loading = false;
}
);
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 生成代码操作 */
handleGenTable(row) {
const tableNames = row.tableName || this.tableNames;
if (tableNames == '') {
this.$modal.msgError('请选择要生成的数据');
return;
}
if (row.genType === '1') {
genCode(row.tableName).then((response) => {
this.$modal.msgSuccess('成功生成到自定义路径:' + row.genPath);
});
} else {
this.$download.zip(
'/tool/gen/batchGenCode?tables=' + tableNames,
'ruoyi.zip'
);
}
},
/** 同步数据库操作 */
handleSynchDb(row) {
const tableName = row.tableName;
this.$modal
.confirm('确认要强制同步"' + tableName + '"表结构吗?')
.then(function () {
return synchDb(tableName);
})
.then(() => {
this.$modal.msgSuccess('同步成功');
})
.catch(() => {});
},
/** 打开导入表弹窗 */
openImportTable() {
this.$refs.import.show();
},
/** 重置按钮操作 */
resetQuery() {
this.dateRange = [];
this.resetForm('queryForm');
this.handleQuery();
},
/** 预览按钮 */
handlePreview(row) {
previewTable(row.tableId).then((response) => {
this.preview.data = response.data;
this.preview.open = true;
this.preview.activeName = 'domain.java';
});
},
/** 高亮显示 */
highlightedCode(code, key) {
const vmName = key.substring(
key.lastIndexOf('/') + 1,
key.indexOf('.vm')
);
var language = vmName.substring(vmName.indexOf('.') + 1, vmName.length);
const result = hljs.highlight(language, code || '', true);
return result.value || '&nbsp;';
},
/** 复制代码成功 */
clipboardSuccess() {
this.$modal.msgSuccess('复制成功');
},
// 多选框选中数据
handleSelectionChange(selection) {
this.ids = selection.map((item) => item.tableId);
this.tableNames = selection.map((item) => item.tableName);
this.single = selection.length != 1;
this.multiple = !selection.length;
},
/** 修改按钮操作 */
handleEditTable(row) {
const tableId = row.tableId || this.ids[0];
const tableName = row.tableName || this.tableNames[0];
const params = { pageNum: this.queryParams.pageNum };
this.$tab.openPage(
'修改[' + tableName + ']生成配置',
'/tool/gen-edit/index/' + tableId,
params
);
},
/** 删除按钮操作 */
handleDelete(row) {
const tableIds = row.tableId || this.ids;
this.$modal
.confirm('是否确认删除表编号为"' + tableIds + '"的数据项?')
.then(function () {
return delTable(tableIds);
})
.then(() => {
this.getList();
this.$modal.msgSuccess('删除成功');
})
.catch(() => {});
}
}
};
</script>
<template>
<i-frame :src="url" />
</template>
<script>
import iFrame from '@/components/iFrame/index';
export default {
name: 'Swagger',
components: { iFrame },
data() {
return {
url: process.env.VUE_APP_BASE_API + '/swagger-ui/index.html'
};
}
};
</script>
......@@ -51,7 +51,8 @@ module.exports = {
css: {
loaderOptions: {
sass: {
sassOptions: { outputStyle: 'expanded' }
sassOptions: { outputStyle: 'expanded' },
additionalData: `@import "~@taiyuan/ty-ui/packages/styles/var.scss";`
}
}
},
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论