Merge branch 'projects/mesxc-test' into projects/mesxc-zhp

This commit is contained in:
‘937886381’ 2024-03-29 09:51:47 +08:00
commit c8913f192c
79 changed files with 11174 additions and 196 deletions

View File

@ -13,14 +13,14 @@ VUE_APP_TITLE = MES系统
# 芋道管理系统/开发环境 # 芋道管理系统/开发环境
# VUE_APP_BASE_API = 'http://100.64.0.26:48082' # VUE_APP_BASE_API = 'http://100.64.0.26:48082'
# VUE_APP_BASE_API = 'http://10.70.2.2:8080' VUE_APP_BASE_API = 'http://10.70.2.2:8080'
# VUE_APP_BASE_API = 'http://192.168.1.20:48080' # VUE_APP_BASE_API = 'http://192.168.1.20:48080'
# VUE_APP_BASE_API = 'http://192.168.2.173:48080' # VUE_APP_BASE_API = 'http://192.168.2.173:48080'
# VUE_APP_BASE_API = 'http://192.168.1.49:48082' # VUE_APP_BASE_API = 'http://192.168.1.49:48082'
# VUE_APP_BASE_API = 'http://192.168.1.8:48082' # VUE_APP_BASE_API = 'http://192.168.1.8:48082'
# VUE_APP_BASE_API = 'http://192.168.4.159:48080' # VUE_APP_BASE_API = 'http://192.168.4.159:48080'
# VUE_APP_BASE_API = 'http://192.168.1.104:48082' # VUE_APP_BASE_API = 'http://192.168.1.104:48082'
VUE_APP_BASE_API = 'http://192.168.0.33:48082' # VUE_APP_BASE_API = 'http://192.168.0.33:48082'
# VUE_APP_BASE_API = 'http://192.168.1.62:48082' # VUE_APP_BASE_API = 'http://192.168.1.62:48082'
# VUE_APP_BASE_API = 'http://192.168.1.78:48082' # VUE_APP_BASE_API = 'http://192.168.1.78:48082'
# VUE_APP_BASE_API = 'http://192.168.1.47:48082' # VUE_APP_BASE_API = 'http://192.168.1.47:48082'

View File

@ -0,0 +1,20 @@
import request from '@/utils/request'
// 获得API 访问日志分页
export function getApiAccessLogPage(query) {
return request({
url: '/infra/api-access-log/page',
method: 'get',
params: query
})
}
// 导出API 访问日志 Excel
export function exportApiAccessLogExcel(query) {
return request({
url: '/infra/api-access-log/export-excel',
method: 'get',
params: query,
responseType: 'blob'
})
}

View File

@ -0,0 +1,28 @@
import request from '@/utils/request'
// 更新 API 错误日志的处理状态
export function updateApiErrorLogProcess(id, processStatus) {
return request({
url: '/infra/api-error-log/update-status?id=' + id + '&processStatus=' + processStatus,
method: 'put',
})
}
// 获得API 错误日志分页
export function getApiErrorLogPage(query) {
return request({
url: '/infra/api-error-log/page',
method: 'get',
params: query
})
}
// 导出API 错误日志 Excel
export function exportApiErrorLogExcel(query) {
return request({
url: '/infra/api-error-log/export-excel',
method: 'get',
params: query,
responseType: 'blob'
})
}

90
src/api/infra/codegen.js Normal file
View File

@ -0,0 +1,90 @@
import request from '@/utils/request'
// 获得表定义分页
export function getCodegenTablePage(query) {
return request({
url: '/infra/codegen/table/page',
method: 'get',
params: query
})
}
// 获得表和字段的明细
export function getCodegenDetail(tableId) {
return request({
url: '/infra/codegen/detail?tableId=' + tableId,
method: 'get',
})
}
// 修改代码生成信息
export function updateCodegen(data) {
return request({
url: '/infra/codegen/update',
method: 'put',
data: data
})
}
// 基于数据库的表结构,同步数据库的表和字段定义
export function syncCodegenFromDB(tableId) {
return request({
url: '/infra/codegen/sync-from-db?tableId=' + tableId,
method: 'put'
})
}
// 基于 SQL 建表语句,同步数据库的表和字段定义
export function syncCodegenFromSQL(tableId, sql) {
return request({
url: '/infra/codegen/sync-from-sql?tableId=' + tableId,
method: 'put',
headers:{
'Content-type': 'application/x-www-form-urlencoded'
},
data: 'tableId=' + tableId + "&sql=" + sql,
})
}
// 预览生成代码
export function previewCodegen(tableId) {
return request({
url: '/infra/codegen/preview?tableId=' + tableId,
method: 'get',
})
}
// 下载生成代码
export function downloadCodegen(tableId) {
return request({
url: '/infra/codegen/download?tableId=' + tableId,
method: 'get',
responseType: 'blob'
})
}
// 获得表定义分页
export function getSchemaTableList(query) {
return request({
url: '/infra/codegen/db/table/list',
method: 'get',
params: query
})
}
// 基于数据库的表结构,创建代码生成器的表定义
export function createCodegenList(data) {
return request({
url: '/infra/codegen/create-list',
method: 'post',
data: data
})
}
// 删除数据库的表和字段定义
export function deleteCodegen(tableId) {
return request({
url: '/infra/codegen/delete?tableId=' + tableId,
method: 'delete'
})
}

62
src/api/infra/config.js Normal file
View File

@ -0,0 +1,62 @@
import request from '@/utils/request'
// 查询参数列表
export function listConfig(query) {
return request({
url: '/infra/config/page',
method: 'get',
params: query
})
}
// 查询参数详细
export function getConfig(configId) {
return request({
url: '/infra/config/get?id=' + configId,
method: 'get'
})
}
// 根据参数键名查询参数值
export function getConfigKey(configKey) {
return request({
url: '/infra/config/get-value-by-key?key=' + configKey,
method: 'get'
})
}
// 新增参数配置
export function addConfig(data) {
return request({
url: '/infra/config/create',
method: 'post',
data: data
})
}
// 修改参数配置
export function updateConfig(data) {
return request({
url: '/infra/config/update',
method: 'put',
data: data
})
}
// 删除参数配置
export function delConfig(configId) {
return request({
url: '/infra/config/delete?id=' + configId,
method: 'delete'
})
}
// 导出参数
export function exportConfig(query) {
return request({
url: '/infra/config/export',
method: 'get',
params: query,
responseType: 'blob'
})
}

View File

@ -0,0 +1,43 @@
import request from '@/utils/request'
// 创建数据源配置
export function createDataSourceConfig(data) {
return request({
url: '/infra/data-source-config/create',
method: 'post',
data: data
})
}
// 更新数据源配置
export function updateDataSourceConfig(data) {
return request({
url: '/infra/data-source-config/update',
method: 'put',
data: data
})
}
// 删除数据源配置
export function deleteDataSourceConfig(id) {
return request({
url: '/infra/data-source-config/delete?id=' + id,
method: 'delete'
})
}
// 获得数据源配置
export function getDataSourceConfig(id) {
return request({
url: '/infra/data-source-config/get?id=' + id,
method: 'get'
})
}
// 获得数据源配置列表
export function getDataSourceConfigList() {
return request({
url: '/infra/data-source-config/list',
method: 'get',
})
}

26
src/api/infra/dbDoc.js Normal file
View File

@ -0,0 +1,26 @@
// 导出参数
import request from "@/utils/request";
export function exportHtml() {
return request({
url: '/infra/db-doc/export-html',
method: 'get',
responseType: 'blob'
})
}
export function exportWord() {
return request({
url: '/infra/db-doc/export-word',
method: 'get',
responseType: 'blob'
})
}
export function exportMarkdown() {
return request({
url: '/infra/db-doc/export-markdown',
method: 'get',
responseType: 'blob'
})
}

18
src/api/infra/file.js Normal file
View File

@ -0,0 +1,18 @@
import request from '@/utils/request'
// 删除文件
export function deleteFile(id) {
return request({
url: '/infra/file/delete?id=' + id,
method: 'delete'
})
}
// 获得文件分页
export function getFilePage(query) {
return request({
url: '/infra/file/page',
method: 'get',
params: query
})
}

View File

@ -0,0 +1,59 @@
import request from '@/utils/request'
// 创建文件配置
export function createFileConfig(data) {
return request({
url: '/infra/file-config/create',
method: 'post',
data: data
})
}
// 更新文件配置
export function updateFileConfig(data) {
return request({
url: '/infra/file-config/update',
method: 'put',
data: data
})
}
// 更新文件配置为主配置
export function updateFileConfigMaster(id) {
return request({
url: '/infra/file-config/update-master?id=' + id,
method: 'put'
})
}
// 删除文件配置
export function deleteFileConfig(id) {
return request({
url: '/infra/file-config/delete?id=' + id,
method: 'delete'
})
}
// 获得文件配置
export function getFileConfig(id) {
return request({
url: '/infra/file-config/get?id=' + id,
method: 'get'
})
}
// 获得文件配置分页
export function getFileConfigPage(query) {
return request({
url: '/infra/file-config/page',
method: 'get',
params: query
})
}
export function testFileConfig(id) {
return request({
url: '/infra/file-config/test?id=' + id,
method: 'get'
})
}

82
src/api/infra/job.js Normal file
View File

@ -0,0 +1,82 @@
import request from '@/utils/request'
// 查询定时任务调度列表
export function listJob(query) {
return request({
url: '/infra/job/page',
method: 'get',
params: query
})
}
// 查询定时任务调度详细
export function getJob(jobId) {
return request({
url: '/infra/job/get?id=' + jobId,
method: 'get'
})
}
// 新增定时任务调度
export function addJob(data) {
return request({
url: '/infra/job/create',
method: 'post',
data: data
})
}
// 修改定时任务调度
export function updateJob(data) {
return request({
url: '/infra/job/update',
method: 'put',
data: data
})
}
// 删除定时任务调度
export function delJob(jobId) {
return request({
url: '/infra/job/delete?id=' + jobId,
method: 'delete'
})
}
// 导出定时任务调度
export function exportJob(query) {
return request({
url: '/infra/job/export-excel',
method: 'get',
params: query,
responseType: 'blob'
})
}
// 任务状态修改
export function updateJobStatus(jobId, status) {
return request({
url: '/infra/job/update-status',
method: 'put',
headers:{
'Content-type': 'application/x-www-form-urlencoded'
},
data: 'id=' + jobId + "&status=" + status,
})
}
// 定时任务立即执行一次
export function runJob(jobId) {
return request({
url: '/infra/job/trigger?id=' + jobId,
method: 'put'
})
}
// 获得定时任务的下 n 次执行时间
export function getJobNextTimes(jobId) {
return request({
url: '/infra/job/get_next_times?id=' + jobId,
method: 'get'
})
}

28
src/api/infra/jobLog.js Normal file
View File

@ -0,0 +1,28 @@
import request from '@/utils/request'
// 获得定时任务
export function getJobLog(id) {
return request({
url: '/infra/job-log/get?id=' + id,
method: 'get'
})
}
// 获得定时任务分页
export function getJobLogPage(query) {
return request({
url: '/infra/job-log/page',
method: 'get',
params: query
})
}
// 导出定时任务 Excel
export function exportJobLogExcel(query) {
return request({
url: '/infra/job-log/export-excel',
method: 'get',
params: query,
responseType: 'blob'
})
}

9
src/api/infra/redis.js Normal file
View File

@ -0,0 +1,9 @@
import request from '@/utils/request'
// 查询缓存详细
export function getCache() {
return request({
url: '/infra/redis/get-monitor-info',
method: 'get'
})
}

54
src/api/infra/testDemo.js Normal file
View File

@ -0,0 +1,54 @@
import request from '@/utils/request'
// 创建字典类型
export function createTestDemo(data) {
return request({
url: '/infra/test-demo/create',
method: 'post',
data: data
})
}
// 更新字典类型
export function updateTestDemo(data) {
return request({
url: '/infra/test-demo/update',
method: 'put',
data: data
})
}
// 删除字典类型
export function deleteTestDemo(id) {
return request({
url: '/infra/test-demo/delete?id=' + id,
method: 'delete'
})
}
// 获得字典类型
export function getTestDemo(id) {
return request({
url: '/infra/test-demo/get?id=' + id,
method: 'get'
})
}
// 获得字典类型分页
export function getTestDemoPage(query) {
return request({
url: '/infra/test-demo/page',
method: 'get',
params: query
})
}
// 导出字典类型 Excel
export function exportTestDemoExcel(query) {
return request({
url: '/infra/test-demo/export-excel',
method: 'get',
params: query,
responseType: 'blob'
})
}

View File

@ -21,9 +21,11 @@ body {
.searchBarBox .el-form-item--medium .el-form-item__label { .searchBarBox .el-form-item--medium .el-form-item__label {
line-height: 40px; line-height: 40px;
} }
.searchBarBox .el-form-item--medium .el-form-item__content { .searchBarBox .el-form-item--medium .el-form-item__content {
line-height: 40px; line-height: 40px;
} }
.searchBarBox .el-range-editor--small.el-input__inner { .searchBarBox .el-range-editor--small.el-input__inner {
height: 34px; height: 34px;
} }
@ -153,13 +155,11 @@ aside {
text-align: right; text-align: right;
padding-right: 20px; padding-right: 20px;
transition: 600ms ease position; transition: 600ms ease position;
background: linear-gradient( background: linear-gradient(90deg,
90deg,
rgba(32, 182, 249, 1) 0%, rgba(32, 182, 249, 1) 0%,
rgba(32, 182, 249, 1) 0%, rgba(32, 182, 249, 1) 0%,
rgba(33, 120, 241, 1) 100%, rgba(33, 120, 241, 1) 100%,
rgba(33, 120, 241, 1) 100% rgba(33, 120, 241, 1) 100%);
);
.subtitle { .subtitle {
font-size: 20px; font-size: 20px;
@ -204,10 +204,85 @@ aside {
z-index: 1000 !important; z-index: 1000 !important;
} }
// 滚动条
::-webkit-scrollbar {
width: 8px;
height: 8px;
background-color: transparent;
}
::-webkit-scrollbar-track-piece {
background-color: rgba(144, 147, 153, 0);
}
::-webkit-scrollbar-corner {
background-color: rgba(144, 147, 153, 0);
}
::-webkit-scrollbar-track {
width: 6px;
background: rgba(144, 147, 153, 0);
-webkit-border-radius: 2em;
-moz-border-radius: 2em;
border-radius: 2em;
}
::-webkit-scrollbar-thumb {
background-color: #EDEDED;
background-clip: padding-box;
min-height: 28px;
-webkit-border-radius: 2em;
-moz-border-radius: 2em;
border-radius: 2em;
transition: background-color .3s;
cursor: pointer;
}
::-webkit-scrollbar-thumb:hover {
background-color: #D9D9D9;
}
// 抽屉head区域---start
.el-drawer__header {
padding-bottom: 20px;
padding-left: 30px;
margin-bottom: 23px;
font-size: 20px;
font-weight: 500;
color: rgba(0, 0, 0, 0.85);
border-bottom: 1px solid rgba(233, 233, 233, 1);
}
.el-drawer__header> :first-child::before {
content: '';
display: inline-block;
width: 4px;
height: 24px;
background-color: #0b58ff;
margin-right: 8px;
vertical-align: middle;
}
// 抽屉head区域---end
// 弹出框上下分布去掉label的padding-bottom
.el-form--label-top .el-form-item__label {
padding: 0;
}
// 大屏滚动表格
.dv-scroll-board .rows .ceil,
.dv-scroll-board .header .header-item {
border-right: 1px solid rgba(13, 23, 40, 1);
}
.dv-scroll-board .rows .ceil:last-child,
.dv-scroll-board .header .header-item:last-child {
border-right: none;
}
.no-data-bg { .no-data-bg {
height: 240px; height: 240px;
background: url(../images/no-data-bg.png) 50% 100% / contain background: url(../images/no-data-bg.png) 50% 100% / contain no-repeat;
no-repeat;
position: relative; position: relative;
&::after { &::after {

View File

@ -2,7 +2,8 @@
<el-breadcrumb class="app-breadcrumb" separator="/"> <el-breadcrumb class="app-breadcrumb" separator="/">
<transition-group name="breadcrumb"> <transition-group name="breadcrumb">
<el-breadcrumb-item v-for="(item, index) in levelList" :key="item.path"> <el-breadcrumb-item v-for="(item, index) in levelList" :key="item.path">
<span v-if="item.redirect==='noRedirect'||index===levelList.length-1" class="no-redirect">{{ item.meta.title }}</span> <span v-if="item.redirect === 'noRedirect' || index === levelList.length - 1" class="no-redirect">{{ item.meta.title
}}</span>
<a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a> <a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a>
</el-breadcrumb-item> </el-breadcrumb-item>
</transition-group> </transition-group>
@ -64,7 +65,7 @@ export default {
.app-breadcrumb.el-breadcrumb { .app-breadcrumb.el-breadcrumb {
display: inline-block; display: inline-block;
font-size: 14px; font-size: 14px;
line-height: 56px; line-height: 48px;
margin-left: 8px; margin-left: 8px;
.no-redirect { .no-redirect {
@ -73,7 +74,8 @@ export default {
} }
} }
.app-breadcrumb .el-breadcrumb__inner a, .el-breadcrumb__inner.is-link { .app-breadcrumb .el-breadcrumb__inner a,
.el-breadcrumb__inner.is-link {
color: rgba(0, 0, 0, 0.45); color: rgba(0, 0, 0, 0.45);
} }
</style> </style>

View File

@ -0,0 +1,160 @@
<template>
<el-form size="small">
<el-form-item>
<el-radio v-model='radioValue' :label="1">
允许的通配符[, - * ? / L W]
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="2">
不指定
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="3">
周期从
<el-input-number v-model='cycle01' :min="1" :max="30" /> -
<el-input-number v-model='cycle02' :min="cycle01 ? cycle01 + 1 : 2" :max="31" />
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="4">
<el-input-number v-model='average01' :min="1" :max="30" /> 号开始
<el-input-number v-model='average02' :min="1" :max="31 - average01 || 1" /> 日执行一次
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="5">
每月
<el-input-number v-model='workday' :min="1" :max="31" /> 号最近的那个工作日
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="6">
本月最后一天
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="7">
指定
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple style="width:100%">
<el-option v-for="item in 31" :key="item" :value="item">{{item}}</el-option>
</el-select>
</el-radio>
</el-form-item>
</el-form>
</template>
<script>
export default {
data() {
return {
radioValue: 1,
workday: 1,
cycle01: 1,
cycle02: 2,
average01: 1,
average02: 1,
checkboxList: [],
checkNum: this.$options.propsData.check
}
},
name: 'crontab-day',
props: ['check', 'cron'],
methods: {
//
radioChange() {
('day rachange');
if (this.radioValue !== 2 && this.cron.week !== '?') {
this.$emit('update', 'week', '?', 'day')
}
switch (this.radioValue) {
case 1:
this.$emit('update', 'day', '*');
break;
case 2:
this.$emit('update', 'day', '?');
break;
case 3:
this.$emit('update', 'day', this.cycleTotal);
break;
case 4:
this.$emit('update', 'day', this.averageTotal);
break;
case 5:
this.$emit('update', 'day', this.workday + 'W');
break;
case 6:
this.$emit('update', 'day', 'L');
break;
case 7:
this.$emit('update', 'day', this.checkboxString);
break;
}
('day rachange end');
},
//
cycleChange() {
if (this.radioValue === '3') {
this.$emit('update', 'day', this.cycleTotal);
}
},
//
averageChange() {
if (this.radioValue === '4') {
this.$emit('update', 'day', this.averageTotal);
}
},
//
workdayChange() {
if (this.radioValue === '5') {
this.$emit('update', 'day', this.workdayCheck + 'W');
}
},
// checkbox
checkboxChange() {
if (this.radioValue === '7') {
this.$emit('update', 'day', this.checkboxString);
}
}
},
watch: {
'radioValue': 'radioChange',
'cycleTotal': 'cycleChange',
'averageTotal': 'averageChange',
'workdayCheck': 'workdayChange',
'checkboxString': 'checkboxChange',
},
computed: {
//
cycleTotal: function () {
const cycle01 = this.checkNum(this.cycle01, 1, 30)
const cycle02 = this.checkNum(this.cycle02, cycle01 ? cycle01 + 1 : 2, 31, 31)
return cycle01 + '-' + cycle02;
},
//
averageTotal: function () {
const average01 = this.checkNum(this.average01, 1, 30)
const average02 = this.checkNum(this.average02, 1, 31 - average01 || 0)
return average01 + '/' + average02;
},
//
workdayCheck: function () {
return this.checkNum(this.workday, 1, 31);
},
// checkbox
checkboxString: function () {
let str = this.checkboxList.join();
return str === '' ? '*' : str;
}
}
}
</script>

View File

@ -0,0 +1,114 @@
<template>
<el-form size="small">
<el-form-item>
<el-radio v-model='radioValue' :label="1">
小时允许的通配符[, - * /]
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="2">
周期从
<el-input-number v-model='cycle01' :min="0" :max="22" /> -
<el-input-number v-model='cycle02' :min="cycle01 ? cycle01 + 1 : 1" :max="23" /> 小时
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="3">
<el-input-number v-model='average01' :min="0" :max="22" /> 小时开始
<el-input-number v-model='average02' :min="1" :max="23 - average01 || 0" /> 小时执行一次
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="4">
指定
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple style="width:100%">
<el-option v-for="item in 24" :key="item" :value="item-1">{{item-1}}</el-option>
</el-select>
</el-radio>
</el-form-item>
</el-form>
</template>
<script>
export default {
data() {
return {
radioValue: 1,
cycle01: 0,
cycle02: 1,
average01: 0,
average02: 1,
checkboxList: [],
checkNum: this.$options.propsData.check
}
},
name: 'crontab-hour',
props: ['check', 'cron'],
methods: {
//
radioChange() {
switch (this.radioValue) {
case 1:
this.$emit('update', 'hour', '*')
break;
case 2:
this.$emit('update', 'hour', this.cycleTotal);
break;
case 3:
this.$emit('update', 'hour', this.averageTotal);
break;
case 4:
this.$emit('update', 'hour', this.checkboxString);
break;
}
},
//
cycleChange() {
if (this.radioValue === '2') {
this.$emit('update', 'hour', this.cycleTotal);
}
},
//
averageChange() {
if (this.radioValue === '3') {
this.$emit('update', 'hour', this.averageTotal);
}
},
// checkbox
checkboxChange() {
if (this.radioValue === '4') {
this.$emit('update', 'hour', this.checkboxString);
}
}
},
watch: {
'radioValue': 'radioChange',
'cycleTotal': 'cycleChange',
'averageTotal': 'averageChange',
'checkboxString': 'checkboxChange'
},
computed: {
//
cycleTotal: function () {
const cycle01 = this.checkNum(this.cycle01, 0, 22)
const cycle02 = this.checkNum(this.cycle02, cycle01 ? cycle01 + 1 : 1, 23)
return cycle01 + '-' + cycle02;
},
//
averageTotal: function () {
const average01 = this.checkNum(this.average01, 0, 22)
const average02 = this.checkNum(this.average02, 1, 23 - average01 || 0)
return average01 + '/' + average02;
},
// checkbox
checkboxString: function () {
let str = this.checkboxList.join();
return str === '' ? '*' : str;
}
}
}
</script>

View File

@ -0,0 +1,430 @@
<template>
<div>
<el-tabs type="border-card">
<el-tab-pane label="秒" v-if="shouldHide('second')">
<CrontabSecond
@update="updateCrontabValue"
:check="checkNumber"
:cron="crontabValueObj"
ref="cronsecond"
/>
</el-tab-pane>
<el-tab-pane label="分钟" v-if="shouldHide('min')">
<CrontabMin
@update="updateCrontabValue"
:check="checkNumber"
:cron="crontabValueObj"
ref="cronmin"
/>
</el-tab-pane>
<el-tab-pane label="小时" v-if="shouldHide('hour')">
<CrontabHour
@update="updateCrontabValue"
:check="checkNumber"
:cron="crontabValueObj"
ref="cronhour"
/>
</el-tab-pane>
<el-tab-pane label="日" v-if="shouldHide('day')">
<CrontabDay
@update="updateCrontabValue"
:check="checkNumber"
:cron="crontabValueObj"
ref="cronday"
/>
</el-tab-pane>
<el-tab-pane label="月" v-if="shouldHide('month')">
<CrontabMonth
@update="updateCrontabValue"
:check="checkNumber"
:cron="crontabValueObj"
ref="cronmonth"
/>
</el-tab-pane>
<el-tab-pane label="周" v-if="shouldHide('week')">
<CrontabWeek
@update="updateCrontabValue"
:check="checkNumber"
:cron="crontabValueObj"
ref="cronweek"
/>
</el-tab-pane>
<el-tab-pane label="年" v-if="shouldHide('year')">
<CrontabYear
@update="updateCrontabValue"
:check="checkNumber"
:cron="crontabValueObj"
ref="cronyear"
/>
</el-tab-pane>
</el-tabs>
<div class="popup-main">
<div class="popup-result">
<p class="title">时间表达式</p>
<table>
<thead>
<th v-for="item of tabTitles" width="40" :key="item">{{item}}</th>
<th>Cron 表达式</th>
</thead>
<tbody>
<td>
<span>{{crontabValueObj.second}}</span>
</td>
<td>
<span>{{crontabValueObj.min}}</span>
</td>
<td>
<span>{{crontabValueObj.hour}}</span>
</td>
<td>
<span>{{crontabValueObj.day}}</span>
</td>
<td>
<span>{{crontabValueObj.month}}</span>
</td>
<td>
<span>{{crontabValueObj.week}}</span>
</td>
<td>
<span>{{crontabValueObj.year}}</span>
</td>
<td>
<span>{{crontabValueString}}</span>
</td>
</tbody>
</table>
</div>
<CrontabResult :ex="crontabValueString"></CrontabResult>
<div class="pop_btn">
<el-button size="small" type="primary" @click="submitFill">确定</el-button>
<el-button size="small" type="warning" @click="clearCron">重置</el-button>
<el-button size="small" @click="hidePopup">取消</el-button>
</div>
</div>
</div>
</template>
<script>
import CrontabSecond from "./second.vue";
import CrontabMin from "./min.vue";
import CrontabHour from "./hour.vue";
import CrontabDay from "./day.vue";
import CrontabMonth from "./month.vue";
import CrontabWeek from "./week.vue";
import CrontabYear from "./year.vue";
import CrontabResult from "./result.vue";
export default {
data() {
return {
tabTitles: ["秒", "分钟", "小时", "日", "月", "周", "年"],
tabActive: 0,
myindex: 0,
crontabValueObj: {
second: "*",
min: "*",
hour: "*",
day: "*",
month: "*",
week: "?",
year: "",
},
};
},
name: "vcrontab",
props: ["expression", "hideComponent"],
methods: {
shouldHide(key) {
return !(this.hideComponent && this.hideComponent.includes(key));
},
resolveExp() {
//
if (this.expression) {
let arr = this.expression.split(" ");
if (arr.length >= 6) {
//6
let obj = {
second: arr[0],
min: arr[1],
hour: arr[2],
day: arr[3],
month: arr[4],
week: arr[5],
year: arr[6] ? arr[6] : "",
};
this.crontabValueObj = {
...obj,
};
for (let i in obj) {
if (obj[i]) this.changeRadio(i, obj[i]);
}
}
} else {
//
this.clearCron();
}
},
// tab
tabCheck(index) {
this.tabActive = index;
},
//
updateCrontabValue(name, value, from) {
"updateCrontabValue", name, value, from;
this.crontabValueObj[name] = value;
if (from && from !== name) {
console.log(`来自组件 ${from} 改变了 ${name} ${value}`);
this.changeRadio(name, value);
}
},
//
changeRadio(name, value) {
let arr = ["second", "min", "hour", "month"],
refName = "cron" + name,
insValue;
if (!this.$refs[refName]) return;
if (arr.includes(name)) {
if (value === "*") {
insValue = 1;
} else if (value.indexOf("-") > -1) {
let indexArr = value.split("-");
isNaN(indexArr[0])
? (this.$refs[refName].cycle01 = 0)
: (this.$refs[refName].cycle01 = indexArr[0]);
this.$refs[refName].cycle02 = indexArr[1];
insValue = 2;
} else if (value.indexOf("/") > -1) {
let indexArr = value.split("/");
isNaN(indexArr[0])
? (this.$refs[refName].average01 = 0)
: (this.$refs[refName].average01 = indexArr[0]);
this.$refs[refName].average02 = indexArr[1];
insValue = 3;
} else {
insValue = 4;
this.$refs[refName].checkboxList = value.split(",");
}
} else if (name === "day") {
if (value === "*") {
insValue = 1;
} else if (value === "?") {
insValue = 2;
} else if (value.indexOf("-") > -1) {
let indexArr = value.split("-");
isNaN(indexArr[0])
? (this.$refs[refName].cycle01 = 0)
: (this.$refs[refName].cycle01 = indexArr[0]);
this.$refs[refName].cycle02 = indexArr[1];
insValue = 3;
} else if (value.indexOf("/") > -1) {
let indexArr = value.split("/");
isNaN(indexArr[0])
? (this.$refs[refName].average01 = 0)
: (this.$refs[refName].average01 = indexArr[0]);
this.$refs[refName].average02 = indexArr[1];
insValue = 4;
} else if (value.indexOf("W") > -1) {
let indexArr = value.split("W");
isNaN(indexArr[0])
? (this.$refs[refName].workday = 0)
: (this.$refs[refName].workday = indexArr[0]);
insValue = 5;
} else if (value === "L") {
insValue = 6;
} else {
this.$refs[refName].checkboxList = value.split(",");
insValue = 7;
}
} else if (name === "week") {
if (value === "*") {
insValue = 1;
} else if (value === "?") {
insValue = 2;
} else if (value.indexOf("-") > -1) {
let indexArr = value.split("-");
isNaN(indexArr[0])
? (this.$refs[refName].cycle01 = 0)
: (this.$refs[refName].cycle01 = indexArr[0]);
this.$refs[refName].cycle02 = indexArr[1];
insValue = 3;
} else if (value.indexOf("#") > -1) {
let indexArr = value.split("#");
isNaN(indexArr[0])
? (this.$refs[refName].average01 = 1)
: (this.$refs[refName].average01 = indexArr[0]);
this.$refs[refName].average02 = indexArr[1];
insValue = 4;
} else if (value.indexOf("L") > -1) {
let indexArr = value.split("L");
isNaN(indexArr[0])
? (this.$refs[refName].weekday = 1)
: (this.$refs[refName].weekday = indexArr[0]);
insValue = 5;
} else {
this.$refs[refName].checkboxList = value.split(",");
insValue = 6;
}
} else if (name === "year") {
if (value === "") {
insValue = 1;
} else if (value === "*") {
insValue = 2;
} else if (value.indexOf("-") > -1) {
insValue = 3;
} else if (value.indexOf("/") > -1) {
insValue = 4;
} else {
this.$refs[refName].checkboxList = value.split(",");
insValue = 5;
}
}
this.$refs[refName].radioValue = insValue;
},
// -props
checkNumber(value, minLimit, maxLimit) {
//
value = Math.floor(value);
if (value < minLimit) {
value = minLimit;
} else if (value > maxLimit) {
value = maxLimit;
}
return value;
},
//
hidePopup() {
this.$emit("hide");
},
//
submitFill() {
this.$emit("fill", this.crontabValueString);
this.hidePopup();
},
clearCron() {
//
("准备还原");
this.crontabValueObj = {
second: "*",
min: "*",
hour: "*",
day: "*",
month: "*",
week: "?",
year: "",
};
for (let j in this.crontabValueObj) {
this.changeRadio(j, this.crontabValueObj[j]);
}
},
},
computed: {
crontabValueString: function() {
let obj = this.crontabValueObj;
let str =
obj.second +
" " +
obj.min +
" " +
obj.hour +
" " +
obj.day +
" " +
obj.month +
" " +
obj.week +
(obj.year === "" ? "" : " " + obj.year);
return str;
},
},
components: {
CrontabSecond,
CrontabMin,
CrontabHour,
CrontabDay,
CrontabMonth,
CrontabWeek,
CrontabYear,
CrontabResult,
},
watch: {
expression: "resolveExp",
hideComponent(value) {
//
},
},
mounted: function() {
this.resolveExp();
},
};
</script>
<style scoped>
.pop_btn {
text-align: center;
margin-top: 20px;
}
.popup-main {
position: relative;
margin: 10px auto;
background: #fff;
border-radius: 5px;
font-size: 12px;
overflow: hidden;
}
.popup-title {
overflow: hidden;
line-height: 34px;
padding-top: 6px;
background: #f2f2f2;
}
.popup-result {
box-sizing: border-box;
line-height: 24px;
margin: 25px auto;
padding: 15px 10px 10px;
border: 1px solid #ccc;
position: relative;
}
.popup-result .title {
position: absolute;
top: -28px;
left: 50%;
width: 140px;
font-size: 14px;
margin-left: -70px;
text-align: center;
line-height: 30px;
background: #fff;
}
.popup-result table {
text-align: center;
width: 100%;
margin: 0 auto;
}
.popup-result table span {
display: block;
width: 100%;
font-family: arial;
line-height: 30px;
height: 30px;
white-space: nowrap;
overflow: hidden;
border: 1px solid #e8e8e8;
}
.popup-result-scroll {
font-size: 12px;
line-height: 24px;
height: 10em;
overflow-y: auto;
}
</style>

View File

@ -0,0 +1,116 @@
<template>
<el-form size="small">
<el-form-item>
<el-radio v-model='radioValue' :label="1">
分钟允许的通配符[, - * /]
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="2">
周期从
<el-input-number v-model='cycle01' :min="0" :max="58" /> -
<el-input-number v-model='cycle02' :min="cycle01 ? cycle01 + 1 : 1" :max="59" /> 分钟
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="3">
<el-input-number v-model='average01' :min="0" :max="58" /> 分钟开始
<el-input-number v-model='average02' :min="1" :max="59 - average01 || 0" /> 分钟执行一次
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="4">
指定
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple style="width:100%">
<el-option v-for="item in 60" :key="item" :value="item-1">{{item-1}}</el-option>
</el-select>
</el-radio>
</el-form-item>
</el-form>
</template>
<script>
export default {
data() {
return {
radioValue: 1,
cycle01: 1,
cycle02: 2,
average01: 0,
average02: 1,
checkboxList: [],
checkNum: this.$options.propsData.check
}
},
name: 'crontab-min',
props: ['check', 'cron'],
methods: {
//
radioChange() {
switch (this.radioValue) {
case 1:
this.$emit('update', 'min', '*', 'min');
break;
case 2:
this.$emit('update', 'min', this.cycleTotal, 'min');
break;
case 3:
this.$emit('update', 'min', this.averageTotal, 'min');
break;
case 4:
this.$emit('update', 'min', this.checkboxString, 'min');
break;
}
},
//
cycleChange() {
if (this.radioValue === '2') {
this.$emit('update', 'min', this.cycleTotal, 'min');
}
},
//
averageChange() {
if (this.radioValue === '3') {
this.$emit('update', 'min', this.averageTotal, 'min');
}
},
// checkbox
checkboxChange() {
if (this.radioValue === '4') {
this.$emit('update', 'min', this.checkboxString, 'min');
}
},
},
watch: {
'radioValue': 'radioChange',
'cycleTotal': 'cycleChange',
'averageTotal': 'averageChange',
'checkboxString': 'checkboxChange',
},
computed: {
//
cycleTotal: function () {
const cycle01 = this.checkNum(this.cycle01, 0, 58)
const cycle02 = this.checkNum(this.cycle02, cycle01 ? cycle01 + 1 : 1, 59)
return cycle01 + '-' + cycle02;
},
//
averageTotal: function () {
const average01 = this.checkNum(this.average01, 0, 58)
const average02 = this.checkNum(this.average02, 1, 59 - average01 || 0)
return average01 + '/' + average02;
},
// checkbox
checkboxString: function () {
let str = this.checkboxList.join();
return str === '' ? '*' : str;
}
}
}
</script>

View File

@ -0,0 +1,114 @@
<template>
<el-form size='small'>
<el-form-item>
<el-radio v-model='radioValue' :label="1">
允许的通配符[, - * /]
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="2">
周期从
<el-input-number v-model='cycle01' :min="1" :max="11" /> -
<el-input-number v-model='cycle02' :min="cycle01 ? cycle01 + 1 : 2" :max="12" />
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="3">
<el-input-number v-model='average01' :min="1" :max="11" /> 月开始
<el-input-number v-model='average02' :min="1" :max="12 - average01 || 0" /> 月月执行一次
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="4">
指定
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple style="width:100%">
<el-option v-for="item in 12" :key="item" :value="item">{{item}}</el-option>
</el-select>
</el-radio>
</el-form-item>
</el-form>
</template>
<script>
export default {
data() {
return {
radioValue: 1,
cycle01: 1,
cycle02: 2,
average01: 1,
average02: 1,
checkboxList: [],
checkNum: this.check
}
},
name: 'crontab-month',
props: ['check', 'cron'],
methods: {
//
radioChange() {
switch (this.radioValue) {
case 1:
this.$emit('update', 'month', '*');
break;
case 2:
this.$emit('update', 'month', this.cycleTotal);
break;
case 3:
this.$emit('update', 'month', this.averageTotal);
break;
case 4:
this.$emit('update', 'month', this.checkboxString);
break;
}
},
//
cycleChange() {
if (this.radioValue === '2') {
this.$emit('update', 'month', this.cycleTotal);
}
},
//
averageChange() {
if (this.radioValue === '3') {
this.$emit('update', 'month', this.averageTotal);
}
},
// checkbox
checkboxChange() {
if (this.radioValue === '4') {
this.$emit('update', 'month', this.checkboxString);
}
}
},
watch: {
'radioValue': 'radioChange',
'cycleTotal': 'cycleChange',
'averageTotal': 'averageChange',
'checkboxString': 'checkboxChange'
},
computed: {
//
cycleTotal: function () {
const cycle01 = this.checkNum(this.cycle01, 1, 11)
const cycle02 = this.checkNum(this.cycle02, cycle01 ? cycle01 + 1 : 2, 12)
return cycle01 + '-' + cycle02;
},
//
averageTotal: function () {
const average01 = this.checkNum(this.average01, 1, 11)
const average02 = this.checkNum(this.average02, 1, 12 - average01 || 0)
return average01 + '/' + average02;
},
// checkbox
checkboxString: function () {
let str = this.checkboxList.join();
return str === '' ? '*' : str;
}
}
}
</script>

View File

@ -0,0 +1,559 @@
<template>
<div class="popup-result">
<p class="title">最近5次运行时间</p>
<ul class="popup-result-scroll">
<template v-if='isShow'>
<li v-for='item in resultList' :key="item">{{item}}</li>
</template>
<li v-else>计算结果中...</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
dayRule: '',
dayRuleSup: '',
dateArr: [],
resultList: [],
isShow: false
}
},
name: 'crontab-result',
methods: {
//
expressionChange() {
// -
this.isShow = false;
// [0123456]
let ruleArr = this.$options.propsData.ex.split(' ');
//
let nums = 0;
//
let resultArr = [];
// []
let nTime = new Date();
let nYear = nTime.getFullYear();
let nMonth = nTime.getMonth() + 1;
let nDay = nTime.getDate();
let nHour = nTime.getHours();
let nMin = nTime.getMinutes();
let nSecond = nTime.getSeconds();
// 100
this.getSecondArr(ruleArr[0]);
this.getMinArr(ruleArr[1]);
this.getHourArr(ruleArr[2]);
this.getDayArr(ruleArr[3]);
this.getMonthArr(ruleArr[4]);
this.getWeekArr(ruleArr[5]);
this.getYearArr(ruleArr[6], nYear);
// -便使
let sDate = this.dateArr[0];
let mDate = this.dateArr[1];
let hDate = this.dateArr[2];
let DDate = this.dateArr[3];
let MDate = this.dateArr[4];
let YDate = this.dateArr[5];
//
let sIdx = this.getIndex(sDate, nSecond);
let mIdx = this.getIndex(mDate, nMin);
let hIdx = this.getIndex(hDate, nHour);
let DIdx = this.getIndex(DDate, nDay);
let MIdx = this.getIndex(MDate, nMonth);
let YIdx = this.getIndex(YDate, nYear);
// ()
const resetSecond = function () {
sIdx = 0;
nSecond = sDate[sIdx]
}
const resetMin = function () {
mIdx = 0;
nMin = mDate[mIdx]
resetSecond();
}
const resetHour = function () {
hIdx = 0;
nHour = hDate[hIdx]
resetMin();
}
const resetDay = function () {
DIdx = 0;
nDay = DDate[DIdx]
resetHour();
}
const resetMonth = function () {
MIdx = 0;
nMonth = MDate[MIdx]
resetDay();
}
//
if (nYear !== YDate[YIdx]) {
resetMonth();
}
//
if (nMonth !== MDate[MIdx]) {
resetDay();
}
//
if (nDay !== DDate[DIdx]) {
resetHour();
}
//
if (nHour !== hDate[hIdx]) {
resetMin();
}
//
if (nMin !== mDate[mIdx]) {
resetSecond();
}
//
goYear: for (let Yi = YIdx; Yi < YDate.length; Yi++) {
let YY = YDate[Yi];
//
if (nMonth > MDate[MDate.length - 1]) {
resetMonth();
continue;
}
//
goMonth: for (let Mi = MIdx; Mi < MDate.length; Mi++) {
// 便
let MM = MDate[Mi];
MM = MM < 10 ? '0' + MM : MM;
//
if (nDay > DDate[DDate.length - 1]) {
resetDay();
if (Mi === MDate.length - 1) {
resetMonth();
continue goYear;
}
continue;
}
//
goDay: for (let Di = DIdx; Di < DDate.length; Di++) {
// 便
let DD = DDate[Di];
let thisDD = DD < 10 ? '0' + DD : DD;
//
if (nHour > hDate[hDate.length - 1]) {
resetHour();
if (Di === DDate.length - 1) {
resetDay();
if (Mi === MDate.length - 1) {
resetMonth();
continue goYear;
}
continue goMonth;
}
continue;
}
//
if (this.checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true && this.dayRule !== 'workDay' && this.dayRule !== 'lastWeek' && this.dayRule !== 'lastDay') {
resetDay();
continue goMonth;
}
//
if (this.dayRule === 'lastDay') {
//
if (this.checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) {
while (DD > 0 && this.checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) {
DD--;
thisDD = DD < 10 ? '0' + DD : DD;
}
}
} else if (this.dayRule === 'workDay') {
// 230
if (this.checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) {
while (DD > 0 && this.checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) {
DD--;
thisDD = DD < 10 ? '0' + DD : DD;
}
}
// X
let thisWeek = this.formatDate(new Date(YY + '-' + MM + '-' + thisDD + ' 00:00:00'), 'week');
//
if (thisWeek === 1) {
//
DD++;
thisDD = DD < 10 ? '0' + DD : DD;
//
if (this.checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) {
DD -= 3;
}
} else if (thisWeek === 7) {
// 61
if (this.dayRuleSup !== 1) {
DD--;
} else {
DD += 2;
}
}
} else if (this.dayRule === 'weekDay') {
//
//
let thisWeek = this.formatDate(new Date(YY + '-' + MM + '-' + DD + ' 00:00:00'), 'week');
// dayRuleSup
if (this.dayRuleSup.indexOf(thisWeek) < 0) {
//
if (Di === DDate.length - 1) {
resetDay();
if (Mi === MDate.length - 1) {
resetMonth();
continue goYear;
}
continue goMonth;
}
continue;
}
} else if (this.dayRule === 'assWeek') {
//
// 1
let thisWeek = this.formatDate(new Date(YY + '-' + MM + '-' + DD + ' 00:00:00'), 'week');
if (this.dayRuleSup[1] >= thisWeek) {
DD = (this.dayRuleSup[0] - 1) * 7 + this.dayRuleSup[1] - thisWeek + 1;
} else {
DD = this.dayRuleSup[0] * 7 + this.dayRuleSup[1] - thisWeek + 1;
}
} else if (this.dayRule === 'lastWeek') {
//
// 230
if (this.checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) {
while (DD > 0 && this.checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) {
DD--;
thisDD = DD < 10 ? '0' + DD : DD;
}
}
//
let thisWeek = this.formatDate(new Date(YY + '-' + MM + '-' + thisDD + ' 00:00:00'), 'week');
//
if (this.dayRuleSup < thisWeek) {
DD -= thisWeek - this.dayRuleSup;
} else if (this.dayRuleSup > thisWeek) {
DD -= 7 - (this.dayRuleSup - thisWeek)
}
}
// 1005
DD = DD < 10 ? '0' + DD : DD;
//
goHour: for (let hi = hIdx; hi < hDate.length; hi++) {
let hh = hDate[hi] < 10 ? '0' + hDate[hi] : hDate[hi]
//
if (nMin > mDate[mDate.length - 1]) {
resetMin();
if (hi === hDate.length - 1) {
resetHour();
if (Di === DDate.length - 1) {
resetDay();
if (Mi === MDate.length - 1) {
resetMonth();
continue goYear;
}
continue goMonth;
}
continue goDay;
}
continue;
}
// ""
goMin: for (let mi = mIdx; mi < mDate.length; mi++) {
let mm = mDate[mi] < 10 ? '0' + mDate[mi] : mDate[mi];
//
if (nSecond > sDate[sDate.length - 1]) {
resetSecond();
if (mi === mDate.length - 1) {
resetMin();
if (hi === hDate.length - 1) {
resetHour();
if (Di === DDate.length - 1) {
resetDay();
if (Mi === MDate.length - 1) {
resetMonth();
continue goYear;
}
continue goMonth;
}
continue goDay;
}
continue goHour;
}
continue;
}
// ""
goSecond: for (let si = sIdx; si <= sDate.length - 1; si++) {
let ss = sDate[si] < 10 ? '0' + sDate[si] : sDate[si];
//
if (MM !== '00' && DD !== '00') {
resultArr.push(YY + '-' + MM + '-' + DD + ' ' + hh + ':' + mm + ':' + ss)
nums++;
}
// 退
if (nums === 5) break goYear;
//
if (si === sDate.length - 1) {
resetSecond();
if (mi === mDate.length - 1) {
resetMin();
if (hi === hDate.length - 1) {
resetHour();
if (Di === DDate.length - 1) {
resetDay();
if (Mi === MDate.length - 1) {
resetMonth();
continue goYear;
}
continue goMonth;
}
continue goDay;
}
continue goHour;
}
continue goMin;
}
} //goSecond
} //goMin
}//goHour
}//goDay
}//goMonth
}
// 100
if (resultArr.length === 0) {
this.resultList = ['没有达到条件的结果!'];
} else {
this.resultList = resultArr;
if (resultArr.length !== 5) {
this.resultList.push('最近100年内只有上面' + resultArr.length + '条结果!')
}
}
// -
this.isShow = true;
},
//
getIndex(arr, value) {
if (value <= arr[0] || value > arr[arr.length - 1]) {
return 0;
} else {
for (let i = 0; i < arr.length - 1; i++) {
if (value > arr[i] && value <= arr[i + 1]) {
return i + 1;
}
}
}
},
// ""
getYearArr(rule, year) {
this.dateArr[5] = this.getOrderArr(year, year + 100);
if (rule !== undefined) {
if (rule.indexOf('-') >= 0) {
this.dateArr[5] = this.getCycleArr(rule, year + 100, false)
} else if (rule.indexOf('/') >= 0) {
this.dateArr[5] = this.getAverageArr(rule, year + 100)
} else if (rule !== '*') {
this.dateArr[5] = this.getAssignArr(rule)
}
}
},
// ""
getMonthArr(rule) {
this.dateArr[4] = this.getOrderArr(1, 12);
if (rule.indexOf('-') >= 0) {
this.dateArr[4] = this.getCycleArr(rule, 12, false)
} else if (rule.indexOf('/') >= 0) {
this.dateArr[4] = this.getAverageArr(rule, 12)
} else if (rule !== '*') {
this.dateArr[4] = this.getAssignArr(rule)
}
},
// ""-
getWeekArr(rule) {
//
if (this.dayRule === '' && this.dayRuleSup === '') {
if (rule.indexOf('-') >= 0) {
this.dayRule = 'weekDay';
this.dayRuleSup = this.getCycleArr(rule, 7, false)
} else if (rule.indexOf('#') >= 0) {
this.dayRule = 'assWeek';
let matchRule = rule.match(/[0-9]/g);
this.dayRuleSup = [Number(matchRule[1]), Number(matchRule[0])];
this.dateArr[3] = [1];
if (this.dayRuleSup[1] === 7) {
this.dayRuleSup[1] = 0;
}
} else if (rule.indexOf('L') >= 0) {
this.dayRule = 'lastWeek';
this.dayRuleSup = Number(rule.match(/[0-9]{1,2}/g)[0]);
this.dateArr[3] = [31];
if (this.dayRuleSup === 7) {
this.dayRuleSup = 0;
}
} else if (rule !== '*' && rule !== '?') {
this.dayRule = 'weekDay';
this.dayRuleSup = this.getAssignArr(rule)
}
}
},
// ""-
getDayArr(rule) {
this.dateArr[3] = this.getOrderArr(1, 31);
this.dayRule = '';
this.dayRuleSup = '';
if (rule.indexOf('-') >= 0) {
this.dateArr[3] = this.getCycleArr(rule, 31, false)
this.dayRuleSup = 'null';
} else if (rule.indexOf('/') >= 0) {
this.dateArr[3] = this.getAverageArr(rule, 31)
this.dayRuleSup = 'null';
} else if (rule.indexOf('W') >= 0) {
this.dayRule = 'workDay';
this.dayRuleSup = Number(rule.match(/[0-9]{1,2}/g)[0]);
this.dateArr[3] = [this.dayRuleSup];
} else if (rule.indexOf('L') >= 0) {
this.dayRule = 'lastDay';
this.dayRuleSup = 'null';
this.dateArr[3] = [31];
} else if (rule !== '*' && rule !== '?') {
this.dateArr[3] = this.getAssignArr(rule)
this.dayRuleSup = 'null';
} else if (rule === '*') {
this.dayRuleSup = 'null';
}
},
// ""
getHourArr(rule) {
this.dateArr[2] = this.getOrderArr(0, 23);
if (rule.indexOf('-') >= 0) {
this.dateArr[2] = this.getCycleArr(rule, 24, true)
} else if (rule.indexOf('/') >= 0) {
this.dateArr[2] = this.getAverageArr(rule, 23)
} else if (rule !== '*') {
this.dateArr[2] = this.getAssignArr(rule)
}
},
// ""
getMinArr(rule) {
this.dateArr[1] = this.getOrderArr(0, 59);
if (rule.indexOf('-') >= 0) {
this.dateArr[1] = this.getCycleArr(rule, 60, true)
} else if (rule.indexOf('/') >= 0) {
this.dateArr[1] = this.getAverageArr(rule, 59)
} else if (rule !== '*') {
this.dateArr[1] = this.getAssignArr(rule)
}
},
// ""
getSecondArr(rule) {
this.dateArr[0] = this.getOrderArr(0, 59);
if (rule.indexOf('-') >= 0) {
this.dateArr[0] = this.getCycleArr(rule, 60, true)
} else if (rule.indexOf('/') >= 0) {
this.dateArr[0] = this.getAverageArr(rule, 59)
} else if (rule !== '*') {
this.dateArr[0] = this.getAssignArr(rule)
}
},
// min-max
getOrderArr(min, max) {
let arr = [];
for (let i = min; i <= max; i++) {
arr.push(i);
}
return arr;
},
//
getAssignArr(rule) {
let arr = [];
let assiginArr = rule.split(',');
for (let i = 0; i < assiginArr.length; i++) {
arr[i] = Number(assiginArr[i])
}
arr.sort(this.compare)
return arr;
},
//
getAverageArr(rule, limit) {
let arr = [];
let agArr = rule.split('/');
let min = Number(agArr[0]);
let step = Number(agArr[1]);
while (min <= limit) {
arr.push(min);
min += step;
}
return arr;
},
//
getCycleArr(rule, limit, status) {
// status--01
let arr = [];
let cycleArr = rule.split('-');
let min = Number(cycleArr[0]);
let max = Number(cycleArr[1]);
if (min > max) {
max += limit;
}
for (let i = min; i <= max; i++) {
let add = 0;
if (status === false && i % limit === 0) {
add = limit;
}
arr.push(Math.round(i % limit + add))
}
arr.sort(this.compare)
return arr;
},
// Array.sort
compare(value1, value2) {
if (value2 - value1 > 0) {
return -1;
} else {
return 1;
}
},
// 2017-9-19 18:04:33
formatDate(value, type) {
//
let time = typeof value == 'number' ? new Date(value) : value;
let Y = time.getFullYear();
let M = time.getMonth() + 1;
let D = time.getDate();
let h = time.getHours();
let m = time.getMinutes();
let s = time.getSeconds();
let week = time.getDay();
// type
if (type === undefined) {
return Y + '-' + (M < 10 ? '0' + M : M) + '-' + (D < 10 ? '0' + D : D) + ' ' + (h < 10 ? '0' + h : h) + ':' + (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s);
} else if (type === 'week') {
// quartz 1
return week + 1;
}
},
//
checkDate(value) {
let time = new Date(value);
let format = this.formatDate(time)
return value === format;
}
},
watch: {
'ex': 'expressionChange'
},
props: ['ex'],
mounted: function () {
//
this.expressionChange();
}
}
</script>

View File

@ -0,0 +1,117 @@
<template>
<el-form size="small">
<el-form-item>
<el-radio v-model='radioValue' :label="1">
允许的通配符[, - * /]
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="2">
周期从
<el-input-number v-model='cycle01' :min="0" :max="58" /> -
<el-input-number v-model='cycle02' :min="cycle01 ? cycle01 + 1 : 1" :max="59" />
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="3">
<el-input-number v-model='average01' :min="0" :max="58" /> 秒开始
<el-input-number v-model='average02' :min="1" :max="59 - average01 || 0" /> 秒执行一次
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="4">
指定
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple style="width:100%">
<el-option v-for="item in 60" :key="item" :value="item-1">{{item-1}}</el-option>
</el-select>
</el-radio>
</el-form-item>
</el-form>
</template>
<script>
export default {
data() {
return {
radioValue: 1,
cycle01: 1,
cycle02: 2,
average01: 0,
average02: 1,
checkboxList: [],
checkNum: this.$options.propsData.check
}
},
name: 'crontab-second',
props: ['check', 'radioParent'],
methods: {
//
radioChange() {
switch (this.radioValue) {
case 1:
this.$emit('update', 'second', '*', 'second');
break;
case 2:
this.$emit('update', 'second', this.cycleTotal);
break;
case 3:
this.$emit('update', 'second', this.averageTotal);
break;
case 4:
this.$emit('update', 'second', this.checkboxString);
break;
}
},
//
cycleChange() {
if (this.radioValue === '2') {
this.$emit('update', 'second', this.cycleTotal);
}
},
//
averageChange() {
if (this.radioValue === '3') {
this.$emit('update', 'second', this.averageTotal);
}
},
// checkbox
checkboxChange() {
if (this.radioValue === '4') {
this.$emit('update', 'second', this.checkboxString);
}
}
},
watch: {
'radioValue': 'radioChange',
'cycleTotal': 'cycleChange',
'averageTotal': 'averageChange',
'checkboxString': 'checkboxChange',
radioParent() {
this.radioValue = this.radioParent
}
},
computed: {
//
cycleTotal: function () {
const cycle01 = this.checkNum(this.cycle01, 0, 58)
const cycle02 = this.checkNum(this.cycle02, cycle01 ? cycle01 + 1 : 1, 59)
return cycle01 + '-' + cycle02;
},
//
averageTotal: function () {
const average01 = this.checkNum(this.average01, 0, 58)
const average02 = this.checkNum(this.average02, 1, 59 - average01 || 0)
return average01 + '/' + average02;
},
// checkbox
checkboxString: function () {
let str = this.checkboxList.join();
return str === '' ? '*' : str;
}
}
}
</script>

View File

@ -0,0 +1,202 @@
<template>
<el-form size='small'>
<el-form-item>
<el-radio v-model='radioValue' :label="1">
允许的通配符[, - * ? / L #]
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="2">
不指定
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="3">
周期从星期
<el-select clearable v-model="cycle01">
<el-option
v-for="(item,index) of weekList"
:key="index"
:label="item.value"
:value="item.key"
:disabled="item.key === 1"
>{{item.value}}</el-option>
</el-select>
-
<el-select clearable v-model="cycle02">
<el-option
v-for="(item,index) of weekList"
:key="index"
:label="item.value"
:value="item.key"
:disabled="item.key < cycle01 && item.key !== 1"
>{{item.value}}</el-option>
</el-select>
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="4">
<el-input-number v-model='average01' :min="1" :max="4" /> 周的星期
<el-select clearable v-model="average02">
<el-option v-for="(item,index) of weekList" :key="index" :label="item.value" :value="item.key">{{item.value}}</el-option>
</el-select>
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="5">
本月最后一个星期
<el-select clearable v-model="weekday">
<el-option v-for="(item,index) of weekList" :key="index" :label="item.value" :value="item.key">{{item.value}}</el-option>
</el-select>
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="6">
指定
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple style="width:100%">
<el-option v-for="(item,index) of weekList" :key="index" :label="item.value" :value="String(item.key)">{{item.value}}</el-option>
</el-select>
</el-radio>
</el-form-item>
</el-form>
</template>
<script>
export default {
data() {
return {
radioValue: 2,
weekday: 2,
cycle01: 2,
cycle02: 3,
average01: 1,
average02: 2,
checkboxList: [],
weekList: [
{
key: 2,
value: '星期一'
},
{
key: 3,
value: '星期二'
},
{
key: 4,
value: '星期三'
},
{
key: 5,
value: '星期四'
},
{
key: 6,
value: '星期五'
},
{
key: 7,
value: '星期六'
},
{
key: 1,
value: '星期日'
}
],
checkNum: this.$options.propsData.check
}
},
name: 'crontab-week',
props: ['check', 'cron'],
methods: {
//
radioChange() {
if (this.radioValue !== 2 && this.cron.day !== '?') {
this.$emit('update', 'day', '?', 'week');
}
switch (this.radioValue) {
case 1:
this.$emit('update', 'week', '*');
break;
case 2:
this.$emit('update', 'week', '?');
break;
case 3:
this.$emit('update', 'week', this.cycleTotal);
break;
case 4:
this.$emit('update', 'week', this.averageTotal);
break;
case 5:
this.$emit('update', 'week', this.weekdayCheck + 'L');
break;
case 6:
this.$emit('update', 'week', this.checkboxString);
break;
}
},
//
cycleChange() {
if (this.radioValue === '3') {
this.$emit('update', 'week', this.cycleTotal);
}
},
//
averageChange() {
if (this.radioValue === '4') {
this.$emit('update', 'week', this.averageTotal);
}
},
//
weekdayChange() {
if (this.radioValue === '5') {
this.$emit('update', 'week', this.weekday + 'L');
}
},
// checkbox
checkboxChange() {
if (this.radioValue === '6') {
this.$emit('update', 'week', this.checkboxString);
}
},
},
watch: {
'radioValue': 'radioChange',
'cycleTotal': 'cycleChange',
'averageTotal': 'averageChange',
'weekdayCheck': 'weekdayChange',
'checkboxString': 'checkboxChange',
},
computed: {
//
cycleTotal: function () {
this.cycle01 = this.checkNum(this.cycle01, 1, 7)
this.cycle02 = this.checkNum(this.cycle02, 1, 7)
return this.cycle01 + '-' + this.cycle02;
},
//
averageTotal: function () {
this.average01 = this.checkNum(this.average01, 1, 4)
this.average02 = this.checkNum(this.average02, 1, 7)
return this.average02 + '#' + this.average01;
},
//
weekdayCheck: function () {
this.weekday = this.checkNum(this.weekday, 1, 7)
return this.weekday;
},
// checkbox
checkboxString: function () {
let str = this.checkboxList.join();
return str === '' ? '*' : str;
}
}
}
</script>

View File

@ -0,0 +1,131 @@
<template>
<el-form size="small">
<el-form-item>
<el-radio :label="1" v-model='radioValue'>
不填允许的通配符[, - * /]
</el-radio>
</el-form-item>
<el-form-item>
<el-radio :label="2" v-model='radioValue'>
每年
</el-radio>
</el-form-item>
<el-form-item>
<el-radio :label="3" v-model='radioValue'>
周期从
<el-input-number v-model='cycle01' :min='fullYear' :max="2098" /> -
<el-input-number v-model='cycle02' :min="cycle01 ? cycle01 + 1 : fullYear + 1" :max="2099" />
</el-radio>
</el-form-item>
<el-form-item>
<el-radio :label="4" v-model='radioValue'>
<el-input-number v-model='average01' :min='fullYear' :max="2098"/> 年开始
<el-input-number v-model='average02' :min="1" :max="2099 - average01 || fullYear" /> 年执行一次
</el-radio>
</el-form-item>
<el-form-item>
<el-radio :label="5" v-model='radioValue'>
指定
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple>
<el-option v-for="item in 9" :key="item" :value="item - 1 + fullYear" :label="item -1 + fullYear" />
</el-select>
</el-radio>
</el-form-item>
</el-form>
</template>
<script>
export default {
data() {
return {
fullYear: 0,
radioValue: 1,
cycle01: 0,
cycle02: 0,
average01: 0,
average02: 1,
checkboxList: [],
checkNum: this.$options.propsData.check
}
},
name: 'crontab-year',
props: ['check', 'month', 'cron'],
methods: {
//
radioChange() {
switch (this.radioValue) {
case 1:
this.$emit('update', 'year', '');
break;
case 2:
this.$emit('update', 'year', '*');
break;
case 3:
this.$emit('update', 'year', this.cycleTotal);
break;
case 4:
this.$emit('update', 'year', this.averageTotal);
break;
case 5:
this.$emit('update', 'year', this.checkboxString);
break;
}
},
//
cycleChange() {
if (this.radioValue === '3') {
this.$emit('update', 'year', this.cycleTotal);
}
},
//
averageChange() {
if (this.radioValue === '4') {
this.$emit('update', 'year', this.averageTotal);
}
},
// checkbox
checkboxChange() {
if (this.radioValue === '5') {
this.$emit('update', 'year', this.checkboxString);
}
}
},
watch: {
'radioValue': 'radioChange',
'cycleTotal': 'cycleChange',
'averageTotal': 'averageChange',
'checkboxString': 'checkboxChange'
},
computed: {
//
cycleTotal: function () {
const cycle01 = this.checkNum(this.cycle01, this.fullYear, 2098)
const cycle02 = this.checkNum(this.cycle02, cycle01 ? cycle01 + 1 : this.fullYear + 1, 2099)
return cycle01 + '-' + cycle02;
},
//
averageTotal: function () {
const average01 = this.checkNum(this.average01, this.fullYear, 2098)
const average02 = this.checkNum(this.average02, 1, 2099 - average01 || this.fullYear)
return average01 + '/' + average02;
},
// checkbox
checkboxString: function () {
let str = this.checkboxList.join();
return str;
}
},
mounted: function () {
//
this.fullYear = Number(new Date().getFullYear());
this.cycle01 = this.fullYear
this.average01 = this.fullYear
}
}
</script>

View File

@ -0,0 +1,630 @@
// 表单属性【右面板】
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 = [
{
// 组件的自定义配置
__config__: {
label: '单行文本',
labelWidth: null,
showLabel: true,
changeTag: true,
tag: 'el-input',
tagIcon: 'input',
defaultValue: undefined,
required: true,
layout: 'colFormItem',
span: 24,
document: 'https://element.eleme.cn/#/zh-CN/component/input',
// 正则校验规则
regList: []
},
// 组件的插槽属性
__slot__: {
prepend: '',
append: ''
},
// 其余的为可直接写在组件标签上的属性
placeholder: '请输入',
style: { width: '100%' },
clearable: true,
'prefix-icon': '',
'suffix-icon': '',
maxlength: null,
'show-word-limit': false,
readonly: false,
disabled: false
},
{
__config__: {
label: '多行文本',
labelWidth: null,
showLabel: true,
tag: 'el-input',
tagIcon: 'textarea',
defaultValue: undefined,
required: true,
layout: 'colFormItem',
span: 24,
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/input'
},
type: 'textarea',
placeholder: '请输入',
autosize: {
minRows: 4,
maxRows: 4
},
style: { width: '100%' },
maxlength: null,
'show-word-limit': false,
readonly: false,
disabled: false
},
{
__config__: {
label: '密码',
showLabel: true,
labelWidth: null,
changeTag: true,
tag: 'el-input',
tagIcon: 'password',
defaultValue: undefined,
layout: 'colFormItem',
span: 24,
required: true,
regList: [],
document: 'https://element.eleme.cn/#/zh-CN/component/input'
},
__slot__: {
prepend: '',
append: ''
},
placeholder: '请输入',
'show-password': true,
style: { width: '100%' },
clearable: true,
'prefix-icon': '',
'suffix-icon': '',
maxlength: null,
'show-word-limit': false,
readonly: false,
disabled: false
},
{
__config__: {
label: '计数器',
showLabel: true,
changeTag: true,
labelWidth: null,
tag: 'el-input-number',
tagIcon: 'number',
defaultValue: undefined,
span: 24,
layout: 'colFormItem',
required: true,
regList: [],
document: 'https://element.eleme.cn/#/zh-CN/component/input-number'
},
placeholder: '',
min: undefined,
max: undefined,
step: 1,
'step-strictly': false,
precision: undefined,
'controls-position': '',
disabled: false
},
{
__config__: {
label: '编辑器',
showLabel: true,
changeTag: true,
labelWidth: null,
tag: 'tinymce',
tagIcon: 'rich-text',
defaultValue: null,
span: 24,
layout: 'colFormItem',
required: true,
regList: [],
document: 'http://tinymce.ax-z.cn'
},
placeholder: '请输入',
height: 300, // 编辑器高度
branding: false // 隐藏右下角品牌烙印
}
]
// 选择型组件 【左面板】
export const selectComponents = [
{
__config__: {
label: '下拉选择',
showLabel: true,
labelWidth: null,
tag: 'el-select',
tagIcon: 'select',
layout: 'colFormItem',
span: 24,
required: true,
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/select'
},
__slot__: {
options: [{
label: '选项一',
value: 1
}, {
label: '选项二',
value: 2
}]
},
placeholder: '请选择',
style: { width: '100%' },
clearable: true,
disabled: false,
filterable: false,
multiple: false
},
{
__config__: {
label: '级联选择',
url: 'https://www.fastmock.site/mock/f8d7a54fb1e60561e2f720d5a810009d/fg/cascaderList',
method: 'get',
dataPath: 'list',
dataConsumer: 'options',
showLabel: true,
labelWidth: null,
tag: 'el-cascader',
tagIcon: 'cascader',
layout: 'colFormItem',
defaultValue: [],
dataType: 'dynamic',
span: 24,
required: true,
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/cascader'
},
options: [{
id: 1,
value: 1,
label: '选项1',
children: [{
id: 2,
value: 2,
label: '选项1-1'
}]
}],
placeholder: '请选择',
style: { width: '100%' },
props: {
props: {
multiple: false,
label: 'label',
value: 'value',
children: 'children'
}
},
'show-all-levels': true,
disabled: false,
clearable: true,
filterable: false,
separator: '/'
},
{
__config__: {
label: '单选框组',
labelWidth: null,
showLabel: true,
tag: 'el-radio-group',
tagIcon: 'radio',
changeTag: true,
defaultValue: undefined,
layout: 'colFormItem',
span: 24,
optionType: 'default',
regList: [],
required: true,
border: false,
document: 'https://element.eleme.cn/#/zh-CN/component/radio'
},
__slot__: {
options: [{
label: '选项一',
value: 1
}, {
label: '选项二',
value: 2
}]
},
style: {},
size: 'medium',
disabled: false
},
{
__config__: {
label: '多选框组',
tag: 'el-checkbox-group',
tagIcon: 'checkbox',
defaultValue: [],
span: 24,
showLabel: true,
labelWidth: null,
layout: 'colFormItem',
optionType: 'default',
required: true,
regList: [],
changeTag: true,
border: false,
document: 'https://element.eleme.cn/#/zh-CN/component/checkbox'
},
__slot__: {
options: [{
label: '选项一',
value: 1
}, {
label: '选项二',
value: 2
}]
},
style: {},
size: 'medium',
min: null,
max: null,
disabled: false
},
{
__config__: {
label: '开关',
tag: 'el-switch',
tagIcon: 'switch',
defaultValue: false,
span: 24,
showLabel: true,
labelWidth: null,
layout: 'colFormItem',
required: true,
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/switch'
},
style: {},
disabled: false,
'active-text': '',
'inactive-text': '',
'active-color': null,
'inactive-color': null,
'active-value': true,
'inactive-value': false
},
{
__config__: {
label: '滑块',
tag: 'el-slider',
tagIcon: 'slider',
defaultValue: null,
span: 24,
showLabel: true,
layout: 'colFormItem',
labelWidth: null,
required: true,
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/slider'
},
disabled: false,
min: 0,
max: 100,
step: 1,
'show-stops': false,
range: false
},
{
__config__: {
label: '时间选择',
tag: 'el-time-picker',
tagIcon: 'time',
defaultValue: null,
span: 24,
showLabel: true,
layout: 'colFormItem',
labelWidth: null,
required: true,
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/time-picker'
},
placeholder: '请选择',
style: { width: '100%' },
disabled: false,
clearable: true,
'picker-options': {
selectableRange: '00:00:00-23:59:59'
},
format: 'HH:mm:ss',
'value-format': 'HH:mm:ss'
},
{
__config__: {
label: '时间范围',
tag: 'el-time-picker',
tagIcon: 'time-range',
span: 24,
showLabel: true,
labelWidth: null,
layout: 'colFormItem',
defaultValue: null,
required: true,
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/time-picker'
},
style: { width: '100%' },
disabled: false,
clearable: true,
'is-range': true,
'range-separator': '至',
'start-placeholder': '开始时间',
'end-placeholder': '结束时间',
format: 'HH:mm:ss',
'value-format': 'HH:mm:ss'
},
{
__config__: {
label: '日期选择',
tag: 'el-date-picker',
tagIcon: 'date',
defaultValue: null,
showLabel: true,
labelWidth: null,
span: 24,
layout: 'colFormItem',
required: true,
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/date-picker'
},
placeholder: '请选择',
type: 'date',
style: { width: '100%' },
disabled: false,
clearable: true,
format: 'yyyy-MM-dd',
'value-format': 'yyyy-MM-dd',
readonly: false
},
{
__config__: {
label: '日期范围',
tag: 'el-date-picker',
tagIcon: 'date-range',
defaultValue: null,
span: 24,
showLabel: true,
labelWidth: null,
required: true,
layout: 'colFormItem',
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/date-picker'
},
style: { width: '100%' },
type: 'daterange',
'range-separator': '至',
'start-placeholder': '开始日期',
'end-placeholder': '结束日期',
disabled: false,
clearable: true,
format: 'yyyy-MM-dd',
'value-format': 'yyyy-MM-dd',
readonly: false
},
{
__config__: {
label: '评分',
tag: 'el-rate',
tagIcon: 'rate',
defaultValue: 0,
span: 24,
showLabel: true,
labelWidth: null,
layout: 'colFormItem',
required: true,
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/rate'
},
style: {},
max: 5,
'allow-half': false,
'show-text': false,
'show-score': false,
disabled: false
},
{
__config__: {
label: '颜色选择',
tag: 'el-color-picker',
tagIcon: 'color',
span: 24,
defaultValue: null,
showLabel: true,
labelWidth: null,
layout: 'colFormItem',
required: true,
regList: [],
changeTag: true,
document: 'https://element.eleme.cn/#/zh-CN/component/color-picker'
},
'show-alpha': false,
'color-format': '',
disabled: false,
size: 'medium'
},
{
__config__: {
label: '上传',
tag: 'el-upload',
tagIcon: 'upload',
layout: 'colFormItem',
defaultValue: null,
showLabel: true,
labelWidth: null,
required: true,
span: 24,
showTip: false,
buttonText: '点击上传',
regList: [],
changeTag: true,
fileSize: 2,
sizeUnit: 'MB',
document: 'https://element.eleme.cn/#/zh-CN/component/upload'
},
__slot__: {
'list-type': true
},
// action: process.env.VUE_APP_BASE_API + "/admin-api/infra/file/upload", // 请求地址
action: '/infra/file/upload', // 请求地址
disabled: false,
accept: '',
name: 'file',
'auto-upload': true,
'list-type': 'text',
multiple: false
}
]
// 布局型组件 【左面板】
export const layoutComponents = [
{
__config__: {
layout: 'rowFormItem',
tagIcon: 'row',
label: '行容器',
layoutTree: true,
document: 'https://element.eleme.cn/#/zh-CN/component/layout#row-attributes'
},
type: 'default',
justify: 'start',
align: 'top'
},
{
__config__: {
label: '按钮',
showLabel: true,
changeTag: true,
labelWidth: null,
tag: 'el-button',
tagIcon: 'button',
span: 24,
layout: 'colFormItem',
document: 'https://element.eleme.cn/#/zh-CN/component/button'
},
__slot__: {
default: '主要按钮'
},
type: 'primary',
icon: 'el-icon-search',
round: false,
size: 'medium',
plain: false,
circle: false,
disabled: false
},
{
__config__: {
layout: 'colFormItem',
tagIcon: 'table',
tag: 'el-table',
document: 'https://element.eleme.cn/#/zh-CN/component/table',
span: 24,
formId: 101,
renderKey: 1595761764203,
componentName: 'row101',
showLabel: true,
changeTag: true,
labelWidth: null,
label: '表格[开发中]',
dataType: 'dynamic',
method: 'get',
dataPath: 'list',
dataConsumer: 'data',
url: 'https://www.fastmock.site/mock/f8d7a54fb1e60561e2f720d5a810009d/fg/tableData',
children: [{
__config__: {
layout: 'raw',
tag: 'el-table-column',
renderKey: 15957617660153
},
prop: 'date',
label: '日期'
}, {
__config__: {
layout: 'raw',
tag: 'el-table-column',
renderKey: 15957617660152
},
prop: 'address',
label: '地址'
}, {
__config__: {
layout: 'raw',
tag: 'el-table-column',
renderKey: 15957617660151
},
prop: 'name',
label: '名称'
}, {
__config__: {
layout: 'raw',
tag: 'el-table-column',
renderKey: 1595774496335,
children: [
{
__config__: {
label: '按钮',
tag: 'el-button',
tagIcon: 'button',
layout: 'raw',
renderKey: 1595779809901
},
__slot__: {
default: '主要按钮'
},
type: 'primary',
icon: 'el-icon-search',
round: false,
size: 'medium'
}
]
},
label: '操作'
}]
},
data: [],
directives: [{
name: 'loading',
value: true
}],
border: true,
type: 'default',
justify: 'start',
align: 'top'
}
]

View File

@ -0,0 +1,18 @@
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.__config__.tag]
css && cssList.indexOf(css) === -1 && cssList.push(css)
if (el.__config__.children) {
el.__config__.children.forEach(el2 => addCss(cssList, el2))
}
}
export function makeUpCss(conf) {
const cssList = []
conf.fields.forEach(el => addCss(cssList, el))
return cssList.join('\n')
}

View File

@ -0,0 +1,37 @@
export default [
{
__config__: {
label: '单行文本',
labelWidth: null,
showLabel: true,
changeTag: true,
tag: 'el-input',
tagIcon: 'input',
defaultValue: undefined,
required: true,
layout: 'colFormItem',
span: 24,
document: 'https://element.eleme.cn/#/zh-CN/component/input',
// 正则校验规则
regList: [{
pattern: '/^1(3|4|5|7|8|9)\\d{9}$/',
message: '手机号格式错误'
}]
},
// 组件的插槽属性
__slot__: {
prepend: '',
append: ''
},
__vModel__: 'mobile',
placeholder: '请输入手机号',
style: { width: '100%' },
clearable: true,
'prefix-icon': 'el-icon-mobile',
'suffix-icon': '',
maxlength: 11,
'show-word-limit': true,
readonly: false,
disabled: false
}
]

View File

@ -0,0 +1,399 @@
/* eslint-disable max-len */
import ruleTrigger from './ruleTrigger'
let confGlobal
let someSpanIsNot24
export function dialogWrapper(str) {
return `<el-dialog v-bind="$attrs" v-on="$listeners" @open="onOpen" @close="onClose" title="Dialog Titile">
${str}
<div slot="footer">
<el-button @click="close">取消</el-button>
<el-button type="primary" @click="handelConfirm">确定</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(scheme, child, type) {
let labelPosition = ''
if (scheme.labelPosition !== 'right') {
labelPosition = `label-position="${scheme.labelPosition}"`
}
const disabled = scheme.disabled ? `:disabled="${scheme.disabled}"` : ''
let str = `<el-form ref="${scheme.formRef}" :model="${scheme.formModel}" :rules="${scheme.formRules}" size="${scheme.size}" ${disabled} label-width="${scheme.labelWidth}px" ${labelPosition}>
${child}
${buildFromBtns(scheme, type)}
</el-form>`
if (someSpanIsNot24) {
str = `<el-row :gutter="${scheme.gutter}">
${str}
</el-row>`
}
return str
}
function buildFromBtns(scheme, type) {
let str = ''
if (scheme.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(scheme, str) {
if (someSpanIsNot24 || scheme.__config__.span !== 24) {
return `<el-col :span="${scheme.__config__.span}">
${str}
</el-col>`
}
return str
}
const layouts = {
colFormItem(scheme) {
const config = scheme.__config__
let labelWidth = ''
let label = `label="${config.label}"`
if (config.labelWidth && config.labelWidth !== confGlobal.labelWidth) {
labelWidth = `label-width="${config.labelWidth}px"`
}
if (config.showLabel === false) {
labelWidth = 'label-width="0"'
label = ''
}
const required = !ruleTrigger[config.tag] && config.required ? 'required' : ''
const tagDom = tags[config.tag] ? tags[config.tag](scheme) : null
let str = `<el-form-item ${labelWidth} ${label} prop="${scheme.__vModel__}" ${required}>
${tagDom}
</el-form-item>`
str = colWrapper(scheme, str)
return str
},
rowFormItem(scheme) {
const config = scheme.__config__
const type = scheme.type === 'default' ? '' : `type="${scheme.type}"`
const justify = scheme.type === 'default' ? '' : `justify="${scheme.justify}"`
const align = scheme.type === 'default' ? '' : `align="${scheme.align}"`
const gutter = scheme.gutter ? `:gutter="${scheme.gutter}"` : ''
const children = config.children.map(el => layouts[el.__config__.layout](el))
let str = `<el-row ${type} ${justify} ${align} ${gutter}>
${children.join('\n')}
</el-row>`
str = colWrapper(scheme, str)
return str
}
}
const tags = {
'el-button': el => {
const {
tag, disabled
} = attrBuilder(el)
const type = el.type ? `type="${el.type}"` : ''
const icon = el.icon ? `icon="${el.icon}"` : ''
const round = el.round ? 'round' : ''
const size = el.size ? `size="${el.size}"` : ''
const plain = el.plain ? 'plain' : ''
const circle = el.circle ? 'circle' : ''
let child = buildElButtonChild(el)
if (child) child = `\n${child}\n` // 换行
return `<${tag} ${type} ${icon} ${round} ${size} ${plain} ${disabled} ${circle}>${child}</${tag}>`
},
'el-input': el => {
const {
tag, 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 `<${tag} ${vModel} ${type} ${placeholder} ${maxlength} ${showWordLimit} ${readonly} ${disabled} ${clearable} ${prefixIcon} ${suffixIcon} ${showPassword} ${autosize} ${width}>${child}</${tag}>`
},
'el-input-number': el => {
const {
tag, 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 `<${tag} ${vModel} ${placeholder} ${step} ${stepStrictly} ${precision} ${controlsPosition} ${min} ${max} ${disabled}></${tag}>`
},
'el-select': el => {
const {
tag, 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 `<${tag} ${vModel} ${placeholder} ${disabled} ${multiple} ${filterable} ${clearable} ${width}>${child}</${tag}>`
},
'el-radio-group': el => {
const { tag, disabled, vModel } = attrBuilder(el)
const size = `size="${el.size}"`
let child = buildElRadioGroupChild(el)
if (child) child = `\n${child}\n` // 换行
return `<${tag} ${vModel} ${size} ${disabled}>${child}</${tag}>`
},
'el-checkbox-group': el => {
const { tag, 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 `<${tag} ${vModel} ${min} ${max} ${size} ${disabled}>${child}</${tag}>`
},
'el-switch': el => {
const { tag, 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 `<${tag} ${vModel} ${activeText} ${inactiveText} ${activeColor} ${inactiveColor} ${activeValue} ${inactiveValue} ${disabled}></${tag}>`
},
'el-cascader': el => {
const {
tag, 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 `<${tag} ${vModel} ${options} ${props} ${width} ${showAllLevels} ${placeholder} ${separator} ${filterable} ${clearable} ${disabled}></${tag}>`
},
'el-slider': el => {
const { tag, 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 `<${tag} ${min} ${max} ${step} ${vModel} ${range} ${showStops} ${disabled}></${tag}>`
},
'el-time-picker': el => {
const {
tag, 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 `<${tag} ${vModel} ${isRange} ${format} ${valueFormat} ${pickerOptions} ${width} ${placeholder} ${startPlaceholder} ${endPlaceholder} ${rangeSeparator} ${clearable} ${disabled}></${tag}>`
},
'el-date-picker': el => {
const {
tag, 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 `<${tag} ${type} ${vModel} ${format} ${valueFormat} ${width} ${placeholder} ${startPlaceholder} ${endPlaceholder} ${rangeSeparator} ${clearable} ${readonly} ${disabled}></${tag}>`
},
'el-rate': el => {
const { tag, 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 `<${tag} ${vModel} ${max} ${allowHalf} ${showText} ${showScore} ${disabled}></${tag}>`
},
'el-color-picker': el => {
const { tag, 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 `<${tag} ${vModel} ${size} ${showAlpha} ${colorFormat} ${disabled}></${tag}>`
},
'el-upload': el => {
const { tag } = el.__config__
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 `<${tag} ${ref} ${fileList} ${action} ${autoUpload} ${multiple} ${beforeUpload} ${listType} ${accept} ${name} ${disabled}>${child}</${tag}>`
},
tinymce: el => {
const { tag, vModel, placeholder } = attrBuilder(el)
const height = el.height ? `:height="${el.height}"` : ''
const branding = el.branding ? `:branding="${el.branding}"` : ''
return `<${tag} ${vModel} ${placeholder} ${height} ${branding}></${tag}>`
}
}
function attrBuilder(el) {
return {
tag: el.__config__.tag,
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(scheme) {
const children = []
const slot = scheme.__slot__ || {}
if (slot.default) {
children.push(slot.default)
}
return children.join('\n')
}
// el-input 子级
function buildElInputChild(scheme) {
const children = []
const slot = scheme.__slot__
if (slot && slot.prepend) {
children.push(`<template slot="prepend">${slot.prepend}</template>`)
}
if (slot && slot.append) {
children.push(`<template slot="append">${slot.append}</template>`)
}
return children.join('\n')
}
// el-select 子级
function buildElSelectChild(scheme) {
const children = []
const slot = scheme.__slot__
if (slot && slot.options && slot.options.length) {
children.push(`<el-option v-for="(item, index) in ${scheme.__vModel__}Options" :key="index" :label="item.label" :value="item.value" :disabled="item.disabled"></el-option>`)
}
return children.join('\n')
}
// el-radio-group 子级
function buildElRadioGroupChild(scheme) {
const children = []
const slot = scheme.__slot__
const config = scheme.__config__
if (slot && slot.options && slot.options.length) {
const tag = config.optionType === 'button' ? 'el-radio-button' : 'el-radio'
const border = config.border ? 'border' : ''
children.push(`<${tag} v-for="(item, index) in ${scheme.__vModel__}Options" :key="index" :label="item.value" :disabled="item.disabled" ${border}>{{item.label}}</${tag}>`)
}
return children.join('\n')
}
// el-checkbox-group 子级
function buildElCheckboxGroupChild(scheme) {
const children = []
const slot = scheme.__slot__
const config = scheme.__config__
if (slot && slot.options && slot.options.length) {
const tag = config.optionType === 'button' ? 'el-checkbox-button' : 'el-checkbox'
const border = config.border ? 'border' : ''
children.push(`<${tag} v-for="(item, index) in ${scheme.__vModel__}Options" :key="index" :label="item.value" :disabled="item.disabled" ${border}>{{item.label}}</${tag}>`)
}
return children.join('\n')
}
// el-upload 子级
function buildElUploadChild(scheme) {
const list = []
const config = scheme.__config__
if (scheme['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">${config.buttonText}</el-button>`)
if (config.showTip) list.push(`<div slot="tip" class="el-upload__tip">只能上传不超过 ${config.fileSize}${config.sizeUnit}${scheme.accept}文件</div>`)
return list.join('\n')
}
/**
* 组装html代码入口函数
* @param {Object} formConfig 整个表单配置
* @param {String} type 生成类型文件或弹窗等
*/
export function makeUpHtml(formConfig, type) {
const htmlList = []
confGlobal = formConfig
// 判断布局是否都沾满了24个栅格以备后续简化代码结构
someSpanIsNot24 = formConfig.fields.some(item => item.__config__.span !== 24)
// 遍历渲染每个组件成html
formConfig.fields.forEach(el => {
htmlList.push(layouts[el.__config__.layout](el))
})
const htmlStr = htmlList.join('\n')
// 将组件代码放进form标签
let temp = buildFormTemplate(formConfig, htmlStr, type)
// dialog标签包裹代码
if (type === 'dialog') {
temp = dialogWrapper(temp)
}
confGlobal = null
return temp
}

View File

@ -0,0 +1,271 @@
import { isArray } from 'util'
import { exportDefault, titleCase, deepClone } from '@/utils'
import ruleTrigger from './ruleTrigger'
const units = {
KB: '1024',
MB: '1024 / 1024',
GB: '1024 / 1024 / 1024'
}
let confGlobal
const inheritAttrs = {
file: '',
dialog: 'inheritAttrs: false,'
}
/**
* 组装js 入口函数
* @param {Object} formConfig 整个表单配置
* @param {String} type 生成类型文件或弹窗等
*/
export function makeUpJs(formConfig, type) {
confGlobal = formConfig = deepClone(formConfig)
const dataList = []
const ruleList = []
const optionsList = []
const propsList = []
const methodList = mixinMethod(type)
const uploadVarList = []
const created = []
formConfig.fields.forEach(el => {
buildAttributes(el, dataList, ruleList, optionsList, methodList, propsList, uploadVarList, created)
})
const script = buildexport(
formConfig,
type,
dataList.join('\n'),
ruleList.join('\n'),
optionsList.join('\n'),
uploadVarList.join('\n'),
propsList.join('\n'),
methodList.join('\n'),
created.join('\n')
)
confGlobal = null
return script
}
// 构建组件属性
function buildAttributes(scheme, dataList, ruleList, optionsList, methodList, propsList, uploadVarList, created) {
const config = scheme.__config__
const slot = scheme.__slot__
buildData(scheme, dataList)
buildRules(scheme, ruleList)
// 特殊处理options属性
if (scheme.options || (slot && slot.options && slot.options.length)) {
buildOptions(scheme, optionsList)
if (config.dataType === 'dynamic') {
const model = `${scheme.__vModel__}Options`
const options = titleCase(model)
const methodName = `get${options}`
buildOptionMethod(methodName, model, methodList, scheme)
callInCreated(methodName, created)
}
}
// 处理props
if (scheme.props && scheme.props.props) {
buildProps(scheme, propsList)
}
// 处理el-upload的action
if (scheme.action && config.tag === 'el-upload') {
uploadVarList.push(
`${scheme.__vModel__}Action: '${scheme.action}',
${scheme.__vModel__}fileList: [],`
)
methodList.push(buildBeforeUpload(scheme))
// 非自动上传时,生成手动上传的函数
if (!scheme['auto-upload']) {
methodList.push(buildSubmitUpload(scheme))
}
}
// 构建子级组件属性
if (config.children) {
config.children.forEach(item => {
buildAttributes(item, dataList, ruleList, optionsList, methodList, propsList, uploadVarList, created)
})
}
}
// 在Created调用函数
function callInCreated(methodName, created) {
created.push(`this.${methodName}()`)
}
// 混入处理函数
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)
},`,
handelConfirm: `handelConfirm() {
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
}
// 构建data
function buildData(scheme, dataList) {
const config = scheme.__config__
if (scheme.__vModel__ === undefined) return
const defaultValue = JSON.stringify(config.defaultValue)
dataList.push(`${scheme.__vModel__}: ${defaultValue},`)
}
// 构建校验规则
function buildRules(scheme, ruleList) {
const config = scheme.__config__
if (scheme.__vModel__ === undefined) return
const rules = []
if (ruleTrigger[config.tag]) {
if (config.required) {
const type = isArray(config.defaultValue) ? 'type: \'array\',' : ''
let message = isArray(config.defaultValue) ? `请至少选择一个${config.label}` : scheme.placeholder
if (message === undefined) message = `${config.label}不能为空`
rules.push(`{ required: true, ${type} message: '${message}', trigger: '${ruleTrigger[config.tag]}' }`)
}
if (config.regList && isArray(config.regList)) {
config.regList.forEach(item => {
if (item.pattern) {
rules.push(
`{ pattern: ${eval(item.pattern)}, message: '${item.message}', trigger: '${ruleTrigger[config.tag]}' }`
)
}
})
}
ruleList.push(`${scheme.__vModel__}: [${rules.join(',')}],`)
}
}
// 构建options
function buildOptions(scheme, optionsList) {
if (scheme.__vModel__ === undefined) return
// el-cascader直接有options属性其他组件都是定义在slot中所以有两处判断
let { options } = scheme
if (!options) options = scheme.__slot__.options
if (scheme.__config__.dataType === 'dynamic') { options = [] }
const str = `${scheme.__vModel__}Options: ${JSON.stringify(options)},`
optionsList.push(str)
}
function buildProps(scheme, propsList) {
const str = `${scheme.__vModel__}Props: ${JSON.stringify(scheme.props.props)},`
propsList.push(str)
}
// el-upload的BeforeUpload
function buildBeforeUpload(scheme) {
const config = scheme.__config__
const unitNum = units[config.sizeUnit]; let rightSizeCode = ''; let acceptCode = ''; const
returnList = []
if (config.fileSize) {
rightSizeCode = `let isRightSize = file.size / ${unitNum} < ${config.fileSize}
if(!isRightSize){
this.$message.error('文件大小超过 ${config.fileSize}${config.sizeUnit}')
}`
returnList.push('isRightSize')
}
if (scheme.accept) {
acceptCode = `let isAccept = new RegExp('${scheme.accept}').test(file.type)
if(!isAccept){
this.$message.error('应该选择${scheme.accept}类型的文件')
}`
returnList.push('isAccept')
}
const str = `${scheme.__vModel__}BeforeUpload(file) {
${rightSizeCode}
${acceptCode}
return ${returnList.join('&&')}
},`
return returnList.length ? str : ''
}
// el-upload的submit
function buildSubmitUpload(scheme) {
const str = `submitUpload() {
this.$refs['${scheme.__vModel__}'].submit()
},`
return str
}
function buildOptionMethod(methodName, model, methodList, scheme) {
const config = scheme.__config__
const str = `${methodName}() {
// 注意this.$axios是通过Vue.prototype.$axios = axios挂载产生的
this.$axios({
method: '${config.method}',
url: '${config.url}'
}).then(resp => {
var { data } = resp
this.${model} = data.${config.dataPath}
})
},`
methodList.push(str)
}
// js整体拼接
function buildexport(conf, type, data, rules, selectOptions, uploadVar, props, methods, created) {
const str = `${exportDefault}{
${inheritAttrs[type]}
components: {},
props: [],
data () {
return {
${conf.formModel}: {
${data}
},
${conf.formRules}: {
${rules}
},
${uploadVar}
${selectOptions}
${props}
}
},
computed: {},
watch: {},
created () {
${created}
},
mounted () {},
methods: {
${methods}
}
}`
return str
}

View File

@ -0,0 +1,16 @@
/**
* 用于生成表单校验指定正则规则的触发方式
* 未在此处声明无触发方式的组件将不生成rule
*/
export default {
'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',
tinymce: 'blur'
}

View File

@ -0,0 +1,3 @@
## 简介
富文本编辑器tinymce的一个vue版本封装。使用cdn动态脚本引入的方式加载。

View File

@ -0,0 +1,8 @@
/* eslint-disable max-len */
export const plugins = [
'advlist anchor autolink autosave code codesample directionality emoticons fullscreen hr image imagetools insertdatetime link lists media nonbreaking noneditable pagebreak paste preview print save searchreplace spellchecker tabfocus table template textpattern visualblocks visualchars wordcount'
]
export const toolbar = [
'code searchreplace bold italic underline strikethrough alignleft aligncenter alignright outdent indent blockquote removeformat subscript superscript codesample hr bullist numlist link image charmap preview anchor pagebreak insertdatetime media table emoticons forecolor backcolor fullscreen'
]

View File

@ -0,0 +1,38 @@
<template>
<div>
<Tinymce v-model="defaultValue" :height="300" placeholder="在这里输入文字" />
</div>
</template>
<script>
import Tinymce from '../index.vue'
export default {
components: {
Tinymce
},
props: {
},
data() {
return {
defaultValue: '<p>配置文档参阅http://tinymce.ax-z.cn</p>'
}
},
computed: {
},
watch: {
},
created() {
},
mounted() {
},
methods: {
}
}
</script>

View File

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

View File

@ -0,0 +1,88 @@
<template>
<textarea :id="tinymceId" style="visibility: hidden" />
</template>
<script>
import loadTinymce from '@/utils/loadTinymce'
import { plugins, toolbar } from './config'
import { debounce } from 'throttle-debounce'
let num = 1
export default {
props: {
id: {
type: String,
default: () => {
num === 10000 && (num = 1)
return `tinymce${+new Date()}${num++}`
}
},
value: {
default: ''
}
},
data() {
return {
tinymceId: this.id
}
},
mounted() {
loadTinymce(tinymce => {
// eslint-disable-next-line global-require
require('./zh_CN')
let conf = {
selector: `#${this.tinymceId}`,
language: 'zh_CN',
menubar: 'file edit insert view format table',
plugins,
toolbar,
height: 300,
branding: false,
object_resizing: false,
end_container_on_empty_block: true,
powerpaste_word_import: 'clean',
code_dialog_height: 450,
code_dialog_width: 1000,
advlist_bullet_styles: 'square',
advlist_number_styles: 'default',
default_link_target: '_blank',
link_title: false,
nonbreaking_force_tab: true
}
conf = Object.assign(conf, this.$attrs)
conf.init_instance_callback = editor => {
if (this.value) editor.setContent(this.value)
this.vModel(editor)
}
tinymce.init(conf)
})
},
destroyed() {
this.destroyTinymce()
},
methods: {
vModel(editor) {
// setContent
const debounceSetContent = debounce(250, editor.setContent)
this.$watch('value', (val, prevVal) => {
if (editor && val !== prevVal && val !== editor.getContent()) {
if (typeof val !== 'string') val = val.toString()
debounceSetContent.call(editor, val)
}
})
editor.on('change keyup undo redo', () => {
this.$emit('input', editor.getContent())
})
},
destroyTinymce() {
if (!window.tinymce) return
const tinymce = window.tinymce.get(this.tinymceId)
if (tinymce) {
tinymce.destroy()
}
}
}
}
</script>

View File

@ -0,0 +1,28 @@
{
"name": "form-gen-tinymce",
"version": "1.0.0",
"description": "富文本编辑器tinymce的一个vue版本封装。使用cdn动态脚本引入的方式加载。",
"main": "lib/form-gen-tinymce.umd.js",
"directories": {
"example": "example"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/JakHuang/form-generator.git"
},
"keywords": [
"tinymce-vue"
],
"dependencies": {
"throttle-debounce": "^2.1.0"
},
"author": "jakHuang",
"license": "MIT",
"bugs": {
"url": "https://github.com/JakHuang/form-generator/issues"
},
"homepage": "https://github.com/JakHuang/form-generator/blob/dev/src/components/tinymce"
}

View File

@ -0,0 +1,420 @@
/* eslint-disable */
tinymce.addI18n('zh_CN',{
"Redo": "\u91cd\u505a",
"Undo": "\u64a4\u9500",
"Cut": "\u526a\u5207",
"Copy": "\u590d\u5236",
"Paste": "\u7c98\u8d34",
"Select all": "\u5168\u9009",
"New document": "\u65b0\u6587\u4ef6",
"Ok": "\u786e\u5b9a",
"Cancel": "\u53d6\u6d88",
"Visual aids": "\u7f51\u683c\u7ebf",
"Bold": "\u7c97\u4f53",
"Italic": "\u659c\u4f53",
"Underline": "\u4e0b\u5212\u7ebf",
"Strikethrough": "\u5220\u9664\u7ebf",
"Superscript": "\u4e0a\u6807",
"Subscript": "\u4e0b\u6807",
"Clear formatting": "\u6e05\u9664\u683c\u5f0f",
"Align left": "\u5de6\u8fb9\u5bf9\u9f50",
"Align center": "\u4e2d\u95f4\u5bf9\u9f50",
"Align right": "\u53f3\u8fb9\u5bf9\u9f50",
"Justify": "\u4e24\u7aef\u5bf9\u9f50",
"Bullet list": "\u9879\u76ee\u7b26\u53f7",
"Numbered list": "\u7f16\u53f7\u5217\u8868",
"Decrease indent": "\u51cf\u5c11\u7f29\u8fdb",
"Increase indent": "\u589e\u52a0\u7f29\u8fdb",
"Close": "\u5173\u95ed",
"Formats": "\u683c\u5f0f",
"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "\u4f60\u7684\u6d4f\u89c8\u5668\u4e0d\u652f\u6301\u6253\u5f00\u526a\u8d34\u677f\uff0c\u8bf7\u4f7f\u7528Ctrl+X\/C\/V\u7b49\u5feb\u6377\u952e\u3002",
"Headers": "\u6807\u9898",
"Header 1": "\u6807\u98981",
"Header 2": "\u6807\u98982",
"Header 3": "\u6807\u98983",
"Header 4": "\u6807\u98984",
"Header 5": "\u6807\u98985",
"Header 6": "\u6807\u98986",
"Headings": "\u6807\u9898",
"Heading 1": "\u6807\u98981",
"Heading 2": "\u6807\u98982",
"Heading 3": "\u6807\u98983",
"Heading 4": "\u6807\u98984",
"Heading 5": "\u6807\u98985",
"Heading 6": "\u6807\u98986",
"Preformatted": "\u9884\u5148\u683c\u5f0f\u5316\u7684",
"Div": "Div",
"Pre": "Pre",
"Code": "\u4ee3\u7801",
"Paragraph": "\u6bb5\u843d",
"Blockquote": "\u5f15\u6587\u533a\u5757",
"Inline": "\u6587\u672c",
"Blocks": "\u57fa\u5757",
"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "\u5f53\u524d\u4e3a\u7eaf\u6587\u672c\u7c98\u8d34\u6a21\u5f0f\uff0c\u518d\u6b21\u70b9\u51fb\u53ef\u4ee5\u56de\u5230\u666e\u901a\u7c98\u8d34\u6a21\u5f0f\u3002",
"Fonts": "\u5b57\u4f53",
"Font Sizes": "\u5b57\u53f7",
"Class": "\u7c7b\u578b",
"Browse for an image": "\u6d4f\u89c8\u56fe\u50cf",
"OR": "\u6216",
"Drop an image here": "\u62d6\u653e\u4e00\u5f20\u56fe\u50cf\u81f3\u6b64",
"Upload": "\u4e0a\u4f20",
"Block": "\u5757",
"Align": "\u5bf9\u9f50",
"Default": "\u9ed8\u8ba4",
"Circle": "\u7a7a\u5fc3\u5706",
"Disc": "\u5b9e\u5fc3\u5706",
"Square": "\u65b9\u5757",
"Lower Alpha": "\u5c0f\u5199\u82f1\u6587\u5b57\u6bcd",
"Lower Greek": "\u5c0f\u5199\u5e0c\u814a\u5b57\u6bcd",
"Lower Roman": "\u5c0f\u5199\u7f57\u9a6c\u5b57\u6bcd",
"Upper Alpha": "\u5927\u5199\u82f1\u6587\u5b57\u6bcd",
"Upper Roman": "\u5927\u5199\u7f57\u9a6c\u5b57\u6bcd",
"Anchor...": "\u951a\u70b9...",
"Name": "\u540d\u79f0",
"Id": "\u6807\u8bc6\u7b26",
"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "\u6807\u8bc6\u7b26\u5e94\u8be5\u4ee5\u5b57\u6bcd\u5f00\u5934\uff0c\u540e\u8ddf\u5b57\u6bcd\u3001\u6570\u5b57\u3001\u7834\u6298\u53f7\u3001\u70b9\u3001\u5192\u53f7\u6216\u4e0b\u5212\u7ebf\u3002",
"You have unsaved changes are you sure you want to navigate away?": "\u4f60\u8fd8\u6709\u6587\u6863\u5c1a\u672a\u4fdd\u5b58\uff0c\u786e\u5b9a\u8981\u79bb\u5f00\uff1f",
"Restore last draft": "\u6062\u590d\u4e0a\u6b21\u7684\u8349\u7a3f",
"Special character...": "\u7279\u6b8a\u5b57\u7b26...",
"Source code": "\u6e90\u4ee3\u7801",
"Insert\/Edit code sample": "\u63d2\u5165\/\u7f16\u8f91\u4ee3\u7801\u793a\u4f8b",
"Language": "\u8bed\u8a00",
"Code sample...": "\u793a\u4f8b\u4ee3\u7801...",
"Color Picker": "\u9009\u8272\u5668",
"R": "R",
"G": "G",
"B": "B",
"Left to right": "\u4ece\u5de6\u5230\u53f3",
"Right to left": "\u4ece\u53f3\u5230\u5de6",
"Emoticons...": "\u8868\u60c5\u7b26\u53f7...",
"Metadata and Document Properties": "\u5143\u6570\u636e\u548c\u6587\u6863\u5c5e\u6027",
"Title": "\u6807\u9898",
"Keywords": "\u5173\u952e\u8bcd",
"Description": "\u63cf\u8ff0",
"Robots": "\u673a\u5668\u4eba",
"Author": "\u4f5c\u8005",
"Encoding": "\u7f16\u7801",
"Fullscreen": "\u5168\u5c4f",
"Action": "\u64cd\u4f5c",
"Shortcut": "\u5feb\u6377\u952e",
"Help": "\u5e2e\u52a9",
"Address": "\u5730\u5740",
"Focus to menubar": "\u79fb\u52a8\u7126\u70b9\u5230\u83dc\u5355\u680f",
"Focus to toolbar": "\u79fb\u52a8\u7126\u70b9\u5230\u5de5\u5177\u680f",
"Focus to element path": "\u79fb\u52a8\u7126\u70b9\u5230\u5143\u7d20\u8def\u5f84",
"Focus to contextual toolbar": "\u79fb\u52a8\u7126\u70b9\u5230\u4e0a\u4e0b\u6587\u83dc\u5355",
"Insert link (if link plugin activated)": "\u63d2\u5165\u94fe\u63a5 (\u5982\u679c\u94fe\u63a5\u63d2\u4ef6\u5df2\u6fc0\u6d3b)",
"Save (if save plugin activated)": "\u4fdd\u5b58(\u5982\u679c\u4fdd\u5b58\u63d2\u4ef6\u5df2\u6fc0\u6d3b)",
"Find (if searchreplace plugin activated)": "\u67e5\u627e(\u5982\u679c\u67e5\u627e\u66ff\u6362\u63d2\u4ef6\u5df2\u6fc0\u6d3b)",
"Plugins installed ({0}):": "\u5df2\u5b89\u88c5\u63d2\u4ef6 ({0}):",
"Premium plugins:": "\u4f18\u79c0\u63d2\u4ef6\uff1a",
"Learn more...": "\u4e86\u89e3\u66f4\u591a...",
"You are using {0}": "\u4f60\u6b63\u5728\u4f7f\u7528 {0}",
"Plugins": "\u63d2\u4ef6",
"Handy Shortcuts": "\u5feb\u6377\u952e",
"Horizontal line": "\u6c34\u5e73\u5206\u5272\u7ebf",
"Insert\/edit image": "\u63d2\u5165\/\u7f16\u8f91\u56fe\u7247",
"Image description": "\u56fe\u7247\u63cf\u8ff0",
"Source": "\u5730\u5740",
"Dimensions": "\u5927\u5c0f",
"Constrain proportions": "\u4fdd\u6301\u7eb5\u6a2a\u6bd4",
"General": "\u666e\u901a",
"Advanced": "\u9ad8\u7ea7",
"Style": "\u6837\u5f0f",
"Vertical space": "\u5782\u76f4\u8fb9\u8ddd",
"Horizontal space": "\u6c34\u5e73\u8fb9\u8ddd",
"Border": "\u8fb9\u6846",
"Insert image": "\u63d2\u5165\u56fe\u7247",
"Image...": "\u56fe\u7247...",
"Image list": "\u56fe\u7247\u5217\u8868",
"Rotate counterclockwise": "\u9006\u65f6\u9488\u65cb\u8f6c",
"Rotate clockwise": "\u987a\u65f6\u9488\u65cb\u8f6c",
"Flip vertically": "\u5782\u76f4\u7ffb\u8f6c",
"Flip horizontally": "\u6c34\u5e73\u7ffb\u8f6c",
"Edit image": "\u7f16\u8f91\u56fe\u7247",
"Image options": "\u56fe\u7247\u9009\u9879",
"Zoom in": "\u653e\u5927",
"Zoom out": "\u7f29\u5c0f",
"Crop": "\u88c1\u526a",
"Resize": "\u8c03\u6574\u5927\u5c0f",
"Orientation": "\u65b9\u5411",
"Brightness": "\u4eae\u5ea6",
"Sharpen": "\u9510\u5316",
"Contrast": "\u5bf9\u6bd4\u5ea6",
"Color levels": "\u989c\u8272\u5c42\u6b21",
"Gamma": "\u4f3d\u9a6c\u503c",
"Invert": "\u53cd\u8f6c",
"Apply": "\u5e94\u7528",
"Back": "\u540e\u9000",
"Insert date\/time": "\u63d2\u5165\u65e5\u671f\/\u65f6\u95f4",
"Date\/time": "\u65e5\u671f\/\u65f6\u95f4",
"Insert\/Edit Link": "\u63d2\u5165\/\u7f16\u8f91\u94fe\u63a5",
"Insert\/edit link": "\u63d2\u5165\/\u7f16\u8f91\u94fe\u63a5",
"Text to display": "\u663e\u793a\u6587\u5b57",
"Url": "\u5730\u5740",
"Open link in...": "\u94fe\u63a5\u6253\u5f00\u4f4d\u7f6e...",
"Current window": "\u5f53\u524d\u7a97\u53e3",
"None": "\u65e0",
"New window": "\u5728\u65b0\u7a97\u53e3\u6253\u5f00",
"Remove link": "\u5220\u9664\u94fe\u63a5",
"Anchors": "\u951a\u70b9",
"Link...": "\u94fe\u63a5...",
"Paste or type a link": "\u7c98\u8d34\u6216\u8f93\u5165\u94fe\u63a5",
"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "\u4f60\u6240\u586b\u5199\u7684URL\u5730\u5740\u4e3a\u90ae\u4ef6\u5730\u5740\uff0c\u9700\u8981\u52a0\u4e0amailto:\u524d\u7f00\u5417\uff1f",
"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "\u4f60\u6240\u586b\u5199\u7684URL\u5730\u5740\u5c5e\u4e8e\u5916\u90e8\u94fe\u63a5\uff0c\u9700\u8981\u52a0\u4e0ahttp:\/\/:\u524d\u7f00\u5417\uff1f",
"Link list": "\u94fe\u63a5\u5217\u8868",
"Insert video": "\u63d2\u5165\u89c6\u9891",
"Insert\/edit video": "\u63d2\u5165\/\u7f16\u8f91\u89c6\u9891",
"Insert\/edit media": "\u63d2\u5165\/\u7f16\u8f91\u5a92\u4f53",
"Alternative source": "\u955c\u50cf",
"Alternative source URL": "\u66ff\u4ee3\u6765\u6e90\u7f51\u5740",
"Media poster (Image URL)": "\u5c01\u9762(\u56fe\u7247\u5730\u5740)",
"Paste your embed code below:": "\u5c06\u5185\u5d4c\u4ee3\u7801\u7c98\u8d34\u5728\u4e0b\u9762:",
"Embed": "\u5185\u5d4c",
"Media...": "\u591a\u5a92\u4f53...",
"Nonbreaking space": "\u4e0d\u95f4\u65ad\u7a7a\u683c",
"Page break": "\u5206\u9875\u7b26",
"Paste as text": "\u7c98\u8d34\u4e3a\u6587\u672c",
"Preview": "\u9884\u89c8",
"Print...": "\u6253\u5370...",
"Save": "\u4fdd\u5b58",
"Find": "\u67e5\u627e",
"Replace with": "\u66ff\u6362\u4e3a",
"Replace": "\u66ff\u6362",
"Replace all": "\u5168\u90e8\u66ff\u6362",
"Previous": "\u4e0a\u4e00\u4e2a",
"Next": "\u4e0b\u4e00\u4e2a",
"Find and replace...": "\u67e5\u627e\u5e76\u66ff\u6362...",
"Could not find the specified string.": "\u672a\u627e\u5230\u641c\u7d22\u5185\u5bb9.",
"Match case": "\u533a\u5206\u5927\u5c0f\u5199",
"Find whole words only": "\u5168\u5b57\u5339\u914d",
"Spell check": "\u62fc\u5199\u68c0\u67e5",
"Ignore": "\u5ffd\u7565",
"Ignore all": "\u5168\u90e8\u5ffd\u7565",
"Finish": "\u5b8c\u6210",
"Add to Dictionary": "\u6dfb\u52a0\u5230\u5b57\u5178",
"Insert table": "\u63d2\u5165\u8868\u683c",
"Table properties": "\u8868\u683c\u5c5e\u6027",
"Delete table": "\u5220\u9664\u8868\u683c",
"Cell": "\u5355\u5143\u683c",
"Row": "\u884c",
"Column": "\u5217",
"Cell properties": "\u5355\u5143\u683c\u5c5e\u6027",
"Merge cells": "\u5408\u5e76\u5355\u5143\u683c",
"Split cell": "\u62c6\u5206\u5355\u5143\u683c",
"Insert row before": "\u5728\u4e0a\u65b9\u63d2\u5165",
"Insert row after": "\u5728\u4e0b\u65b9\u63d2\u5165",
"Delete row": "\u5220\u9664\u884c",
"Row properties": "\u884c\u5c5e\u6027",
"Cut row": "\u526a\u5207\u884c",
"Copy row": "\u590d\u5236\u884c",
"Paste row before": "\u7c98\u8d34\u5230\u4e0a\u65b9",
"Paste row after": "\u7c98\u8d34\u5230\u4e0b\u65b9",
"Insert column before": "\u5728\u5de6\u4fa7\u63d2\u5165",
"Insert column after": "\u5728\u53f3\u4fa7\u63d2\u5165",
"Delete column": "\u5220\u9664\u5217",
"Cols": "\u5217",
"Rows": "\u884c",
"Width": "\u5bbd",
"Height": "\u9ad8",
"Cell spacing": "\u5355\u5143\u683c\u5916\u95f4\u8ddd",
"Cell padding": "\u5355\u5143\u683c\u5185\u8fb9\u8ddd",
"Show caption": "\u663e\u793a\u6807\u9898",
"Left": "\u5de6\u5bf9\u9f50",
"Center": "\u5c45\u4e2d",
"Right": "\u53f3\u5bf9\u9f50",
"Cell type": "\u5355\u5143\u683c\u7c7b\u578b",
"Scope": "\u8303\u56f4",
"Alignment": "\u5bf9\u9f50\u65b9\u5f0f",
"H Align": "\u6c34\u5e73\u5bf9\u9f50",
"V Align": "\u5782\u76f4\u5bf9\u9f50",
"Top": "\u9876\u90e8\u5bf9\u9f50",
"Middle": "\u5782\u76f4\u5c45\u4e2d",
"Bottom": "\u5e95\u90e8\u5bf9\u9f50",
"Header cell": "\u8868\u5934\u5355\u5143\u683c",
"Row group": "\u884c\u7ec4",
"Column group": "\u5217\u7ec4",
"Row type": "\u884c\u7c7b\u578b",
"Header": "\u8868\u5934",
"Body": "\u8868\u4f53",
"Footer": "\u8868\u5c3e",
"Border color": "\u8fb9\u6846\u989c\u8272",
"Insert template...": "\u63d2\u5165\u6a21\u677f...",
"Templates": "\u6a21\u677f",
"Template": "\u6a21\u677f",
"Text color": "\u6587\u5b57\u989c\u8272",
"Background color": "\u80cc\u666f\u8272",
"Custom...": "\u81ea\u5b9a\u4e49...",
"Custom color": "\u81ea\u5b9a\u4e49\u989c\u8272",
"No color": "\u65e0",
"Remove color": "\u79fb\u9664\u989c\u8272",
"Table of Contents": "\u5185\u5bb9\u5217\u8868",
"Show blocks": "\u663e\u793a\u533a\u5757\u8fb9\u6846",
"Show invisible characters": "\u663e\u793a\u4e0d\u53ef\u89c1\u5b57\u7b26",
"Word count": "\u5b57\u6570",
"Count": "\u8ba1\u6570",
"Document": "\u6587\u6863",
"Selection": "\u9009\u62e9",
"Words": "\u5355\u8bcd",
"Words: {0}": "\u5b57\u6570\uff1a{0}",
"{0} words": "{0} \u5b57",
"File": "\u6587\u4ef6",
"Edit": "\u7f16\u8f91",
"Insert": "\u63d2\u5165",
"View": "\u89c6\u56fe",
"Format": "\u683c\u5f0f",
"Table": "\u8868\u683c",
"Tools": "\u5de5\u5177",
"Powered by {0}": "\u7531{0}\u9a71\u52a8",
"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "\u5728\u7f16\u8f91\u533a\u6309ALT-F9\u6253\u5f00\u83dc\u5355\uff0c\u6309ALT-F10\u6253\u5f00\u5de5\u5177\u680f\uff0c\u6309ALT-0\u67e5\u770b\u5e2e\u52a9",
"Image title": "\u56fe\u7247\u6807\u9898",
"Border width": "\u8fb9\u6846\u5bbd\u5ea6",
"Border style": "\u8fb9\u6846\u6837\u5f0f",
"Error": "\u9519\u8bef",
"Warn": "\u8b66\u544a",
"Valid": "\u6709\u6548",
"To open the popup, press Shift+Enter": "\u6309Shitf+Enter\u952e\u6253\u5f00\u5bf9\u8bdd\u6846",
"Rich Text Area. Press ALT-0 for help.": "\u7f16\u8f91\u533a\u3002\u6309Alt+0\u952e\u6253\u5f00\u5e2e\u52a9\u3002",
"System Font": "\u7cfb\u7edf\u5b57\u4f53",
"Failed to upload image: {0}": "\u56fe\u7247\u4e0a\u4f20\u5931\u8d25: {0}",
"Failed to load plugin: {0} from url {1}": "\u63d2\u4ef6\u52a0\u8f7d\u5931\u8d25: {0} \u6765\u81ea\u94fe\u63a5 {1}",
"Failed to load plugin url: {0}": "\u63d2\u4ef6\u52a0\u8f7d\u5931\u8d25 \u94fe\u63a5: {0}",
"Failed to initialize plugin: {0}": "\u63d2\u4ef6\u521d\u59cb\u5316\u5931\u8d25: {0}",
"example": "\u793a\u4f8b",
"Search": "\u641c\u7d22",
"All": "\u5168\u90e8",
"Currency": "\u8d27\u5e01",
"Text": "\u6587\u5b57",
"Quotations": "\u5f15\u7528",
"Mathematical": "\u6570\u5b66",
"Extended Latin": "\u62c9\u4e01\u8bed\u6269\u5145",
"Symbols": "\u7b26\u53f7",
"Arrows": "\u7bad\u5934",
"User Defined": "\u81ea\u5b9a\u4e49",
"dollar sign": "\u7f8e\u5143\u7b26\u53f7",
"currency sign": "\u8d27\u5e01\u7b26\u53f7",
"euro-currency sign": "\u6b27\u5143\u7b26\u53f7",
"colon sign": "\u5192\u53f7",
"cruzeiro sign": "\u514b\u9c81\u8d5b\u7f57\u5e01\u7b26\u53f7",
"french franc sign": "\u6cd5\u90ce\u7b26\u53f7",
"lira sign": "\u91cc\u62c9\u7b26\u53f7",
"mill sign": "\u5bc6\u5c14\u7b26\u53f7",
"naira sign": "\u5948\u62c9\u7b26\u53f7",
"peseta sign": "\u6bd4\u585e\u5854\u7b26\u53f7",
"rupee sign": "\u5362\u6bd4\u7b26\u53f7",
"won sign": "\u97e9\u5143\u7b26\u53f7",
"new sheqel sign": "\u65b0\u8c22\u514b\u5c14\u7b26\u53f7",
"dong sign": "\u8d8a\u5357\u76fe\u7b26\u53f7",
"kip sign": "\u8001\u631d\u57fa\u666e\u7b26\u53f7",
"tugrik sign": "\u56fe\u683c\u91cc\u514b\u7b26\u53f7",
"drachma sign": "\u5fb7\u62c9\u514b\u9a6c\u7b26\u53f7",
"german penny symbol": "\u5fb7\u56fd\u4fbf\u58eb\u7b26\u53f7",
"peso sign": "\u6bd4\u7d22\u7b26\u53f7",
"guarani sign": "\u74dc\u62c9\u5c3c\u7b26\u53f7",
"austral sign": "\u6fb3\u5143\u7b26\u53f7",
"hryvnia sign": "\u683c\u91cc\u592b\u5c3c\u4e9a\u7b26\u53f7",
"cedi sign": "\u585e\u5730\u7b26\u53f7",
"livre tournois sign": "\u91cc\u5f17\u5f17\u5c14\u7b26\u53f7",
"spesmilo sign": "spesmilo\u7b26\u53f7",
"tenge sign": "\u575a\u6208\u7b26\u53f7",
"indian rupee sign": "\u5370\u5ea6\u5362\u6bd4",
"turkish lira sign": "\u571f\u8033\u5176\u91cc\u62c9",
"nordic mark sign": "\u5317\u6b27\u9a6c\u514b",
"manat sign": "\u9a6c\u7eb3\u7279\u7b26\u53f7",
"ruble sign": "\u5362\u5e03\u7b26\u53f7",
"yen character": "\u65e5\u5143\u5b57\u6837",
"yuan character": "\u4eba\u6c11\u5e01\u5143\u5b57\u6837",
"yuan character, in hong kong and taiwan": "\u5143\u5b57\u6837\uff08\u6e2f\u53f0\u5730\u533a\uff09",
"yen\/yuan character variant one": "\u5143\u5b57\u6837\uff08\u5927\u5199\uff09",
"Loading emoticons...": "\u52a0\u8f7d\u8868\u60c5\u7b26\u53f7...",
"Could not load emoticons": "\u4e0d\u80fd\u52a0\u8f7d\u8868\u60c5\u7b26\u53f7",
"People": "\u4eba\u7c7b",
"Animals and Nature": "\u52a8\u7269\u548c\u81ea\u7136",
"Food and Drink": "\u98df\u7269\u548c\u996e\u54c1",
"Activity": "\u6d3b\u52a8",
"Travel and Places": "\u65c5\u6e38\u548c\u5730\u70b9",
"Objects": "\u7269\u4ef6",
"Flags": "\u65d7\u5e1c",
"Characters": "\u5b57\u7b26",
"Characters (no spaces)": "\u5b57\u7b26(\u65e0\u7a7a\u683c)",
"{0} characters": "{0} \u4e2a\u5b57\u7b26",
"Error: Form submit field collision.": "\u9519\u8bef: \u8868\u5355\u63d0\u4ea4\u5b57\u6bb5\u51b2\u7a81\u3002",
"Error: No form element found.": "\u9519\u8bef: \u6ca1\u6709\u8868\u5355\u63a7\u4ef6\u3002",
"Update": "\u66f4\u65b0",
"Color swatch": "\u989c\u8272\u6837\u672c",
"Turquoise": "\u9752\u7eff\u8272",
"Green": "\u7eff\u8272",
"Blue": "\u84dd\u8272",
"Purple": "\u7d2b\u8272",
"Navy Blue": "\u6d77\u519b\u84dd",
"Dark Turquoise": "\u6df1\u84dd\u7eff\u8272",
"Dark Green": "\u6df1\u7eff\u8272",
"Medium Blue": "\u4e2d\u84dd\u8272",
"Medium Purple": "\u4e2d\u7d2b\u8272",
"Midnight Blue": "\u6df1\u84dd\u8272",
"Yellow": "\u9ec4\u8272",
"Orange": "\u6a59\u8272",
"Red": "\u7ea2\u8272",
"Light Gray": "\u6d45\u7070\u8272",
"Gray": "\u7070\u8272",
"Dark Yellow": "\u6697\u9ec4\u8272",
"Dark Orange": "\u6df1\u6a59\u8272",
"Dark Red": "\u6df1\u7ea2\u8272",
"Medium Gray": "\u4e2d\u7070\u8272",
"Dark Gray": "\u6df1\u7070\u8272",
"Light Green": "\u6d45\u7eff\u8272",
"Light Yellow": "\u6d45\u9ec4\u8272",
"Light Red": "\u6d45\u7ea2\u8272",
"Light Purple": "\u6d45\u7d2b\u8272",
"Light Blue": "\u6d45\u84dd\u8272",
"Dark Purple": "\u6df1\u7d2b\u8272",
"Dark Blue": "\u6df1\u84dd\u8272",
"Black": "\u9ed1\u8272",
"White": "\u767d\u8272",
"Switch to or from fullscreen mode": "\u5207\u6362\u5168\u5c4f\u6a21\u5f0f",
"Open help dialog": "\u6253\u5f00\u5e2e\u52a9\u5bf9\u8bdd\u6846",
"history": "\u5386\u53f2",
"styles": "\u6837\u5f0f",
"formatting": "\u683c\u5f0f\u5316",
"alignment": "\u5bf9\u9f50",
"indentation": "\u7f29\u8fdb",
"permanent pen": "\u8bb0\u53f7\u7b14",
"comments": "\u5907\u6ce8",
"Format Painter": "\u683c\u5f0f\u5237",
"Insert\/edit iframe": "\u63d2\u5165\/\u7f16\u8f91\u6846\u67b6",
"Capitalization": "\u5927\u5199",
"lowercase": "\u5c0f\u5199",
"UPPERCASE": "\u5927\u5199",
"Title Case": "\u9996\u5b57\u6bcd\u5927\u5199",
"Permanent Pen Properties": "\u6c38\u4e45\u7b14\u5c5e\u6027",
"Permanent pen properties...": "\u6c38\u4e45\u7b14\u5c5e\u6027...",
"Font": "\u5b57\u4f53",
"Size": "\u5b57\u53f7",
"More...": "\u66f4\u591a...",
"Spellcheck Language": "\u62fc\u5199\u68c0\u67e5\u8bed\u8a00",
"Select...": "\u9009\u62e9...",
"Preferences": "\u9996\u9009\u9879",
"Yes": "\u662f",
"No": "\u5426",
"Keyboard Navigation": "\u952e\u76d8\u6307\u5f15",
"Version": "\u7248\u672c",
"Anchor": "\u951a\u70b9",
"Special character": "\u7279\u6b8a\u7b26\u53f7",
"Code sample": "\u4ee3\u7801\u793a\u4f8b",
"Color": "\u989c\u8272",
"Emoticons": "\u8868\u60c5",
"Document properties": "\u6587\u6863\u5c5e\u6027",
"Image": "\u56fe\u7247",
"Insert link": "\u63d2\u5165\u94fe\u63a5",
"Target": "\u6253\u5f00\u65b9\u5f0f",
"Link": "\u94fe\u63a5",
"Poster": "\u5c01\u9762",
"Media": "\u5a92\u4f53",
"Print": "\u6253\u5370",
"Prev": "\u4e0a\u4e00\u4e2a",
"Find and replace": "\u67e5\u627e\u548c\u66ff\u6362",
"Whole words": "\u5168\u5b57\u5339\u914d",
"Spellcheck": "\u62fc\u5199\u68c0\u67e5",
"Caption": "\u6807\u9898",
"Insert template": "\u63d2\u5165\u6a21\u677f"
});

View File

@ -1,6 +1,7 @@
<template> <template>
<div class="navbar"> <div class="navbar">
<hamburger id="hamburger-container" :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" /> <hamburger id="hamburger-container" :is-active="sidebar.opened" class="hamburger-container"
@toggleClick="toggleSideBar" />
<breadcrumb id="breadcrumb-container" class="breadcrumb-container" v-if="!topNav" /> <breadcrumb id="breadcrumb-container" class="breadcrumb-container" v-if="!topNav" />
<top-nav id="topmenu-container" class="topmenu-container" v-if="topNav" /> <top-nav id="topmenu-container" class="topmenu-container" v-if="topNav" />
@ -8,41 +9,6 @@
<div style="padding-top: 5px; padding-right: 38px;"> <div style="padding-top: 5px; padding-right: 38px;">
<navbar-right :blackTitle='true' /> <navbar-right :blackTitle='true' />
</div> </div>
<!-- <div class="right-menu"> -->
<!-- <template v-if="device!=='mobile'"> -->
<!-- <search id="header-search" class="right-menu-item" /> -->
<!-- 站内信 -->
<!-- <notify-message class="right-menu-item hover-effect" /> -->
<!-- <screenfull id="screenfull" class="right-menu-item hover-effect" /> -->
<!-- <el-tooltip content="布局大小" effect="dark" placement="bottom">
<size-select id="size-select" class="right-menu-item hover-effect" />
</el-tooltip> -->
<!-- </template> -->
<!-- <el-dropdown class="avatar-container right-menu-item hover-effect" trigger="click">
<div class="avatar-wrapper"> -->
<!-- <img :src="avatar" class="user-avatar"> -->
<!-- <span v-if="nickname" class="user-nickname">{{ nickname }}</span>
<i class="el-icon-caret-bottom" />
</div>
<el-dropdown-menu slot="dropdown">
<router-link to="/user/profile">
<el-dropdown-item>个人中心</el-dropdown-item>
</router-link> -->
<!-- <el-dropdown-item @click.native="setting = true">
<span>布局设置</span>
</el-dropdown-item> -->
<!-- <el-dropdown-item divided @click.native="logout">
<span>退出登录</span>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown> -->
<!-- </div> -->
</div> </div>
</template> </template>
@ -55,7 +21,6 @@ import Screenfull from '@/components/Screenfull'
import SizeSelect from '@/components/SizeSelect' import SizeSelect from '@/components/SizeSelect'
import Search from '@/components/HeaderSearch' import Search from '@/components/HeaderSearch'
import NotifyMessage from '@/layout/components/Message' import NotifyMessage from '@/layout/components/Message'
// import {getPath} from "@/utils/ruoyi";
import NavbarRight from './NavbarRight.vue' import NavbarRight from './NavbarRight.vue'
export default { export default {
@ -117,7 +82,7 @@ export default {
box-shadow: 0 1px 4px rgba(0, 21, 41, .08); box-shadow: 0 1px 4px rgba(0, 21, 41, .08);
.hamburger-container { .hamburger-container {
line-height: 52px; line-height: 44px;
height: 100%; height: 100%;
float: left; float: left;
cursor: pointer; cursor: pointer;
@ -185,6 +150,7 @@ export default {
height: 35px; height: 35px;
border-radius: 50%; border-radius: 50%;
} }
.user-nickname { .user-nickname {
margin-left: 5px; margin-left: 5px;
font-size: 14px; font-size: 14px;

View File

@ -8,17 +8,15 @@
<tags-view v-if="needTagsView" /> <tags-view v-if="needTagsView" />
</div> </div>
<app-main /> <app-main />
<div class="main-footer" style="color: #C7C7C7; user-select: none; font-size: 14px; letter-spacing: 1px; height: 30px; display: grid; place-content: center;">&copy; 中建材智能自动化研究院有限公司</div> <div class="main-footer"
<right-panel> style="color: #C7C7C7; user-select: none; font-size: 14px; letter-spacing: 1px; height: 30px; display: grid; place-content: center;">
<settings /> &copy; 中建材智能自动化研究院有限公司</div>
</right-panel>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import RightPanel from '@/components/RightPanel' import { AppMain, Navbar, Sidebar, TagsView } from './components'
import { AppMain, Navbar, Settings, Sidebar, TagsView } from './components'
import ResizeMixin from './mixin/ResizeHandler' import ResizeMixin from './mixin/ResizeHandler'
import { mapState } from 'vuex' import { mapState } from 'vuex'
import variables from '@/assets/styles/variables.scss' import variables from '@/assets/styles/variables.scss'
@ -28,8 +26,6 @@ export default {
components: { components: {
AppMain, AppMain,
Navbar, Navbar,
RightPanel,
Settings,
Sidebar, Sidebar,
TagsView TagsView
}, },

View File

@ -1,5 +1,7 @@
$editorTabsborderColor: #121315; $editorTabsborderColor: #121315;
body, html{
body,
html {
margin: 0; margin: 0;
padding: 0; padding: 0;
background: #F2F4F9; background: #F2F4F9;
@ -9,19 +11,23 @@ body, html{
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji; font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji;
} }
input, textarea{ input,
textarea {
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji; font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji;
} }
.editor-tabs { .editor-tabs {
background: $editorTabsborderColor; background: $editorTabsborderColor;
.el-tabs__header { .el-tabs__header {
margin: 0; margin: 0;
border-bottom-color: $editorTabsborderColor; border-bottom-color: $editorTabsborderColor;
.el-tabs__nav { .el-tabs__nav {
border-color: $editorTabsborderColor; border-color: $editorTabsborderColor;
} }
} }
.el-tabs__item { .el-tabs__item {
height: 32px; height: 32px;
line-height: 32px; line-height: 32px;
@ -31,17 +37,21 @@ input, textarea{
margin-right: 5px; margin-right: 5px;
user-select: none; user-select: none;
} }
.el-tabs__item.is-active { .el-tabs__item.is-active {
background: #1e1e1e; background: #1e1e1e;
border-bottom-color: #1e1e1e !important; border-bottom-color: #1e1e1e !important;
color: #fff; color: #fff;
} }
.el-icon-edit { .el-icon-edit {
color: #f1fa8c; color: #f1fa8c;
} }
.el-icon-document { .el-icon-document {
color: #a95812; color: #a95812;
} }
:focus.is-active.is-focus:not(:active) { :focus.is-active.is-focus:not(:active) {
box-shadow: none; box-shadow: none;
border-radius: 0; border-radius: 0;
@ -54,28 +64,34 @@ input, textarea{
padding: 12px 18px 15px 15px; padding: 12px 18px 15px 15px;
} }
} }
.el-scrollbar__wrap { .el-scrollbar__wrap {
box-sizing: border-box; box-sizing: border-box;
overflow-x: hidden !important; overflow-x: hidden !important;
margin-bottom: 0 !important; margin-bottom: 0 !important;
} }
.center-tabs { .center-tabs {
.el-tabs__header { .el-tabs__header {
margin-bottom: 0 !important; margin-bottom: 0 !important;
} }
.el-tabs__item { .el-tabs__item {
width: 50%; width: 50%;
text-align: center; text-align: center;
} }
.el-tabs__nav { .el-tabs__nav {
width: 100%; width: 100%;
} }
} }
.reg-item { .reg-item {
padding: 12px 6px; padding: 12px 6px;
background: #f8f8f8; background: #f8f8f8;
position: relative; position: relative;
border-radius: 4px; border-radius: 4px;
.close-btn { .close-btn {
position: absolute; position: absolute;
right: -6px; right: -6px;
@ -91,18 +107,22 @@ input, textarea{
z-index: 1; z-index: 1;
cursor: pointer; cursor: pointer;
font-size: 12px; font-size: 12px;
&:hover { &:hover {
background: rgba(210, 23, 23, 0.5) background: rgba(210, 23, 23, 0.5)
} }
} }
&+.reg-item { &+.reg-item {
margin-top: 18px; margin-top: 18px;
} }
} }
.action-bar { .action-bar {
& .el-button+.el-button { & .el-button+.el-button {
margin-left: 15px; margin-left: 15px;
} }
& i { & i {
font-size: 20px; font-size: 20px;
vertical-align: middle; vertical-align: middle;
@ -114,15 +134,19 @@ input, textarea{
.custom-tree-node { .custom-tree-node {
width: 100%; width: 100%;
font-size: 14px; font-size: 14px;
.node-operation { .node-operation {
float: right; float: right;
} }
i[class*="el-icon"]+i[class*="el-icon"] { i[class*="el-icon"]+i[class*="el-icon"] {
margin-left: 6px; margin-left: 6px;
} }
.el-icon-plus { .el-icon-plus {
color: #409EFF; color: #409EFF;
} }
.el-icon-delete { .el-icon-delete {
color: #157a0c; color: #157a0c;
} }
@ -136,76 +160,7 @@ input, textarea{
display: inline-block; display: inline-block;
vertical-align: text-top; vertical-align: text-top;
} }
.el-upload__tip { .el-upload__tip {
line-height: 1.2; line-height: 1.2;
} }
// 滚动条
::-webkit-scrollbar {
width: 8px;
height: 8px;
background-color: transparent;
}
::-webkit-scrollbar-track-piece {
background-color:rgba(144,147,153,0);
}
::-webkit-scrollbar-corner {
background-color:rgba(144,147,153,0);
}
::-webkit-scrollbar-track {
width: 6px;
background: rgba(144,147,153,0);
-webkit-border-radius: 2em;
-moz-border-radius: 2em;
border-radius: 2em;
}
::-webkit-scrollbar-thumb {
background-color: #EDEDED;
background-clip: padding-box;
min-height: 28px;
-webkit-border-radius: 2em;
-moz-border-radius: 2em;
border-radius: 2em;
transition: background-color .3s;
cursor: pointer;
}
::-webkit-scrollbar-thumb:hover {
background-color: #D9D9D9;
}
// 抽屉head区域---start
.el-drawer__header {
padding-bottom: 20px;
padding-left: 30px;
margin-bottom: 23px;
font-size: 20px;
font-weight: 500;
color: rgba(0,0,0,0.85);
border-bottom: 1px solid rgba(233, 233, 233, 1);
}
.el-drawer__header > :first-child::before {
content: '';
display: inline-block;
width: 4px;
height: 24px;
background-color: #0b58ff;
margin-right: 8px;
vertical-align: middle;
}
// 抽屉head区域---end
// 弹出框上下分布去掉label的padding-bottom
.el-form--label-top .el-form-item__label {
padding: 0;
}
// 大屏滚动表格
.dv-scroll-board .rows .ceil, .dv-scroll-board .header .header-item {
border-right: 1px solid rgba(13, 23, 40, 1);
}
.dv-scroll-board .rows .ceil:last-child, .dv-scroll-board .header .header-item:last-child {
border-right: none;
}

View File

@ -0,0 +1,28 @@
import loadScript from './loadScript'
import ELEMENT from 'element-ui'
import pluginsConfig from './pluginsConfig'
let beautifierObj
export default function loadBeautifier(cb) {
const { beautifierUrl } = pluginsConfig
if (beautifierObj) {
cb(beautifierObj)
return
}
const loading = ELEMENT.Loading.service({
fullscreen: true,
lock: true,
text: '格式化资源加载中...',
spinner: 'el-icon-loading',
background: 'rgba(255, 255, 255, 0.5)'
})
loadScript(beautifierUrl, () => {
loading.close()
// eslint-disable-next-line no-undef
beautifierObj = beautifier
cb(beautifierObj)
})
}

40
src/utils/loadMonaco.js Normal file
View File

@ -0,0 +1,40 @@
import loadScript from './loadScript'
import ELEMENT from 'element-ui'
import pluginsConfig from './pluginsConfig'
// monaco-editor单例
let monacoEidtor
/**
* 动态加载monaco-editor cdn资源
* @param {Function} cb 回调必填
*/
export default function loadMonaco(cb) {
if (monacoEidtor) {
cb(monacoEidtor)
return
}
const { monacoEditorUrl: vs } = pluginsConfig
// 使用element ui实现加载提示
const loading = ELEMENT.Loading.service({
fullscreen: true,
lock: true,
text: '编辑器资源初始化中...',
spinner: 'el-icon-loading',
background: 'rgba(255, 255, 255, 0.5)'
})
!window.require && (window.require = {})
!window.require.paths && (window.require.paths = {})
window.require.paths.vs = vs
loadScript(`${vs}/loader.js`, () => {
window.require(['vs/editor/editor.main'], () => {
loading.close()
monacoEidtor = window.monaco
cb(monacoEidtor)
})
})
}

60
src/utils/loadScript.js Normal file
View File

@ -0,0 +1,60 @@
const callbacks = {}
/**
* 加载一个远程脚本
* @param {String} src 一个远程脚本
* @param {Function} callback 回调
*/
function loadScript(src, callback) {
const existingScript = document.getElementById(src)
const cb = callback || (() => {})
if (!existingScript) {
callbacks[src] = []
const $script = document.createElement('script')
$script.src = src
$script.id = src
$script.async = 1
document.body.appendChild($script)
const onEnd = 'onload' in $script ? stdOnEnd.bind($script) : ieOnEnd.bind($script)
onEnd($script)
}
callbacks[src].push(cb)
function stdOnEnd(script) {
script.onload = () => {
this.onerror = this.onload = null
callbacks[src].forEach(item => {
item(null, script)
})
delete callbacks[src]
}
script.onerror = () => {
this.onerror = this.onload = null
cb(new Error(`Failed to load ${src}`), script)
}
}
function ieOnEnd(script) {
script.onreadystatechange = () => {
if (this.readyState !== 'complete' && this.readyState !== 'loaded') return
this.onreadystatechange = null
callbacks[src].forEach(item => {
item(null, script)
})
delete callbacks[src]
}
}
}
/**
* 顺序加载一组远程脚本
* @param {Array} list 一组远程脚本
* @param {Function} cb 回调
*/
export function loadScriptQueue(list, cb) {
const first = list.shift()
list.length ? loadScript(first, () => loadScriptQueue(list, cb)) : loadScript(first, cb)
}
export default loadScript

29
src/utils/loadTinymce.js Normal file
View File

@ -0,0 +1,29 @@
import loadScript from './loadScript'
import ELEMENT from 'element-ui'
import pluginsConfig from './pluginsConfig'
let tinymceObj
export default function loadTinymce(cb) {
const { tinymceUrl } = pluginsConfig
if (tinymceObj) {
cb(tinymceObj)
return
}
const loading = ELEMENT.Loading.service({
fullscreen: true,
lock: true,
text: '富文本资源加载中...',
spinner: 'el-icon-loading',
background: 'rgba(255, 255, 255, 0.5)'
})
loadScript(tinymceUrl, () => {
loading.close()
// eslint-disable-next-line no-undef
tinymceObj = tinymce
cb(tinymceObj)
})
}

View File

@ -0,0 +1,13 @@
const CDN = 'https://lib.baomitu.com/' // CDN Homepage: https://cdn.baomitu.com/
const publicPath = process.env.BASE_URL
function splicingPluginUrl(PluginName, version, fileName) {
return `${CDN}${PluginName}/${version}/${fileName}`
}
export default {
beautifierUrl: splicingPluginUrl('js-beautify', '1.13.5', 'beautifier.min.js'),
// monacoEditorUrl: splicingPluginUrl('monaco-editor', '0.19.3', 'min/vs'), // 使用 monaco-editor CDN 链接
monacoEditorUrl: `${publicPath}libs/monaco-editor/vs`, // 使用 monaco-editor 本地代码
tinymceUrl: splicingPluginUrl('tinymce', '5.7.0', 'tinymce.min.js')
}

View File

@ -0,0 +1,210 @@
<template>
<div class="app-container">
<doc-alert title="系统日志" url="https://doc.iocoder.cn/system-log/" />
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="用户编号" prop="userId">
<el-input v-model="queryParams.userId" placeholder="请输入用户编号" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="用户类型" prop="userType">
<el-select v-model="queryParams.userType" placeholder="请选择用户类型" clearable>
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.USER_TYPE)"
:key="dict.value" :label="dict.label" :value="dict.value"/>
</el-select>
</el-form-item>
<el-form-item label="应用名" prop="applicationName">
<el-input v-model="queryParams.applicationName" placeholder="请输入应用名" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="请求地址" prop="requestUrl">
<el-input v-model="queryParams.requestUrl" placeholder="请输入请求地址" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="请求时间" prop="beginTime">
<el-date-picker v-model="queryParams.beginTime" style="width: 240px" value-format="yyyy-MM-dd HH:mm:ss" type="daterange"
range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" :default-time="['00:00:00', '23:59:59']" />
</el-form-item>
<el-form-item label="执行时长" prop="duration">
<el-input v-model="queryParams.duration" placeholder="请输入执行时长" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="结果码" prop="resultCode">
<el-input v-model="queryParams.resultCode" 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 :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="warning" plain icon="el-icon-download" size="mini" :loading="exportLoading" @click="handleExport"
v-hasPermi="['infra:api-access-log:export']">导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<!-- 列表 -->
<el-table v-loading="loading" :data="list">
<el-table-column label="日志编号" align="center" prop="id" />
<el-table-column label="用户编号" align="center" prop="userId" />
<el-table-column label="用户类型" align="center" prop="userType">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.USER_TYPE" :value="scope.row.userType"/>
</template>
</el-table-column>>
<el-table-column label="应用名" align="center" prop="applicationName" />
<el-table-column label="请求方法名" align="center" prop="requestMethod" />
<el-table-column label="请求地址" align="center" prop="requestUrl" width="250" />
<el-table-column label="请求时间" align="center" prop="beginTime" width="180">
<template v-slot="scope">
<span>{{ parseTime(scope.row.beginTime) }}</span>
</template>
</el-table-column>
<el-table-column label="执行时长" align="center" prop="startTime">
<template v-slot="scope">
<span>{{ scope.row.duration }} ms</span>
</template>
</el-table-column>
<el-table-column label="操作结果" align="center" prop="status">
<template v-slot="scope">
<span>{{ scope.row.resultCode === 0 ? '成功' : '失败(' + scope.row.resultMsg + ')' }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-view" @click="handleView(scope.row,scope.index)"
v-hasPermi="['infra:api-access-log:query']">详细</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
<!-- 查看明细 -->
<el-dialog title="API 访问日志详细" :visible.sync="open" width="700px" append-to-body>
<el-form ref="form" :model="form" label-width="100px" size="mini">
<el-row>
<el-col :span="24">
<el-form-item label="日志主键:">{{ form.id }}</el-form-item>
<el-form-item label="链路追踪:">{{ form.traceId }}</el-form-item>
<el-form-item label="应用名:">{{ form.applicationName }}</el-form-item>
<el-form-item label="用户信息:">
{{ form.userId }} <dict-tag :type="DICT_TYPE.USER_TYPE" :value="form.userType"/> | {{ form.userIp }} | {{ form.userAgent}}
</el-form-item>
<el-form-item label="请求信息:">{{ form.requestMethod }} | {{ form.requestUrl }} </el-form-item>
<el-form-item label="请求参数:">{{ form.requestParams }}</el-form-item>
<el-form-item label="开始时间:">
{{ parseTime(form.beginTime) }} ~ {{ parseTime(form.endTime) }} | {{ form.duration }} ms
</el-form-item>
<el-form-item label="操作结果:">
<div v-if="form.resultCode === 0">正常</div>
<div v-else-if="form.resultCode > 0">失败 | {{ form.resultCode }} || {{ form.resultMsg}}</div>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="open = false"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { getApiAccessLogPage, exportApiAccessLogExcel } from "@/api/infra/apiAccessLog";
export default {
name: "InfraApiAccessLog",
components: {
},
data() {
return {
//
loading: true,
//
exportLoading: false,
//
showSearch: true,
//
total: 0,
// API 访
list: [],
//
title: "",
//
open: false,
//
queryParams: {
pageNo: 1,
pageSize: 10,
userId: null,
userType: null,
applicationName: null,
requestUrl: null,
duration: null,
resultCode: null,
beginTime: []
},
//
form: {},
};
},
created() {
this.getList();
},
methods: {
/** 查询列表 */
getList() {
this.loading = true;
//
getApiAccessLogPage(this.queryParams).then(response => {
this.list = response.data.list;
this.total = response.data.total;
this.loading = false;
});
},
/** 取消按钮 */
cancel() {
this.open = false;
this.reset();
},
/** 表单重置 */
reset() {
this.form = {};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
/** 详细按钮操作 */
handleView(row) {
this.open = true;
this.form = row;
},
/** 导出按钮操作 */
handleExport() {
//
let params = {...this.queryParams};
params.pageNo = undefined;
params.pageSize = undefined;
//
this.$modal.confirm('是否确认导出所有API 访问日志数据项?').then(() => {
this.exportLoading = true;
return exportApiAccessLogExcel(params);
}).then(response => {
this.$download.excel(response, 'API 访问日志.xls');
this.exportLoading = false;
}).catch(() => {});
}
}
};
</script>

View File

@ -0,0 +1,228 @@
<template>
<div class="app-container">
<doc-alert title="系统日志" url="https://doc.iocoder.cn/system-log/" />
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="用户编号" prop="userId">
<el-input v-model="queryParams.userId" placeholder="请输入用户编号" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="用户类型" prop="userType">
<el-select v-model="queryParams.userType" placeholder="请选择用户类型" clearable>
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.USER_TYPE)"
:key="dict.value" :label="dict.label" :value="dict.value"/>
</el-select>
</el-form-item>
<el-form-item label="应用名" prop="applicationName">
<el-input v-model="queryParams.applicationName" placeholder="请输入应用名" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="请求地址" prop="requestUrl">
<el-input v-model="queryParams.requestUrl" placeholder="请输入请求地址" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="异常时间" prop="exceptionTime">
<el-date-picker v-model="queryParams.exceptionTime" style="width: 240px" value-format="yyyy-MM-dd HH:mm:ss" type="daterange"
range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" :default-time="['00:00:00', '23:59:59']" />
</el-form-item>
<el-form-item label="处理状态" prop="processStatus">
<el-select v-model="queryParams.processStatus" placeholder="请选择处理状态" clearable>
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS)"
:key="dict.value" :label="dict.label" :value="dict.value"/>
</el-select>
</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 type="warning" plain icon="el-icon-download" size="mini" @click="handleExport" :loading="exportLoading"
v-hasPermi="['infra:api-error-log:export']">导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<!-- 列表 -->
<el-table v-loading="loading" :data="list">
<el-table-column label="日志编号" align="center" prop="id" />
<el-table-column label="用户编号" align="center" prop="userId" />
<el-table-column label="用户类型" align="center" prop="userType">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.USER_TYPE" :value="scope.row.userType"/>
</template>
</el-table-column>>
<el-table-column label="应用名" align="center" prop="applicationName" />
<el-table-column label="请求方法名" align="center" prop="requestMethod" />
<el-table-column label="请求地址" align="center" prop="requestUrl" width="250" />
<el-table-column label="异常发生时间" align="center" prop="exceptionTime" width="180">
<template v-slot="scope">
<span>{{ parseTime(scope.row.exceptionTime) }}</span>
</template>
</el-table-column>
<el-table-column label="异常名" align="center" prop="exceptionName" width="250" />
<el-table-column label="处理状态" align="center" prop="processStatus">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS" :value="scope.row.processStatus" />
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-view" @click="handleView(scope.row,scope.index)"
v-hasPermi="['infra:api-access-log:query']">详细</el-button>
<el-button type="text" size="mini" icon="el-icon-check"
v-if="scope.row.processStatus === InfApiErrorLogProcessStatusEnum.INIT" v-hasPermi="['infra:api-error-log:update-status']"
@click="handleProcessClick(scope.row, InfApiErrorLogProcessStatusEnum.DONE)">已处理</el-button>
<el-button type="text" size="mini" icon="el-icon-check"
v-if="scope.row.processStatus === InfApiErrorLogProcessStatusEnum.INIT" v-hasPermi="['infra:api-error-log:update-status']"
@click="handleProcessClick(scope.row, InfApiErrorLogProcessStatusEnum.IGNORE)">已忽略</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
<!-- 查看明细 -->
<el-dialog title="API 异常日志详细" :visible.sync="open" width="1280px" append-to-body>
<el-form ref="form" :model="form" label-width="100px" size="mini">
<el-row>
<el-col :span="24">
<el-form-item label="日志主键:">{{ form.id }}</el-form-item>
<el-form-item label="链路追踪:">{{ form.traceId }}</el-form-item>
<el-form-item label="应用名:">{{ form.applicationName }}</el-form-item>
<el-form-item label="用户信息:">
{{ form.userId }} <dict-tag :type="DICT_TYPE.USER_TYPE" :value="form.userType" /> | {{ form.userIp }} | {{ form.userAgent}}
</el-form-item>
<el-form-item label="请求信息:">{{ form.requestMethod }} | {{ form.requestUrl }} </el-form-item>
<el-form-item label="请求参数:">{{ form.requestParams }}</el-form-item>
<el-form-item label="异常时间:">{{ parseTime(form.exceptionTime) }}</el-form-item>
<el-form-item label="异常名">{{ form.exceptionName }}</el-form-item>
<el-form-item label="异常名">
<el-input type="textarea" :readonly="true" :autosize="{ maxRows: 20}" v-model="form.exceptionStackTrace"></el-input>
</el-form-item>
<el-form-item label="处理状态">
<dict-tag :type="DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS" :value="form.processStatus" />
</el-form-item>
<el-form-item label="处理人">{{ form.processUserId }}</el-form-item>
<el-form-item label="处理时间">{{ parseTime(form.processTime) }}</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="open = false"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { updateApiErrorLogProcess, getApiErrorLogPage, exportApiErrorLogExcel } from "@/api/infra/apiErrorLog";
import { InfraApiErrorLogProcessStatusEnum } from '@/utils/constants'
export default {
name: "InfraApiErrorLog",
components: {
},
data() {
return {
//
loading: true,
//
exportLoading: false,
//
showSearch: true,
//
total: 0,
// API
list: [],
//
title: "",
//
open: false,
//
queryParams: {
pageNo: 1,
pageSize: 10,
userId: null,
userType: null,
applicationName: null,
requestUrl: null,
processStatus: null,
exceptionTime: []
},
//
form: {},
//
InfApiErrorLogProcessStatusEnum: InfraApiErrorLogProcessStatusEnum,
};
},
created() {
this.getList();
},
methods: {
/** 查询列表 */
getList() {
this.loading = true;
//
getApiErrorLogPage(this.queryParams).then(response => {
this.list = response.data.list;
this.total = response.data.total;
this.loading = false;
});
},
/** 取消按钮 */
cancel() {
this.open = false;
this.reset();
},
/** 表单重置 */
reset() {
this.form = {};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
/** 详细按钮操作 */
handleView(row) {
this.open = true;
this.form = row;
},
/** 处理已处理 / 已忽略的操作 **/
handleProcessClick(row, processStatus) {
const processStatusText = this.getDictDataLabel(this.DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS, processStatus)
this.$modal.confirm('确认标记为' + processStatusText).then(() => {
updateApiErrorLogProcess(row.id, processStatus).then(() => {
this.$modal.msgSuccess("修改成功");
this.getList();
});
}).catch(() => {});
},
/** 导出按钮操作 */
handleExport() {
//
let params = {...this.queryParams};
params.pageNo = undefined;
params.pageSize = undefined;
//
this.$modal.confirm('是否确认导出所有API 错误日志数据项?').then(() => {
this.exportLoading = true;
return exportApiErrorLogExcel(params);
}).then(response => {
this.$download.excel(response, 'API 错误日志.xls');
this.exportLoading = false;
}).catch(() => {});
}
}
};
</script>

View File

@ -0,0 +1,22 @@
<template>
<div>
<router-view />
</div>
</template>
<script>
export default {
mounted() {
// loading
const preLoader = document.querySelector('#pre-loader')
preLoader.style.display = 'none'
// fix: firefox
// https://github.com/JakHuang/form-generator/issues/15
document.body.ondrop = event => {
event.preventDefault()
event.stopPropagation()
}
}
}
</script>

View File

@ -0,0 +1,110 @@
<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="handelConfirm">
确定
</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)
},
handelConfirm() {
this.$refs.elForm.validate(valid => {
if (!valid) return
this.$emit('confirm', { ...this.formData })
this.close()
})
}
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,120 @@
<script>
import draggable from 'vuedraggable'
import render from '@/components/render/render'
const components = {
itemBtns(h, currentItem, index, list) {
const { copyItem, deleteItem } = this.$listeners
return [
<span class="drawing-item-copy" title="复制" onClick={event => {
copyItem(currentItem, list); event.stopPropagation()
}}>
<i class="el-icon-copy-document" />
</span>,
<span class="drawing-item-delete" title="删除" onClick={event => {
deleteItem(index, list); event.stopPropagation()
}}>
<i class="el-icon-delete" />
</span>
]
}
}
const layouts = {
colFormItem(h, currentItem, index, list) {
const { activeItem } = this.$listeners
const config = currentItem.__config__
const child = renderChildren.apply(this, arguments)
let className = this.activeId === config.formId ? 'drawing-item active-from-item' : 'drawing-item'
if (this.formConf.unFocusedComponentBorder) className += ' unfocus-bordered'
let labelWidth = config.labelWidth ? `${config.labelWidth}px` : null
if (config.showLabel === false) labelWidth = '0'
return (
<el-col span={config.span} class={className}
nativeOnClick={event => { activeItem(currentItem); event.stopPropagation() }}>
<el-form-item label-width={labelWidth}
label={config.showLabel ? config.label : ''} required={config.required}>
<render key={config.renderKey} conf={currentItem} onInput={ event => {
this.$set(config, 'defaultValue', event)
}}>
{child}
</render>
</el-form-item>
{components.itemBtns.apply(this, arguments)}
</el-col>
)
},
rowFormItem(h, currentItem, index, list) {
const { activeItem } = this.$listeners
const config = currentItem.__config__
const className = this.activeId === config.formId
? 'drawing-row-item active-from-item'
: 'drawing-row-item'
let child = renderChildren.apply(this, arguments)
if (currentItem.type === 'flex') {
child = <el-row type={currentItem.type} justify={currentItem.justify} align={currentItem.align}>
{child}
</el-row>
}
return (
<el-col span={config.span}>
<el-row gutter={config.gutter} class={className}
nativeOnClick={event => { activeItem(currentItem); event.stopPropagation() }}>
<span class="component-name">{config.componentName}</span>
<draggable list={config.children || []} animation={340}
group="componentsGroup" class="drag-wrapper">
{child}
</draggable>
{components.itemBtns.apply(this, arguments)}
</el-row>
</el-col>
)
},
raw(h, currentItem, index, list) {
const config = currentItem.__config__
const child = renderChildren.apply(this, arguments)
return <render key={config.renderKey} conf={currentItem} onInput={ event => {
this.$set(config, 'defaultValue', event)
}}>
{child}
</render>
}
}
function renderChildren(h, currentItem, index, list) {
const config = currentItem.__config__
if (!Array.isArray(config.children)) return null
return config.children.map((el, i) => {
const layout = layouts[el.__config__.layout]
if (layout) {
return layout.call(this, h, el, i, config.children)
}
return layoutIsNotFound.call(this)
})
}
function layoutIsNotFound() {
throw new Error(`没有与${this.currentItem.__config__.layout}匹配的layout`)
}
export default {
components: {
render,
draggable
},
props: [
'currentItem',
'index',
'drawingList',
'activeId',
'formConf'
],
render(h) {
const layout = layouts[this.currentItem.__config__.layout]
if (layout) {
return layout.call(this, h, this.currentItem, this.index, this.drawingList)
}
return layoutIsNotFound.call(this)
}
}
</script>

View File

@ -0,0 +1,331 @@
<template>
<div>
<el-drawer v-bind="$attrs" v-on="$listeners" @opened="onOpen" @close="onClose">
<div style="height:100%">
<el-row style="height:100%;overflow:auto">
<el-col :md="24" :lg="12" class="left-editor">
<div class="setting" title="资源引用" @click="showResource">
<el-badge :is-dot="!!resources.length" class="item">
<i class="el-icon-setting" />
</el-badge>
</div>
<el-tabs v-model="activeTab" type="card" class="editor-tabs">
<el-tab-pane name="html">
<span slot="label">
<i v-if="activeTab==='html'" class="el-icon-edit" />
<i v-else class="el-icon-document" />
template
</span>
</el-tab-pane>
<el-tab-pane name="js">
<span slot="label">
<i v-if="activeTab==='js'" class="el-icon-edit" />
<i v-else class="el-icon-document" />
script
</span>
</el-tab-pane>
<el-tab-pane name="css">
<span slot="label">
<i v-if="activeTab==='css'" class="el-icon-edit" />
<i v-else class="el-icon-document" />
css
</span>
</el-tab-pane>
</el-tabs>
<div v-show="activeTab==='html'" id="editorHtml" class="tab-editor" />
<div v-show="activeTab==='js'" id="editorJs" class="tab-editor" />
<div v-show="activeTab==='css'" id="editorCss" class="tab-editor" />
</el-col>
<el-col :md="24" :lg="12" class="right-preview">
<div class="action-bar" :style="{'text-align': 'left'}">
<span class="bar-btn" @click="runCode">
<i class="el-icon-refresh" />
刷新
</span>
<span class="bar-btn" @click="exportFile">
<i class="el-icon-download" />
导出vue文件
</span>
<span ref="copyBtn" class="bar-btn copy-btn">
<i class="el-icon-document-copy" />
复制代码
</span>
<span class="bar-btn delete-btn" @click="$emit('update:visible', false)">
<i class="el-icon-circle-close" />
关闭
</span>
</div>
<iframe
v-show="isIframeLoaded"
ref="previewPage"
class="result-wrapper"
frameborder="0"
src="preview.html"
@load="iframeLoad"
/>
<div v-show="!isIframeLoaded" v-loading="true" class="result-wrapper" />
</el-col>
</el-row>
</div>
</el-drawer>
<resource-dialog
:visible.sync="resourceVisible"
:origin-resource="resources"
@save="setResource"
/>
</div>
</template>
<script>
import { parse } from '@babel/parser'
import ClipboardJS from 'clipboard'
import { saveAs } from 'file-saver'
import {
makeUpHtml, vueTemplate, vueScript, cssStyle
} from '@/components/generator/html'
import { makeUpJs } from '@/components/generator/js'
import { makeUpCss } from '@/components/generator/css'
import { exportDefault, beautifierConf } from '@/utils'
import ResourceDialog from './ResourceDialog'
import loadMonaco from '@/utils/loadMonaco'
import loadBeautifier from '@/utils/loadBeautifier'
const editorObj = {
html: null,
js: null,
css: null
}
const mode = {
html: 'html',
js: 'javascript',
css: 'css'
}
let beautifier
let monaco
export default {
components: { ResourceDialog },
props: ['formData', 'generateConf'],
data() {
return {
activeTab: 'html',
htmlCode: '',
jsCode: '',
cssCode: '',
codeFrame: '',
isIframeLoaded: false,
isInitcode: false, // openruncode
isRefreshCode: false, //
resourceVisible: false,
scripts: [],
links: [],
monaco: null
}
},
computed: {
resources() {
return this.scripts.concat(this.links)
}
},
watch: {},
created() {
},
mounted() {
window.addEventListener('keydown', this.preventDefaultSave)
const clipboard = new ClipboardJS('.copy-btn', {
text: trigger => {
const codeStr = this.generateCode()
this.$notify({
title: '成功',
message: '代码已复制到剪切板,可粘贴。',
type: 'success'
})
return codeStr
}
})
clipboard.on('error', e => {
this.$message.error('代码复制失败')
})
},
beforeDestroy() {
window.removeEventListener('keydown', this.preventDefaultSave)
},
methods: {
preventDefaultSave(e) {
if (e.key === 's' && (e.metaKey || e.ctrlKey)) {
e.preventDefault()
}
},
onOpen() {
const { type } = this.generateConf
this.htmlCode = makeUpHtml(this.formData, type)
this.jsCode = makeUpJs(this.formData, type)
this.cssCode = makeUpCss(this.formData)
loadBeautifier(btf => {
beautifier = btf
this.htmlCode = beautifier.html(this.htmlCode, beautifierConf.html)
this.jsCode = beautifier.js(this.jsCode, beautifierConf.js)
this.cssCode = beautifier.css(this.cssCode, beautifierConf.html)
loadMonaco(val => {
monaco = val
this.setEditorValue('editorHtml', 'html', this.htmlCode)
this.setEditorValue('editorJs', 'js', this.jsCode)
this.setEditorValue('editorCss', 'css', this.cssCode)
if (!this.isInitcode) {
this.isRefreshCode = true
this.isIframeLoaded && (this.isInitcode = true) && this.runCode()
}
})
})
},
onClose() {
this.isInitcode = false
this.isRefreshCode = false
},
iframeLoad() {
if (!this.isInitcode) {
this.isIframeLoaded = true
this.isRefreshCode && (this.isInitcode = true) && this.runCode()
}
},
setEditorValue(id, type, codeStr) {
if (editorObj[type]) {
editorObj[type].setValue(codeStr)
} else {
editorObj[type] = monaco.editor.create(document.getElementById(id), {
value: codeStr,
theme: 'vs-dark',
language: mode[type],
automaticLayout: true
})
}
// ctrl + s
editorObj[type].onKeyDown(e => {
if (e.keyCode === 49 && (e.metaKey || e.ctrlKey)) {
this.runCode()
}
})
},
runCode() {
const jsCodeStr = editorObj.js.getValue()
try {
const ast = parse(jsCodeStr, { sourceType: 'module' })
const astBody = ast.program.body
if (astBody.length > 1) {
this.$confirm(
'js格式不能识别仅支持修改export default的对象内容',
'提示',
{
type: 'warning'
}).catch(() => {});
return
}
if (astBody[0].type === 'ExportDefaultDeclaration') {
const postData = {
type: 'refreshFrame',
data: {
generateConf: this.generateConf,
html: editorObj.html.getValue(),
js: jsCodeStr.replace(exportDefault, ''),
css: editorObj.css.getValue(),
scripts: this.scripts,
links: this.links
}
}
this.$refs.previewPage.contentWindow.postMessage(
postData,
location.origin
)
} else {
this.$message.error('请使用export default')
}
} catch (err) {
this.$message.error(`js错误${err}`)
console.error(err)
}
},
generateCode() {
const html = vueTemplate(editorObj.html.getValue())
const script = vueScript(editorObj.js.getValue())
const css = cssStyle(editorObj.css.getValue())
return beautifier.html(html + script + css, beautifierConf.html)
},
exportFile() {
this.$prompt('文件名:', '导出文件', {
inputValue: `${+new Date()}.vue`,
closeOnClickModal: false,
inputPlaceholder: '请输入文件名'
}).then(({ value }) => {
if (!value) value = `${+new Date()}.vue`
const codeStr = this.generateCode()
const blob = new Blob([codeStr], { type: 'text/plain;charset=utf-8' })
saveAs(blob, value)
})
},
showResource() {
this.resourceVisible = true
},
setResource(arr) {
const scripts = []; const
links = []
if (Array.isArray(arr)) {
arr.forEach(item => {
if (item.endsWith('.css')) {
links.push(item)
} else {
scripts.push(item)
}
})
this.scripts = scripts
this.links = links
} else {
this.scripts = []
this.links = []
}
}
}
}
</script>
<style lang="scss" scoped>
@import '@/styles/mixin.scss';
.tab-editor {
position: absolute;
top: 33px;
bottom: 0;
left: 0;
right: 0;
font-size: 14px;
}
.left-editor {
position: relative;
height: 100%;
background: #1e1e1e;
overflow: hidden;
}
.setting{
position: absolute;
right: 15px;
top: 3px;
color: #a9f122;
font-size: 18px;
cursor: pointer;
z-index: 1;
}
.right-preview {
height: 100%;
.result-wrapper {
height: calc(100vh - 33px);
width: 100%;
overflow: auto;
padding: 12px;
box-sizing: border-box;
}
}
@include action-bar;
:deep(.el-drawer__header) {
display: none;
}
</style>

View File

@ -0,0 +1,123 @@
<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"
size="mini"
: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/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 {
: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>

View File

@ -0,0 +1,144 @@
<template>
<div>
<el-drawer v-bind="$attrs" v-on="$listeners" @opened="onOpen" @close="onClose">
<div class="action-bar" :style="{'text-align': 'left'}">
<span class="bar-btn" @click="refresh">
<i class="el-icon-refresh" />
刷新
</span>
<span ref="copyBtn" class="bar-btn copy-json-btn">
<i class="el-icon-document-copy" />
复制JSON
</span>
<span class="bar-btn" @click="exportJsonFile">
<i class="el-icon-download" />
导出JSON文件
</span>
<span class="bar-btn delete-btn" @click="$emit('update:visible', false)">
<i class="el-icon-circle-close" />
关闭
</span>
</div>
<div id="editorJson" class="json-editor" />
</el-drawer>
</div>
</template>
<script>
import { beautifierConf } from '@/utils'
import ClipboardJS from 'clipboard'
import { saveAs } from 'file-saver'
import loadMonaco from '@/utils/loadMonaco'
import loadBeautifier from '@/utils/loadBeautifier'
let beautifier
let monaco
export default {
components: {},
props: {
jsonStr: {
type: String,
required: true
}
},
data() {
return {}
},
computed: {},
watch: {},
created() {},
mounted() {
window.addEventListener('keydown', this.preventDefaultSave)
const clipboard = new ClipboardJS('.copy-json-btn', {
text: trigger => {
this.$notify({
title: '成功',
message: '代码已复制到剪切板,可粘贴。',
type: 'success'
})
return this.beautifierJson
}
})
clipboard.on('error', e => {
this.$message.error('代码复制失败')
})
},
beforeDestroy() {
window.removeEventListener('keydown', this.preventDefaultSave)
},
methods: {
preventDefaultSave(e) {
if (e.key === 's' && (e.metaKey || e.ctrlKey)) {
e.preventDefault()
}
},
onOpen() {
loadBeautifier(btf => {
beautifier = btf
this.beautifierJson = beautifier.js(this.jsonStr, beautifierConf.js)
loadMonaco(val => {
monaco = val
this.setEditorValue('editorJson', this.beautifierJson)
})
})
},
onClose() {},
setEditorValue(id, codeStr) {
if (this.jsonEditor) {
this.jsonEditor.setValue(codeStr)
} else {
this.jsonEditor = monaco.editor.create(document.getElementById(id), {
value: codeStr,
theme: 'vs-dark',
language: 'json',
automaticLayout: true
})
// ctrl + s
this.jsonEditor.onKeyDown(e => {
if (e.keyCode === 49 && (e.metaKey || e.ctrlKey)) {
this.refresh()
}
})
}
},
exportJsonFile() {
this.$prompt('文件名:', '导出文件', {
inputValue: `${+new Date()}.json`,
closeOnClickModal: false,
inputPlaceholder: '请输入文件名'
}).then(({ value }) => {
if (!value) value = `${+new Date()}.json`
const codeStr = this.jsonEditor.getValue()
const blob = new Blob([codeStr], { type: 'text/plain;charset=utf-8' })
saveAs(blob, value)
})
},
refresh() {
try {
this.$emit('refresh', JSON.parse(this.jsonEditor.getValue()))
} catch (error) {
this.$notify({
title: '错误',
message: 'JSON格式错误请检查',
type: 'error'
})
}
}
}
}
</script>
<style lang="scss" scoped>
@import '@/styles/mixin.scss';
:deep(.el-drawer__header) {
display: none;
}
@include action-bar;
.json-editor{
height: calc(100vh - 33px);
}
</style>

View File

View File

@ -0,0 +1,116 @@
<template>
<div>
<el-dialog
v-bind="$attrs"
title="外部资源引用"
width="600px"
:close-on-click-modal="false"
v-on="$listeners"
@open="onOpen"
@close="onClose"
>
<el-input
v-for="(item, index) in resources"
:key="index"
v-model="resources[index]"
class="url-item"
placeholder="请输入 css 或 js 资源路径"
prefix-icon="el-icon-link"
clearable
>
<el-button
slot="append"
icon="el-icon-delete"
@click="deleteOne(index)"
/>
</el-input>
<el-button-group class="add-item">
<el-button
plain
@click="addOne('https://lib.baomitu.com/jquery/1.8.3/jquery.min.js')"
>
jQuery1.8.3
</el-button>
<el-button
plain
@click="addOne('https://unpkg.com/http-vue-loader')"
>
http-vue-loader
</el-button>
<el-button
icon="el-icon-circle-plus-outline"
plain
@click="addOne('')"
>
添加其他
</el-button>
</el-button-group>
<div slot="footer">
<el-button @click="close">
取消
</el-button>
<el-button
type="primary"
@click="handelConfirm"
>
确定
</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { deepClone } from '@/utils'
export default {
components: {},
inheritAttrs: false,
props: ['originResource'],
data() {
return {
resources: null
}
},
computed: {},
watch: {},
created() {},
mounted() {},
methods: {
onOpen() {
this.resources = this.originResource.length ? deepClone(this.originResource) : ['']
},
onClose() {
},
close() {
this.$emit('update:visible', false)
},
handelConfirm() {
const results = this.resources.filter(item => !!item) || []
this.$emit('save', results)
this.close()
if (results.length) {
this.resources = results
}
},
deleteOne(index) {
this.resources.splice(index, 1)
},
addOne(url) {
if (this.resources.indexOf(url) > -1) {
this.$message('资源已存在')
} else {
this.resources.push(url)
}
}
}
}
</script>
<style lang="scss" scoped>
.add-item{
margin-top: 8px;
}
.url-item{
margin-bottom: 12px;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,158 @@
<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="handelConfirm"
>
确定
</el-button>
<el-button @click="close">
取消
</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { isNumberStr } from '@/utils'
import { getTreeNodeId, saveTreeNodeId } from '@/utils/db'
const id = getTreeNodeId()
export default {
components: {},
inheritAttrs: false,
props: [],
data() {
return {
id,
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'
},
id(val) {
saveTreeNodeId(val)
}
},
created() {},
mounted() {},
methods: {
onOpen() {
this.formData = {
label: undefined,
value: undefined
}
},
onClose() {},
close() {
this.$emit('update:visible', false)
},
handelConfirm() {
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>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,470 @@
<template>
<div class="container">
<div class="left-board">
<div class="logo-wrapper">
<div class="logo">
<img :src="logo" alt="logo"> Form Generator
<a class="github" href="https://github.com/JakHuang/form-generator" target="_blank">
<img src="https://github.githubassets.com/pinned-octocat.svg" alt>
</a>
</div>
</div>
<el-scrollbar class="left-scrollbar">
<div class="components-list">
<div v-for="(item, listIndex) in leftComponents" :key="listIndex">
<div class="components-title">
<svg-icon icon-class="component" />
{{ item.title }}
</div>
<draggable
class="components-draggable"
:list="item.list"
:group="{ name: 'componentsGroup', pull: 'clone', put: false }"
:clone="cloneComponent"
draggable=".components-item"
:sort="false"
@end="onEnd"
>
<div
v-for="(element, index) in item.list"
:key="index"
class="components-item"
@click="addComponent(element)"
>
<div class="components-body">
<svg-icon :icon-class="element.__config__.tagIcon" />
{{ element.__config__.label }}
</div>
</div>
</draggable>
</div>
</div>
</el-scrollbar>
</div>
<div class="center-board">
<div class="action-bar">
<!-- <el-button icon="el-icon-video-play" type="text" @click="run">-->
<!-- 运行-->
<!-- </el-button>-->
<el-button icon="el-icon-view" type="text" @click="showJson">
查看json
</el-button>
<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="(item, index) in drawingList"
:key="item.renderKey"
:drawing-list="drawingList"
:current-item="item"
: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"
@fetch-data="fetchData"
/>
<form-drawer
:visible.sync="drawerVisible"
:form-data="formData"
size="100%"
:generate-conf="generateConf"
/>
<json-drawer
size="60%"
:visible.sync="jsonDrawerVisible"
:json-str="JSON.stringify(formData)"
@refresh="refreshJson"
/>
<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 { debounce } from 'throttle-debounce'
import { saveAs } from 'file-saver'
import ClipboardJS from 'clipboard'
import render from '@/components/render/render'
import FormDrawer from './FormDrawer'
import JsonDrawer from './JsonDrawer'
import RightPanel from './RightPanel'
import {
inputComponents, selectComponents, layoutComponents, formConf
} from '@/components/generator/config'
import {
beautifierConf, titleCase, deepClone
} from '@/utils'
import {
makeUpHtml, vueTemplate, vueScript, cssStyle
} from '@/components/generator/html'
import { makeUpJs } from '@/components/generator/js'
import { makeUpCss } from '@/components/generator/css'
import drawingDefalut from '@/components/generator/drawingDefalut'
import logo from '@/assets/logo/logo.png'
import CodeTypeDialog from './CodeTypeDialog'
import DraggableItem from './DraggableItem'
import {
getDrawingList, saveDrawingList, getIdGlobal, saveIdGlobal, getFormConf
} from '@/utils/db'
import loadBeautifier from '@/utils/loadBeautifier'
let beautifier
const emptyActiveData = { style: {}, autosize: {} }
let oldActiveId
let tempActiveData
const drawingListInDB = getDrawingList()
const formConfInDB = getFormConf()
const idGlobal = getIdGlobal()
export default {
name: "InfraBuild",
components: {
draggable,
render,
FormDrawer,
JsonDrawer,
RightPanel,
CodeTypeDialog,
DraggableItem
},
data() {
return {
logo,
idGlobal,
formConf,
inputComponents,
selectComponents,
layoutComponents,
labelWidth: 100,
drawingList: drawingDefalut,
drawingData: {},
activeId: drawingDefalut[0].formId,
drawerVisible: false,
formData: {},
dialogVisible: false,
jsonDrawerVisible: false,
generateConf: null,
showFileName: false,
activeData: drawingDefalut[0],
saveDrawingListDebounce: debounce(340, saveDrawingList),
saveIdGlobalDebounce: debounce(340, saveIdGlobal),
leftComponents: [
{
title: '输入型组件',
list: inputComponents
},
{
title: '选择型组件',
list: selectComponents
},
{
title: '布局型组件',
list: layoutComponents
}
]
}
},
computed: {
},
watch: {
// eslint-disable-next-line func-names
'activeData.__config__.label': function (val, oldVal) {
if (
this.activeData.placeholder === undefined
|| !this.activeData.__config__.tag
|| oldActiveId !== this.activeId
) {
return
}
this.activeData.placeholder = this.activeData.placeholder.replace(oldVal, '') + val
},
activeId: {
handler(val) {
oldActiveId = val
},
immediate: true
},
drawingList: {
handler(val) {
this.saveDrawingListDebounce(val)
if (val.length === 0) this.idGlobal = 100
},
deep: true
},
idGlobal: {
handler(val) {
this.saveIdGlobalDebounce(val)
},
immediate: true
}
},
mounted() {
if (Array.isArray(drawingListInDB) && drawingListInDB.length > 0) {
this.drawingList = drawingListInDB
} else {
this.drawingList = drawingDefalut
}
this.activeFormItem(this.drawingList[0])
if (formConfInDB) {
this.formConf = formConfInDB
}
loadBeautifier(btf => {
beautifier = btf
})
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: {
setObjectValueReduce(obj, strKeys, data) {
const arr = strKeys.split('.')
arr.reduce((pre, item, i) => {
if (arr.length === i + 1) {
pre[item] = data
} else if (pre[item]===undefined) {
pre[item] = {}
}
return pre[item]
}, obj)
},
setRespData(component, resp) {
const { dataPath, renderKey, dataConsumer } = component.__config__
if (!dataPath || !dataConsumer) return
const respData = dataPath.split('.').reduce((pre, item) => pre[item], resp)
//
// el-tabelElementel-tabeldatadataConsumer'data';
// component[dataConsumer] = respData
// dataConsumer'options.data',使setObjectValueReduce
this.setObjectValueReduce(component, dataConsumer, respData)
const i = this.drawingList.findIndex(item => item.__config__.renderKey === renderKey)
if (i > -1) this.$set(this.drawingList, i, component)
},
fetchData(component) {
const { dataType, method, url } = component.__config__
if (dataType === 'dynamic' && method && url) {
this.setLoading(component, true)
this.$axios({
method,
url
}).then(resp => {
this.setLoading(component, false)
this.setRespData(component, resp)
})
}
},
setLoading(component, val) {
const { directives } = component
if (Array.isArray(directives)) {
const t = directives.find(d => d.name === 'loading')
if (t) t.value = val
}
},
activeFormItem(currentItem) {
this.activeData = currentItem
this.activeId = currentItem.__config__.formId
},
onEnd(obj) {
if (obj.from !== obj.to) {
this.fetchData(tempActiveData)
this.activeData = tempActiveData
this.activeId = this.idGlobal
}
},
addComponent(item) {
const clone = this.cloneComponent(item)
this.fetchData(clone)
this.drawingList.push(clone)
this.activeFormItem(clone)
},
cloneComponent(origin) {
const clone = deepClone(origin)
const config = clone.__config__
config.span = this.formConf.span // span
this.createIdAndKey(clone)
clone.placeholder !== undefined && (clone.placeholder += config.label)
tempActiveData = clone
return tempActiveData
},
createIdAndKey(item) {
const config = item.__config__
config.formId = ++this.idGlobal
config.renderKey = `${config.formId}${+new Date()}` // renderKey
if (config.layout === 'colFormItem') {
item.__vModel__ = `field${this.idGlobal}`
} else if (config.layout === 'rowFormItem') {
config.componentName = `row${this.idGlobal}`
!Array.isArray(config.children) && (config.children = [])
delete config.label // rowFormItemlabel
}
if (Array.isArray(config.children)) {
config.children = config.children.map(childItem => this.createIdAndKey(childItem))
}
return item
},
AssembleFormData() {
this.formData = {
fields: deepClone(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' })
saveAs(blob, data.fileName)
},
execCopy(data) {
document.getElementById('copyNode').click()
},
empty() {
this.$confirm('确定要清空所有组件吗?', '提示', { type: 'warning' }).then(
() => {
this.drawingList = []
this.idGlobal = 100
}).catch(() => {});
},
drawingItemCopy(item, list) {
let clone = deepClone(item)
clone = this.createIdAndKey(clone)
list.push(clone)
this.activeFormItem(clone)
},
drawingItemDelete(index, list) {
list.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)
},
showJson() {
this.AssembleFormData()
this.jsonDrawerVisible = true
},
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)
const config = newTag.__config__
newTag.__vModel__ = this.activeData.__vModel__
config.formId = this.activeId
config.span = this.activeData.__config__.span
this.activeData.__config__.tag = config.tag
this.activeData.__config__.tagIcon = config.tagIcon
this.activeData.__config__.document = config.document
if (typeof this.activeData.__config__.defaultValue === typeof config.defaultValue) {
config.defaultValue = this.activeData.__config__.defaultValue
}
Object.keys(newTag).forEach(key => {
if (this.activeData[key] !== undefined) {
newTag[key] = this.activeData[key]
}
})
this.activeData = newTag
this.updateDrawingList(newTag, this.drawingList)
},
updateDrawingList(newTag, list) {
const index = list.findIndex(item => item.__config__.formId === this.activeId)
if (index > -1) {
list.splice(index, 1, newTag)
} else {
list.forEach(item => {
if (Array.isArray(item.__config__.children)) this.updateDrawingList(newTag, item.__config__.children)
})
}
},
refreshJson(data) {
this.drawingList = deepClone(data.fields)
delete data.fields
this.formConf = data
}
}
}
</script>
<style lang='scss'>
@import '@/styles/home';
</style>

View File

@ -0,0 +1,19 @@
import Vue from 'vue'
import App from './App.vue'
import router from '@/router'
import '@/styles/index.scss'
import '@/assets/icons'
import axios from 'axios'
import Tinymce from '@/components/tinymce/index.vue'
Vue.component('tinymce', Tinymce)
Vue.config.productionTip = false
Vue.prototype.$axios = axios
// add by 芋道源码:引用自 https://github.com/JakHuang/form-generator/tree/dev/src/views/index
new Vue({
router,
render: h => h(App)
}).$mount('#app')

View File

@ -0,0 +1,67 @@
<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 placeholder="请输入仓库名称" v-model="info.tableName" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="表描述" prop="tableComment">
<el-input placeholder="请输入" v-model="info.tableComment" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="className">
<span slot="label">
实体类名称
<el-tooltip content="默认去除表名的前缀。如果存在重复,则需要手动添加前缀,避免 MyBatis 报 Alias 重复的问题。" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
</span>
<el-input placeholder="请输入" v-model="info.className" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="作者" prop="author">
<el-input placeholder="请输入" v-model="info.author" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="备注" prop="remark">
<el-input type="textarea" :rows="3" v-model="info.remark"></el-input>
</el-form-item>
</el-col>
</el-row>
</el-form>
</template>
<script>
export default {
name: "BasicInfoForm",
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" }
],
author: [
{ required: true, message: "请输入作者", trigger: "blur" }
]
}
};
}
};
</script>

View File

@ -0,0 +1,235 @@
<template>
<el-card>
<el-tabs v-model="activeName">
<el-tab-pane label="基本信息" name="basic">
<basic-info-form ref="basicInfo" :info="table" />
</el-tab-pane>
<el-tab-pane label="字段信息" name="cloum">
<el-table ref="dragTable" :data="columns" row-key="columnId" :max-height="tableHeight">
<el-table-column
label="字段列名"
prop="columnName"
min-width="10%"
:show-overflow-tooltip="true"
/>
<el-table-column label="字段描述" min-width="10%">
<template v-slot="scope">
<el-input v-model="scope.row.columnComment"></el-input>
</template>
</el-table-column>
<el-table-column
label="物理类型"
prop="dataType"
min-width="10%"
:show-overflow-tooltip="true"
/>
<el-table-column label="Java类型" min-width="11%">
<template v-slot="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="LocalDateTime" value="LocalDateTime" />
<el-option label="Boolean" value="Boolean" />
</el-select>
</template>
</el-table-column>
<el-table-column label="java属性" min-width="10%">
<template v-slot="scope">
<el-input v-model="scope.row.javaField"></el-input>
</template>
</el-table-column>
<el-table-column label="插入" min-width="4%">
<template v-slot="scope">
<el-checkbox true-label="true" false-label="false" v-model="scope.row.createOperation"></el-checkbox>
</template>
</el-table-column>
<el-table-column label="编辑" min-width="4%">
<template v-slot="scope">
<el-checkbox true-label="true" false-label="false" v-model="scope.row.updateOperation"></el-checkbox>
</template>
</el-table-column>
<el-table-column label="列表" min-width="4%">
<template v-slot="scope">
<el-checkbox true-label="true" false-label="false" v-model="scope.row.listOperationResult"></el-checkbox>
</template>
</el-table-column>
<el-table-column label="查询" min-width="4%">
<template v-slot="scope">
<el-checkbox true-label="true" false-label="false" v-model="scope.row.listOperation"></el-checkbox>
</template>
</el-table-column>
<el-table-column label="查询方式" min-width="10%">
<template v-slot="scope">
<el-select v-model="scope.row.listOperationCondition">
<el-option label="=" value="=" />
<el-option label="!=" value="!=" />
<el-option label=">" value=">" />
<el-option label=">=" value=">=" />
<el-option label="<" value="<>" />
<el-option label="<=" value="<=" />
<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 v-slot="scope">
<el-checkbox true-label="true" false-label="false" v-model="scope.row.nullable"></el-checkbox>
</template>
</el-table-column>
<el-table-column label="显示类型" min-width="12%">
<template v-slot="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 v-slot="scope">
<el-select v-model="scope.row.dictType" clearable filterable placeholder="请选择">
<el-option
v-for="dict in dictOptions"
:key="dict.id"
:label="dict.name"
:value="dict.type"
/>
</el-select>
</template>
</el-table-column>
<el-table-column label="示例" min-width="10%">
<template v-slot="scope">
<el-input v-model="scope.row.example"></el-input>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="生成信息" name="genInfo">
<gen-info-form ref="genInfo" :info="table" :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 { getCodegenDetail, updateCodegen } from "@/api/infra/codegen";
import { listAllSimple as listAllSimpleDictType } from "@/api/system/dict/type";
import { listSimpleMenus } 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: "cloum",
//
tableHeight: document.documentElement.scrollHeight - 245 + "px",
//
tables: [],
//
columns: [],
//
dictOptions: [],
//
menus: [],
//
table: {}
};
},
created() {
const tableId = this.$route.params && this.$route.params.tableId;
if (tableId) {
//
getCodegenDetail(tableId).then(res => {
this.table = res.data.table;
this.columns = res.data.columns;
});
/** 查询字典下拉列表 */
listAllSimpleDictType().then(response => {
this.dictOptions = response.data;
});
/** 查询菜单下拉列表 */
listSimpleMenus().then(response => {
this.menus = [];
this.menus.push(...this.handleTree(response.data, "id"));
});
}
},
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 = {};
genTable.table = Object.assign({}, basicForm.model, genForm.model);
genTable.columns = this.columns;
genTable.params = {
treeCode: genTable.treeCode,
treeName: genTable.treeName,
treeParentCode: genTable.treeParentCode,
parentMenuId: genTable.parentMenuId
};
updateCodegen(genTable).then(res => {
this.$modal.msgSuccess("修改成功!");
this.close();
});
} else {
this.$modal.msgError("表单校验未通过,请重新检查提交内容");
}
});
},
getFormPromise(form) {
return new Promise(resolve => {
form.validate(res => {
resolve(res);
});
});
},
/** 关闭按钮 */
close() {
this.$tab.closeOpenPage({
path: "/infra/codegen",
query: { t: Date.now(), pageNum: this.$route.query.pageNum } }
);
}
},
mounted() {
const el = this.$refs.dragTable.$el.querySelectorAll(".el-table__body-wrapper > table > tbody")[0];
const sortable = Sortable.create(el, {
handle: ".allowDrag",
onEnd: evt => {
const targetRow = this.columns.splice(evt.oldIndex, 1)[0];
this.columns.splice(evt.newIndex, 0, targetRow);
for (let index in this.columns) {
this.columns[index].sort = parseInt(index) + 1;
}
}
});
}
};
</script>

View File

@ -0,0 +1,339 @@
<template>
<el-form ref="genInfoForm" :model="info" :rules="rules" label-width="150px">
<el-row>
<el-col :span="12">
<el-form-item prop="templateType">
<span slot="label">生成模板</span>
<el-select v-model="info.templateType" @change="tplSelectChange">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.INFRA_CODEGEN_TEMPLATE_TYPE)"
:key="parseInt(dict.value)" :label="dict.label" :value="parseInt(dict.value)"/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="templateType">
<span slot="label">前端类型</span>
<el-select v-model="info.frontType">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.INFRA_CODEGEN_FRONT_TYPE)"
:key="parseInt(dict.value)" :label="dict.label" :value="parseInt(dict.value)"/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="scene">
<span slot="label">生成场景</span>
<el-select v-model="info.scene">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.INFRA_CODEGEN_SCENE)"
:key="parseInt(dict.value)" :label="dict.label" :value="parseInt(dict.value)"/>
</el-select>
</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"></i>
</el-tooltip>
</span>
<treeselect :append-to-body="true" v-model="info.parentMenuId" :options="menus"
:normalizer="normalizer" :show-count="true" placeholder="请选择系统菜单" />
</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"></i>-->
<!-- </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、infra、tool 等等" placement="top">
<i class="el-icon-question"></i>
</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、permission、dict 等等" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
</span>
<el-input v-model="info.businessName" />
</el-form-item>
</el-col>
<!-- <el-col :span="12">-->
<!-- <el-form-item prop="businessPackage">-->
<!-- <span slot="label">-->
<!-- 业务包-->
<!-- <el-tooltip content="业务包,自定义二级目录。例如说,我们希望将 dictType 和 dictData 归类成 dict 业务" placement="top">-->
<!-- <i class="el-icon-question"></i>-->
<!-- </el-tooltip>-->
<!-- </span>-->
<!-- <el-input v-model="info.businessPackage" />-->
<!-- </el-form-item>-->
<!-- </el-col>-->
<el-col :span="12">
<el-form-item prop="className">
<span slot="label">
类名称
<el-tooltip content="类名称首字母大写例如SysUser、SysMenu、SysDictData 等等" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
</span>
<el-input v-model="info.className" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="classComment">
<span slot="label">
类描述
<el-tooltip content="用作类描述,例如 用户" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
</span>
<el-input v-model="info.classComment" />
</el-form-item>
</el-col>
<el-col :span="24" v-if="info.genType === '1'">
<el-form-item prop="genPath">
<span slot="label">
自定义路径
<el-tooltip content="填写磁盘绝对路径若不填写则生成到当前Web项目下" placement="top">
<i class="el-icon-question"></i>
</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"></i>
</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"></i>
</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-option>
</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"></i>
</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-option>
</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"></i>
</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-option>
</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"></i>
</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-option>
</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"></i>
</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-option>
</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 {
name: "BasicInfoForm",
components: { Treeselect },
props: {
info: {
type: Object,
default: null
},
tables: {
type: Array,
default: null
},
menus: {
type: Array,
default: []
},
},
data() {
return {
subColumns: [],
rules: {
templateType: [
{ required: true, message: "请选择生成模板", trigger: "blur" }
],
scene: [
{ required: true, message: "请选择生成场景", trigger: "blur" }
],
frontType: [
{ required: true, message: "请选择前端类型", trigger: "blur" }
],
// packageName: [
// { required: true, message: "", trigger: "blur" }
// ],
moduleName: [
{ required: true, message: "请输入生成模块名", trigger: "blur" }
],
businessName: [
{ required: true, message: "请输入生成业务名", trigger: "blur" }
],
businessPackage: [
{ required: true, message: "请输入生成业务包", trigger: "blur" }
],
className: [
{ required: true, message: "请输入生成类名称", trigger: "blur" }
],
classComment: [
{ required: true, message: "请输入生成类描述", trigger: "blur" }
],
}
};
},
created() {},
watch: {
'info.subTableName': function(val) {
this.setSubTableColumns(val);
}
},
methods: {
/** 转换菜单数据结构 */
normalizer(node) {
if (node.children && !node.children.length) {
delete node.children;
}
return {
id: node.id,
label: node.name,
children: node.children
};
},
/** 选择子表名触发 */
subSelectChange(value) {
this.info.subTableFkName = '';
},
/** 选择生成模板触发 */
tplSelectChange(value) {
if (value !== 1) {
// TODO
this.$modal.msgError('暂时不考虑支持【树形】和【主子表】的代码生成。原因是:导致 vm 模板过于复杂,不利于胖友二次开发');
return false;
}
if(value !== 'sub') {
this.info.subTableName = '';
this.info.subTableFkName = '';
}
},
/** 设置关联外键 */
setSubTableColumns(value) {
for (let item in this.tables) {
const name = this.tables[item].tableName;
if (value === name) {
this.subColumns = this.tables[item].columns;
break;
}
}
}
}
};
</script>

View File

@ -0,0 +1,114 @@
<template>
<!-- 导入表 -->
<el-dialog title="导入表" :visible.sync="visible" width="800px" top="5vh" append-to-body>
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true">
<el-form-item label="数据源" prop="dataSourceConfigId">
<el-select v-model="queryParams.dataSourceConfigId" placeholder="请选择数据源" clearable>
<el-option v-for="config in dataSourceConfigs"
:key="config.id" :label="config.name" :value="config.id"/>
</el-select>
</el-form-item>
<el-form-item label="表名称" prop="name">
<el-input v-model="queryParams.name" placeholder="请输入表名称" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="表描述" prop="comment">
<el-input v-model="queryParams.comment" placeholder="请输入表描述" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row>
<el-table v-loading="loading" @row-click="clickRow" ref="table" :data="dbTableList"
@selection-change="handleSelectionChange" height="260px">
<el-table-column type="selection" width="55" />
<el-table-column prop="name" label="表名称" :show-overflow-tooltip="true" />
<el-table-column prop="comment" label="表描述" :show-overflow-tooltip="true" />
</el-table>
</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 { getSchemaTableList, createCodegenList } from "@/api/infra/codegen";
import {getDataSourceConfigList} from "@/api/infra/dataSourceConfig";
export default {
data() {
return {
//
loading: false,
//
visible: false,
//
tables: [],
//
total: 0,
//
dbTableList: [],
//
queryParams: {
dataSourceConfigId: undefined,
name: undefined,
comment: undefined,
},
//
dataSourceConfigs: [],
};
},
methods: {
//
show() {
this.visible = true;
//
getDataSourceConfigList().then(response => {
this.dataSourceConfigs = response.data;
this.queryParams.dataSourceConfigId = this.dataSourceConfigs[0].id;
//
this.getList();
});
},
clickRow(row) {
this.$refs.table.toggleRowSelection(row);
},
//
handleSelectionChange(selection) {
this.tables = selection.map(item => item.name);
},
//
getList() {
this.loading = true;
getSchemaTableList(this.queryParams).then(res => {
this.dbTableList = res.data;
}).finally(() => {
this.loading = false;
});
},
/** 搜索按钮操作 */
handleQuery() {
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.queryParams.dataSourceConfigId = this.dataSourceConfigs[0].id;
this.handleQuery();
},
/** 导入按钮操作 */
handleImportTable() {
createCodegenList({
dataSourceConfigId: this.queryParams.dataSourceConfigId,
tableNames: this.tables
}).then(res => {
this.$modal.msgSuccess("导入成功");
this.visible = false;
this.$emit("ok");
});
}
}
};
</script>

View File

@ -0,0 +1,317 @@
<template>
<div class="app-container">
<doc-alert title="代码生成" url="https://doc.iocoder.cn/new-feature/" />
<doc-alert title="单元测试" url="https://doc.iocoder.cn/unit-test/" />
<!-- 操作工作栏 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" 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="创建时间" prop="createTime">
<el-date-picker v-model="queryParams.createTime" style="width: 240px" value-format="yyyy-MM-dd HH:mm:ss" type="daterange"
range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" :default-time="['00:00:00', '23:59:59']" />
</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 type="info" plain icon="el-icon-upload" size="mini" @click="openImportTable"
v-hasPermi="['infra:codegen:create']">导入</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<!-- 列表 -->
<el-table v-loading="loading" :data="tableList">
<el-table-column label="数据源" align="center" :formatter="dataSourceConfigNameFormat"/>
<el-table-column label="表名称" align="center" prop="tableName" width="200"/>
<el-table-column label="表描述" align="center" prop="tableComment" :show-overflow-tooltip="true" width="120"/>
<el-table-column label="实体" align="center" prop="className" width="200"/>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="更新时间" align="center" prop="createTime" width="180">
<template v-slot="scope">
<span>{{ parseTime(scope.row.updateTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="300px" class-name="small-padding fixed-width">
<template v-slot="scope">
<el-button type="text" size="small" icon="el-icon-view" @click="handlePreview(scope.row)" v-hasPermi="['infra:codegen:preview']">预览</el-button>
<el-button type="text" size="small" icon="el-icon-edit" @click="handleEditTable(scope.row)" v-hasPermi="['infra:codegen:update']">编辑</el-button>
<el-button type="text" size="small" icon="el-icon-delete" @click="handleDelete(scope.row)" v-hasPermi="['infra:codegen:delete']">删除</el-button>
<el-button type="text" size="small" icon="el-icon-refresh" @click="handleSynchDb(scope.row)" v-hasPermi="['infra:codegen:update']">同步</el-button>
<el-button type="text" size="small" icon="el-icon-download" @click="handleGenTable(scope.row)" v-hasPermi="['infra:codegen:download']">生成代码</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize" @pagination="getList"/>
<!-- 预览界面 -->
<el-dialog :title="preview.title" :visible.sync="preview.open" width="90%" top="5vh" append-to-body class="scrollbar">
<el-row>
<el-col :span="7">
<el-tree :data="preview.fileTree" :expand-on-click-node="false" default-expand-all highlight-current
@node-click="handleNodeClick"/>
</el-col>
<el-col :span="17">
<el-tabs v-model="preview.activeName">
<el-tab-pane v-for="item in preview.data" :label="item.filePath.substring(item.filePath.lastIndexOf('/') + 1)"
:name="item.filePath" :key="item.filePath">
<el-link :underline="false" icon="el-icon-document-copy" v-clipboard:copy="item.code" v-clipboard:success="clipboardSuccess" style="float:right">复制</el-link>
<pre><code class="hljs" v-html="highlightedCode(item)"></code></pre>
</el-tab-pane>
</el-tabs>
</el-col>
</el-row>
</el-dialog>
<!-- 基于 DB 导入 -->
<import-table ref="import" @ok="handleQuery" />
</div>
</template>
<script>
import { getCodegenTablePage, previewCodegen, downloadCodegen, deleteCodegen,
syncCodegenFromDB } from "@/api/infra/codegen";
import importTable from "./importTable";
//
import hljs from "highlight.js/lib/highlight";
import "highlight.js/styles/github-gist.css";
import {getDataSourceConfigList} from "@/api/infra/dataSourceConfig";
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"));
hljs.registerLanguage("typescript", require("highlight.js/lib/languages/typescript"));
export default {
name: "InfraCodegen",
components: { importTable },
data() {
return {
//
loading: true,
//
uniqueId: "",
//
tableNames: [],
//
showSearch: true,
//
total: 0,
//
tableList: [],
//
dateRange: "",
//
queryParams: {
pageNo: 1,
pageSize: 10,
tableName: undefined,
tableComment: undefined,
createTime: []
},
//
preview: {
open: false,
title: "代码预览",
fileTree: [],
data: {},
activeName: "",
},
//
dataSourceConfigs: [],
};
},
created() {
this.getList();
//
getDataSourceConfigList().then(response => {
this.dataSourceConfigs = response.data;
});
},
activated() {
const time = this.$route.query.t;
if (time != null && time !== this.uniqueId) {
this.uniqueId = time;
this.resetQuery();
}
},
methods: {
/** 查询表集合 */
getList() {
this.loading = true;
getCodegenTablePage(this.queryParams).then(response => {
this.tableList = response.data.list;
this.total = response.data.total;
this.loading = false;
}
);
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1;
this.getList();
},
/** 生成代码操作 */
handleGenTable(row) {
downloadCodegen(row.id).then(response => {
this.$download.zip(response, 'codegen-' + row.tableName + '.zip');
})
},
/** 同步数据库操作 */
handleSynchDb(row) {
// DB
const tableName = row.tableName;
this.$modal.confirm('确认要强制同步"' + tableName + '"表结构吗?').then(function() {
return syncCodegenFromDB(row.id);
}).then(() => {
this.$modal.msgSuccess("同步成功");
}).catch(() => {});
},
/** 打开导入表弹窗 */
openImportTable() {
this.$refs.import.show();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
/** 预览按钮 */
handlePreview(row) {
previewCodegen(row.id).then(response => {
this.preview.data = response.data;
let files = this.handleFiles(response.data);
this.preview.fileTree = this.handleTree(files, "id", "parentId", "children",
"/"); // "/"
// console.log(this.preview.fileTree)
this.preview.activeName = response.data[0].filePath;
this.preview.open = true;
});
},
/** 高亮显示 */
highlightedCode(item) {
// const vmName = key.substring(key.lastIndexOf("/") + 1, key.indexOf(".vm"));
// var language = vmName.substring(vmName.indexOf(".") + 1, vmName.length);
const language = item.filePath.substring(item.filePath.lastIndexOf('.') + 1)
const result = hljs.highlight(language, item.code || "", true);
return result.value || '&nbsp;';
},
/** 复制代码成功 */
clipboardSuccess() {
this.$modal.msgSuccess("复制成功");
},
/** 生成 files 目录 **/
handleFiles(datas) {
let exists = {}; // keyfile idvaluetrue
let files = [];
//
for (const data of datas) {
let paths = data.filePath.split('/');
let fullPath = ''; // id
// java
if (paths[paths.length - 1].indexOf('.java') >= 0) {
let newPaths = [];
for (let i = 0; i < paths.length; i++) {
let path = paths[i];
if (path !== 'java') {
newPaths.push(path);
continue;
}
newPaths.push(path);
// package
let tmp = undefined;
while (i < paths.length) {
path = paths[i + 1];
if (path === 'controller'
|| path === 'convert'
|| path === 'dal'
|| path === 'enums'
|| path === 'service'
|| path === 'vo' //
|| path === 'mysql'
|| path === 'dataobject') {
break;
}
tmp = tmp ? tmp + '.' + path : path;
i++;
}
if (tmp) {
newPaths.push(tmp);
}
}
paths = newPaths;
}
// path
for (let i = 0; i < paths.length; i++) {
// files
let oldFullPath = fullPath;
// replaceAll tabs replaceAll
fullPath = fullPath.length === 0 ? paths[i] : fullPath.replaceAll('.', '/') + '/' + paths[i];
if (exists[fullPath]) {
continue;
}
// files
exists[fullPath] = true;
files.push({
id: fullPath,
label: paths[i],
parentId: oldFullPath || '/' // "/" 为根节点
});
}
}
return files;
},
/** 节点单击事件 **/
handleNodeClick(data, node) {
if (node && !node.isLeaf) {
return false;
}
//
this.preview.activeName = data.id;
},
/** 修改按钮操作 */
handleEditTable(row) {
const tableId = row.id;
const tableName = row.tableName || this.tableNames[0];
const params = { pageNum: this.queryParams.pageNum };
this.$tab.openPage("修改[" + tableName + "]生成配置", '/codegen/edit/' + tableId, params);
},
/** 删除按钮操作 */
handleDelete(row) {
const tableIds = row.id;
this.$modal.confirm('是否确认删除表名称为"' + row.tableName + '"的数据项?').then(function() {
return deleteCodegen(tableIds);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
},
//
dataSourceConfigNameFormat(row, column) {
for (const config of this.dataSourceConfigs) {
if (row.dataSourceConfigId === config.id) {
return config.name;
}
}
return '未知【' + row.leaderUserId + '】';
},
}
};
</script>

View File

@ -0,0 +1,262 @@
<template>
<div class="app-container">
<doc-alert title="配置中心" url="https://doc.iocoder.cn/config-center/" />
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="参数名称" prop="name">
<el-input v-model="queryParams.name" placeholder="请输入参数名称" clearable style="width: 240px"
@keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="参数键名" prop="key">
<el-input v-model="queryParams.key" placeholder="请输入参数键名" clearable style="width: 240px"
@keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="系统内置" prop="type">
<el-select v-model="queryParams.type" placeholder="系统内置" clearable>
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.INFRA_CONFIG_TYPE)" :key="parseInt(dict.value)"
:label="dict.label" :value="parseInt(dict.value)"/>
</el-select>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker v-model="queryParams.createTime" style="width: 240px" value-format="yyyy-MM-dd HH:mm:ss" type="daterange"
range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" :default-time="['00:00:00', '23:59:59']" />
</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 type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
v-hasPermi="['infra:config:create']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" icon="el-icon-download" size="mini" @click="handleExport" :loading="exportLoading"
v-hasPermi="['infra:config:export']">导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="configList">
<el-table-column label="参数主键" align="center" prop="id" />
<el-table-column label="参数分类" align="center" prop="category" />
<el-table-column label="参数名称" align="center" prop="name" :show-overflow-tooltip="true" />
<el-table-column label="参数键名" align="center" prop="key" :show-overflow-tooltip="true" />
<el-table-column label="参数键值" align="center" prop="value" />
<el-table-column label="系统内置" align="center" prop="type">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.INFRA_CONFIG_TYPE" :value="scope.row.type" />
</template>
</el-table-column>
<el-table-column label="是否可见" align="center" prop="visible">
<template v-slot="scope">
<span>{{ scope.row.visible ? '是' : '否' }}</span>
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['infra:config:update']">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
v-hasPermi="['infra:config:delete']">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize" @pagination="getList"/>
<!-- 添加或修改参数配置对话框 -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="参数分类" prop="category">
<el-input v-model="form.category" placeholder="请输入参数分类" />
</el-form-item>
<el-form-item label="参数名称" prop="name">
<el-input v-model="form.name" placeholder="请输入参数名称" />
</el-form-item>
<el-form-item label="参数键名" prop="key">
<el-input v-model="form.key" placeholder="请输入参数键名" />
</el-form-item>
<el-form-item label="参数键值" prop="value">
<el-input v-model="form.value" placeholder="请输入参数键值" />
</el-form-item>
<el-form-item label="是否可见" prop="type">
<el-radio-group v-model="form.visible">
<el-radio :key="true" :label="true"></el-radio>
<el-radio :key="false" :label="false"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listConfig, getConfig, delConfig, addConfig, updateConfig, exportConfig } from "@/api/infra/config";
export default {
name: "InfraConfig",
data() {
return {
//
loading: true,
//
exportLoading: false,
//
showSearch: true,
//
total: 0,
//
configList: [],
//
title: "",
//
open: false,
//
typeOptions: [],
//
queryParams: {
pageNo: 1,
pageSize: 10,
name: undefined,
key: undefined,
type: undefined,
createTime: []
},
//
form: {},
//
rules: {
category: [
{ required: true, message: "参数分类不能为空", trigger: "blur" }
],
name: [
{ required: true, message: "参数名称不能为空", trigger: "blur" }
],
key: [
{ required: true, message: "参数键名不能为空", trigger: "blur" }
],
value: [
{ required: true, message: "参数键值不能为空", trigger: "blur" }
]
}
};
},
created() {
this.getList();
},
methods: {
/** 查询参数列表 */
getList() {
this.loading = true;
listConfig(this.queryParams).then(response => {
this.configList = response.data.list;
this.total = response.data.total;
this.loading = false;
}
);
},
//
cancel() {
this.open = false;
this.reset();
},
//
reset() {
this.form = {
id: undefined,
name: undefined,
key: undefined,
value: undefined,
remark: undefined
};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
/** 新增按钮操作 */
handleAdd() {
this.reset();
this.open = true;
this.title = "添加参数";
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
const id = row.id || this.ids
getConfig(id).then(response => {
this.form = response.data;
this.open = true;
this.title = "修改参数";
});
},
/** 提交按钮 */
submitForm: function() {
this.$refs["form"].validate(valid => {
if (valid) {
if (this.form.id !== undefined) {
updateConfig(this.form).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
});
} else {
addConfig(this.form).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
});
}
}
});
},
/** 删除按钮操作 */
handleDelete(row) {
const ids = row.id || this.ids;
this.$modal.confirm('是否确认删除参数编号为"' + ids + '"的数据项?').then(function() {
return delConfig(ids);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
},
/** 导出按钮操作 */
handleExport() {
this.$modal.confirm('是否确认导出所有参数数据项?').then(() => {
//
let params = {...this.queryParams};
params.pageNo = undefined;
params.pageSize = undefined;
this.exportLoading = true;
return exportConfig(params);
}).then(response => {
this.$download.excel(response, '参数配置.xls');
this.exportLoading = false;
}).catch(() => {});
},
}
};
</script>

View File

@ -0,0 +1,166 @@
<template>
<div class="app-container">
<!-- 操作工具栏 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
v-hasPermi="['infra:data-source-config:create']">新增</el-button>
</el-col>
</el-row>
<!-- 列表 -->
<el-table v-loading="loading" :data="list">
<el-table-column label="主键编号" align="center" prop="id" />
<el-table-column label="数据源名称" align="center" prop="name" />
<el-table-column label="数据源连接" align="center" prop="url" />
<el-table-column label="用户名" align="center" prop="username" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['infra:data-source-config:update']">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
v-hasPermi="['infra:data-source-config:delete']">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 对话框(添加 / 修改) -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
<el-form-item label="数据源名称" prop="name">
<el-input v-model="form.name" placeholder="请输入参数名称" />
</el-form-item>
<el-form-item label="数据源连接" prop="url">
<el-input v-model="form.url" placeholder="请输入数据源连接" />
</el-form-item>
<el-form-item label="用户名" prop="username">
<el-input v-model="form.username" placeholder="请输入用户名" />
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="form.password" placeholder="请输入密码" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { createDataSourceConfig, updateDataSourceConfig, deleteDataSourceConfig, getDataSourceConfig, getDataSourceConfigList } from "@/api/infra/dataSourceConfig";
export default {
name: "InfraDataSourceConfig",
components: {
},
data() {
return {
//
loading: true,
//
total: 0,
//
list: [],
//
title: "",
//
open: false,
//
form: {},
//
rules: {
name: [{ required: true, message: "数据源名称不能为空", trigger: "blur" }],
url: [{ required: true, message: "数据源连接不能为空", trigger: "blur" }],
username: [{ required: true, message: "用户名不能为空", trigger: "blur" }],
password: [{ required: true, message: "密码不能为空", trigger: "blur" }],
}
};
},
created() {
this.getList();
},
methods: {
/** 查询列表 */
getList() {
this.loading = true;
//
getDataSourceConfigList().then(response => {
this.list = response.data;
this.loading = false;
});
},
/** 取消按钮 */
cancel() {
this.open = false;
this.reset();
},
/** 表单重置 */
reset() {
this.form = {
id: undefined,
name: undefined,
url: undefined,
username: undefined,
password: undefined,
};
this.resetForm("form");
},
/** 新增按钮操作 */
handleAdd() {
this.reset();
this.open = true;
this.title = "添加数据源配置";
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
const id = row.id;
getDataSourceConfig(id).then(response => {
this.form = response.data;
this.open = true;
this.title = "修改数据源配置";
});
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (!valid) {
return;
}
//
if (this.form.id != null) {
updateDataSourceConfig(this.form).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
});
return;
}
//
createDataSourceConfig(this.form).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
});
});
},
/** 删除按钮操作 */
handleDelete(row) {
const id = row.id;
this.$modal.confirm('是否确认删除数据源配置编号为"' + id + '"的数据项?').then(function() {
return deleteDataSourceConfig(id);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
}
}
};
</script>

View File

@ -0,0 +1,70 @@
<template>
<div class="app-container">
<doc-alert title="数据库文档" url="https://doc.iocoder.cn/db-doc/" />
<!-- 操作工作栏 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" icon="el-icon-plus" size="mini" @click="handleExportHtml">导出 HTML</el-button>
<el-button type="primary" icon="el-icon-plus" size="mini" @click="handleExportWord">导出 Word</el-button>
<el-button type="primary" icon="el-icon-plus" size="mini" @click="handleExportMarkdown">导出 Markdown</el-button>
</el-col>
</el-row>
<!-- 展示文档 -->
<div v-loading="loading" :style="'height:'+ height">
<i-frame :src="src" />
</div>
</div>
</template>
<script>
import { exportHtml, exportWord, exportMarkdown} from "@/api/infra/dbDoc";
import iFrame from "@/components/iFrame/index";
export default {
name: "InfraDBDoc",
components: { iFrame },
data() {
return {
height: document.documentElement.clientHeight - 94.5 + "px;",
loading: true,
src: "undefined",
};
},
mounted: function() {
setTimeout(() => {
this.loading = false;
}, 230);
const that = this;
window.onresize = function temp() {
that.height = document.documentElement.clientHeight - 94.5 + "px;";
};
},
created() {
// Html
exportHtml().then(response => {
let blob = new Blob([response], {type : 'text/html'});
this.src = window.URL.createObjectURL(blob);
})
},
methods: {
/** 处理导出 HTML */
handleExportHtml() {
exportHtml().then(response => {
this.$download.html(response, '数据库文档.html');
})
},
/** 处理导出 Word */
handleExportWord() {
exportWord().then(response => {
this.$download.word(response, '数据库文档.doc');
})
},
/** 处理导出 Markdown */
handleExportMarkdown() {
exportMarkdown().then(response => {
this.$download.markdown(response, '数据库文档.md');
})
}
}
};
</script>

View File

@ -0,0 +1,32 @@
<template>
<div>
<doc-alert title="数据库 MyBatis" url="https://doc.iocoder.cn/mybatis/" />
<doc-alert title="多数据源(读写分离)" url="https://doc.iocoder.cn/dynamic-datasource/" />
<i-frame v-if="!loading" :src="url" />
</div>
</template>
<script>
import iFrame from "@/components/iFrame/index";
import { getConfigKey } from "@/api/infra/config";
export default {
name: "Druid",
components: { iFrame },
data() {
return {
url: process.env.VUE_APP_BASE_API + "/druid/index.html",
loading: true
};
},
created() {
getConfigKey("url.druid").then(response => {
if (!response.data || response.data.length === 0) {
return
}
this.url = response.data;
}).finally(() => {
this.loading = false;
})
}
};
</script>

View File

@ -0,0 +1,216 @@
<template>
<div class="app-container">
<doc-alert title="上传下载" url="https://doc.iocoder.cn/file/"/>
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="文件路径" prop="path">
<el-input v-model="queryParams.path" placeholder="请输入文件路径" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker v-model="queryParams.createTime" style="width: 240px" value-format="yyyy-MM-dd HH:mm:ss"
type="daterange"
range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"
:default-time="['00:00:00', '23:59:59']"/>
</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 type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd">上传文件</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<!-- 列表 -->
<el-table v-loading="loading" :data="list">
<el-table-column label="文件名" :show-overflow-tooltip="true" align="center" min-width="200px" prop="name"/>
<el-table-column label="文件路径" :show-overflow-tooltip="true" align="center" min-width="250px" prop="path"/>
<el-table-column label="文件 URL" :show-overflow-tooltip="true" align="center" min-width="300px" prop="url"/>
<el-table-column label="文件大小" align="center" prop="size" min-width="120px" :formatter="sizeFormat"/>
<el-table-column label="文件类型" :show-overflow-tooltip="true" align="center" prop="type" width="180px"/>
<el-table-column label="文件内容" align="center" prop="content" min-width="150px">
<template v-slot="scope">
<image-preview v-if="scope.row.type&&scope.row.type.indexOf('image/') === 0" :src="scope.row.url"
:width="'100px'"></image-preview>
<video v-else-if="scope.row.type&&scope.row.type.indexOf('video/') === 0" :width="'100px'">
<source :src="scope.row.url"/>
</video>
<i v-else>无法预览点击
<el-link type="primary" :underline="false" style="font-size:12px;vertical-align: baseline;" target="_blank"
:href="getFileUrl + scope.row.configId + '/get/' + scope.row.path">下载
</el-link>
</i>
</template>
</el-table-column>
<el-table-column label="上传时间" align="center" prop="createTime" min-width="170px">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" min-width="100px">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
v-hasPermi="['infra:file:delete']">删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
<!-- 对话框(添加 / 修改) -->
<el-dialog :title="upload.title" :visible.sync="upload.open" width="400px" append-to-body>
<el-upload ref="upload" :limit="1" accept=".jpg, .png, .gif" :auto-upload="false" drag
:headers="upload.headers" :action="upload.url" :data="upload.data" :disabled="upload.isUploading"
:on-change="handleFileChange"
:on-progress="handleFileUploadProgress"
:on-success="handleFileSuccess">
<i class="el-icon-upload"></i>
<div class="el-upload__text">
将文件拖到此处 <em>点击上传</em>
</div>
<div class="el-upload__tip" style="color:red" slot="tip">提示仅允许导入 jpgpnggif 格式文件</div>
</el-upload>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitFileForm"> </el-button>
<el-button @click="upload.open = false"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import {deleteFile, getFilePage} from "@/api/infra/file";
import {getAccessToken} from "@/utils/auth";
import ImagePreview from "@/components/ImagePreview";
export default {
name: "InfraFile",
components: {
ImagePreview
},
data() {
return {
getFileUrl: process.env.VUE_APP_BASE_API + '/admin-api/infra/file/',
//
loading: true,
//
showSearch: true,
//
total: 0,
//
list: [],
//
title: "",
//
queryParams: {
pageNo: 1,
pageSize: 10,
path: null,
type: null,
createTime: []
},
//
upload: {
open: false, //
title: "", //
isUploading: false, //
url: process.env.VUE_APP_BASE_API + "/admin-api/infra/file/upload", //
headers: {Authorization: "Bearer " + getAccessToken()}, //
data: {} //
},
};
},
created() {
this.getList();
},
methods: {
/** 查询列表 */
getList() {
this.loading = true;
//
getFilePage(this.queryParams).then(response => {
this.list = response.data.list;
this.total = response.data.total;
this.loading = false;
});
},
/** 取消按钮 */
cancel() {
this.open = false;
this.reset();
},
/** 表单重置 */
reset() {
this.form = {
content: undefined,
};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
/** 新增按钮操作 */
handleAdd() {
this.upload.open = true;
this.upload.title = "上传文件";
},
/** 处理上传的文件发生变化 */
handleFileChange(file, fileList) {
},
/** 处理文件上传中 */
handleFileUploadProgress(event, file, fileList) {
this.upload.isUploading = true; //
},
/** 发起文件上传 */
submitFileForm() {
this.$refs.upload.submit();
},
/** 文件上传成功处理 */
handleFileSuccess(response, file, fileList) {
//
this.upload.open = false;
this.upload.isUploading = false;
this.$refs.upload.clearFiles();
//
this.$modal.msgSuccess("上传成功");
this.getList();
},
/** 删除按钮操作 */
handleDelete(row) {
const id = row.id;
this.$modal.confirm('是否确认删除文件编号为"' + id + '"的数据项?').then(function () {
return deleteFile(id);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {
});
},
//
sizeFormat(row, column) {
const unitArr = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
const srcSize = parseFloat(row.size);
const index = Math.floor(Math.log(srcSize) / Math.log(1024));
let size = srcSize / Math.pow(1024, index);
size = size.toFixed(2);//
return size + ' ' + unitArr[index];
},
}
};
</script>

View File

@ -0,0 +1,305 @@
<template>
<div class="app-container">
<doc-alert title="上传下载" url="https://doc.iocoder.cn/file/" />
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="配置名" prop="name">
<el-input v-model="queryParams.name" placeholder="请输入配置名" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="存储器" prop="storage">
<el-select v-model="queryParams.storage" placeholder="请选择存储器" clearable>
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.INFRA_FILE_STORAGE)"
:key="dict.value" :label="dict.label" :value="dict.value"/>
</el-select>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker v-model="queryParams.createTime" style="width: 240px" value-format="yyyy-MM-dd HH:mm:ss" type="daterange"
range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" :default-time="['00:00:00', '23:59:59']" />
</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 type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
v-hasPermi="['infra:file-config:create']">新增</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<!-- 列表 -->
<el-table v-loading="loading" :data="list">
<el-table-column label="编号" align="center" prop="id" />
<el-table-column label="配置名" align="center" prop="name" />
<el-table-column label="存储器" align="center" prop="storage">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.INFRA_FILE_STORAGE" :value="scope.row.storage" />
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="主配置" align="center" prop="primary">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.master" />
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="240">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['infra:file-config:update']">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-attract" @click="handleMaster(scope.row)"
:disabled="scope.row.master" v-hasPermi="['infra:file-config:update']">主配置</el-button>
<el-button size="mini" type="text" icon="el-icon-share" @click="handleTest(scope.row)">测试</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
v-hasPermi="['infra:file-config:delete']">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
<!-- 对话框(添加 / 修改) -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="120px">
<el-form-item label="配置名" prop="name">
<el-input v-model="form.name" placeholder="请输入配置名" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" placeholder="请输入备注" />
</el-form-item>
<el-form-item label="存储器" prop="storage">
<el-select v-model="form.storage" placeholder="请选择存储器" :disabled="form.id !== undefined">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.INFRA_FILE_STORAGE)"
:key="dict.value" :label="dict.label" :value="parseInt(dict.value)" />
</el-select>
</el-form-item>
<!-- DB -->
<!-- Local / FTP / SFTP -->
<el-form-item v-if="form.storage >= 10 && form.storage <= 12" label="基础路径" prop="config.basePath">
<el-input v-model="form.config.basePath" placeholder="请输入基础路径" />
</el-form-item>
<el-form-item v-if="form.storage >= 11 && form.storage <= 12" label="主机地址" prop="config.host">
<el-input v-model="form.config.host" placeholder="请输入主机地址" />
</el-form-item>
<el-form-item v-if="form.storage >= 11 && form.storage <= 12" label="主机端口" prop="config.port">
<el-input-number :min="0" v-model="form.config.port" placeholder="请输入主机端口" />
</el-form-item>
<el-form-item v-if="form.storage >= 11 && form.storage <= 12" label="用户名" prop="config.username">
<el-input v-model="form.config.username" placeholder="请输入密码" />
</el-form-item>
<el-form-item v-if="form.storage >= 11 && form.storage <= 12" label="密码" prop="config.password">
<el-input v-model="form.config.password" placeholder="请输入密码" />
</el-form-item>
<el-form-item v-if="form.storage === 11" label="连接模式" prop="config.mode">
<el-radio-group v-model="form.config.mode">
<el-radio key="Active" label="Active">主动模式</el-radio>
<el-radio key="Passive" label="Passive">主动模式</el-radio>
</el-radio-group>
</el-form-item>
<!-- S3 -->
<el-form-item v-if="form.storage === 20" label="节点地址" prop="config.endpoint">
<el-input v-model="form.config.endpoint" placeholder="请输入节点地址" />
</el-form-item>
<el-form-item v-if="form.storage === 20" label="存储 bucket" prop="config.bucket">
<el-input v-model="form.config.bucket" placeholder="请输入 bucket" />
</el-form-item>
<el-form-item v-if="form.storage === 20" label="accessKey" prop="config.accessKey">
<el-input v-model="form.config.accessKey" placeholder="请输入 accessKey" />
</el-form-item>
<el-form-item v-if="form.storage === 20" label="accessSecret" prop="config.accessSecret">
<el-input v-model="form.config.accessSecret" placeholder="请输入 accessSecret" />
</el-form-item>
<!-- 通用 -->
<el-form-item v-if="form.storage === 20" label="自定义域名"> <!-- 无需参数校验所以去掉 prop -->
<el-input v-model="form.config.domain" placeholder="请输入自定义域名" />
</el-form-item>
<el-form-item v-else-if="form.storage" label="自定义域名" prop="config.domain">
<el-input v-model="form.config.domain" placeholder="请输入自定义域名" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import {
createFileConfig,
updateFileConfig,
deleteFileConfig,
getFileConfig,
getFileConfigPage,
testFileConfig, updateFileConfigMaster
} from "@/api/infra/fileConfig";
export default {
name: "InfraFileConfig",
components: {
},
data() {
return {
//
loading: true,
//
showSearch: true,
//
total: 0,
//
list: [],
//
title: "",
//
open: false,
//
queryParams: {
pageNo: 1,
pageSize: 10,
name: null,
storage: null,
createTime: []
},
//
form: {
storage: undefined,
config: {}
},
//
rules: {
name: [{ required: true, message: "配置名不能为空", trigger: "blur" }],
storage: [{ required: true, message: "存储器不能为空", trigger: "change" }],
config: {
basePath: [{ required: true, message: "基础路径不能为空", trigger: "blur" }],
host: [{ required: true, message: "主机地址不能为空", trigger: "blur" }],
port: [{ required: true, message: "主机端口不能为空", trigger: "blur" }],
username: [{ required: true, message: "用户名不能为空", trigger: "blur" }],
password: [{ required: true, message: "密码不能为空", trigger: "blur" }],
mode: [{ required: true, message: "连接模式不能为空", trigger: "change" }],
endpoint: [{ required: true, message: "节点地址不能为空", trigger: "blur" }],
bucket: [{ required: true, message: "存储 bucket 不能为空", trigger: "blur" }],
accessKey: [{ required: true, message: "accessKey 不能为空", trigger: "blur" }],
accessSecret: [{ required: true, message: "accessSecret 不能为空", trigger: "blur" }],
domain: [{ required: true, message: "自定义域名不能为空", trigger: "blur" }],
},
}
};
},
created() {
this.getList();
},
methods: {
/** 查询列表 */
getList() {
this.loading = true;
//
getFileConfigPage(this.queryParams).then(response => {
this.list = response.data.list;
this.total = response.data.total;
this.loading = false;
});
},
/** 取消按钮 */
cancel() {
this.open = false;
this.reset();
},
/** 表单重置 */
reset() {
this.form = {
id: undefined,
name: undefined,
storage: undefined,
remark: undefined,
config: {},
};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
/** 新增按钮操作 */
handleAdd() {
this.reset();
this.open = true;
this.title = "添加文件配置";
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
const id = row.id;
getFileConfig(id).then(response => {
this.form = response.data;
this.open = true;
this.title = "修改文件配置";
});
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (!valid) {
return;
}
//
if (this.form.id != null) {
updateFileConfig(this.form).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
});
return;
}
//
createFileConfig(this.form).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
});
});
},
/** 删除按钮操作 */
handleDelete(row) {
const id = row.id;
this.$modal.confirm('是否确认删除文件配置编号为"' + id + '"的数据项?').then(function() {
return deleteFileConfig(id);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
},
/** 主配置按钮操作 */
handleMaster(row) {
const id = row.id;
this.$modal.confirm('是否确认修改配置编号为"' + id + '"的数据项为主配置?').then(function() {
return updateFileConfigMaster(id);
}).then(() => {
this.getList();
this.$modal.msgSuccess("修改成功");
}).catch(() => {});
},
/** 测试按钮操作 */
handleTest(row) {
testFileConfig(row.id).then((response) => {
this.$modal.alert("测试通过,上传文件成功!访问地址:" + response.data);
}).catch(() => {});
},
}
};
</script>

View File

@ -0,0 +1,378 @@
<template>
<div class="app-container">
<doc-alert title="定时任务" url="https://doc.iocoder.cn/job/" />
<doc-alert title="异步任务" url="https://doc.iocoder.cn/async-task/" />
<doc-alert title="消息队列" url="https://doc.iocoder.cn/message-queue/" />
<!-- 搜索栏 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="100px">
<el-form-item label="任务名称" prop="name">
<el-input v-model="queryParams.name" placeholder="请输入任务名称" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="任务状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择任务状态" clearable>
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.INFRA_JOB_STATUS)"
:key="dict.value" :label="dict.label" :value="dict.value"/>
</el-select>
</el-form-item>
<el-form-item label="处理器的名字" prop="handlerName">
<el-input v-model="queryParams.handlerName" 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 :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
v-hasPermi="['infra:job:create']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" icon="el-icon-download" size="mini" @click="handleExport" :loading="exportLoading"
v-hasPermi="['infra:job:export']">导出</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="info" icon="el-icon-s-operation" size="mini" @click="handleJobLog"
v-hasPermi="['infra:job:query']">执行日志</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="jobList">
<el-table-column label="任务编号" align="center" prop="id" />
<el-table-column label="任务名称" align="center" prop="name" />
<el-table-column label="任务状态" align="center" prop="status">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.INFRA_JOB_STATUS" :value="scope.row.status" />
</template>
</el-table-column>>
<el-table-column label="处理器的名字" align="center" prop="handlerName" />
<el-table-column label="处理器的参数" align="center" prop="handlerParam" />
<el-table-column label="CRON 表达式" align="center" prop="cronExpression" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['infra:job:update']">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-check" @click="handleChangeStatus(scope.row, true)"
v-if="scope.row.status === InfJobStatusEnum.STOP" v-hasPermi="['infra:job:update']">开启</el-button>
<el-button size="mini" type="text" icon="el-icon-close" @click="handleChangeStatus(scope.row, false)"
v-if="scope.row.status === InfJobStatusEnum.NORMAL" v-hasPermi="['infra:job:update']">暂停</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
v-hasPermi="['infra:job:delete']">删除</el-button>
<el-dropdown size="mini" @command="(command) => handleCommand(command, scope.row)"
v-hasPermi="['infra:job:trigger', 'infra:job:query']">
<el-button size="mini" type="text" icon="el-icon-d-arrow-right">更多</el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="handleRun" icon="el-icon-caret-right"
v-hasPermi="['infra:job:trigger']">执行一次</el-dropdown-item>
<el-dropdown-item command="handleView" icon="el-icon-view"
v-hasPermi="['infra:job:query']">任务详细</el-dropdown-item>
<el-dropdown-item command="handleJobLog" icon="el-icon-s-operation"
v-hasPermi="['infra:job:query']">调度日志</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
<!-- 添加或修改定时任务对话框 -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="120px">
<el-form-item label="任务名称" prop="name">
<el-input v-model="form.name" placeholder="请输入任务名称" />
</el-form-item>
<el-form-item label="处理器的名字" prop="handlerName">
<el-input v-model="form.handlerName" placeholder="请输入处理器的名字" v-bind:readonly="form.id !== undefined" />
</el-form-item>
<el-form-item label="处理器的参数" prop="handlerParam">
<el-input v-model="form.handlerParam" placeholder="请输入处理器的参数" />
</el-form-item>
<el-form-item label="CRON 表达式" prop="cronExpression">
<el-input v-model="form.cronExpression" placeholder="请输入CRON 表达式">
<template slot="append">
<el-button type="primary" @click="handleShowCron">
生成表达式
<i class="el-icon-time el-icon--right"></i>
</el-button>
</template>
</el-input>
</el-form-item>
<el-form-item label="重试次数" prop="retryCount">
<el-input v-model="form.retryCount" placeholder="请输入重试次数。设置为 0 时,不进行重试" />
</el-form-item>
<el-form-item label="重试间隔" prop="retryInterval">
<el-input v-model="form.retryInterval" placeholder="请输入重试间隔,单位:毫秒。设置为 0 时,无需间隔" />
</el-form-item>
<el-form-item label="监控超时时间" prop="monitorTimeout">
<el-input v-model="form.monitorTimeout" placeholder="请输入监控超时时间,单位:毫秒" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
<el-dialog title="Cron表达式生成器" :visible.sync="openCron" append-to-body class="scrollbar" destroy-on-close>
<crontab @hide="openCron=false" @fill="crontabFill" :expression="expression"></crontab>
</el-dialog>
<!-- 任务详细 -->
<el-dialog title="任务详细" :visible.sync="openView" width="700px" append-to-body>
<el-form ref="form" :model="form" label-width="200px" size="mini">
<el-row>
<el-col :span="24">
<el-form-item label="任务编号:">{{ form.id }}</el-form-item>
<el-form-item label="任务名称:">{{ form.name }}</el-form-item>
<el-form-item label="任务名称:">
<dict-tag :type="DICT_TYPE.INFRA_JOB_STATUS" :value="form.status" />
</el-form-item>
<el-form-item label="处理器的名字:">{{ form.handlerName }}</el-form-item>
<el-form-item label="处理器的参数:">{{ form.handlerParam }}</el-form-item>
<el-form-item label="cron表达式">{{ form.cronExpression }}</el-form-item>
<el-form-item label="重试次数:">{{ form.retryCount }}</el-form-item>
<el-form-item label="重试间隔:">{{ form.retryInterval + " 毫秒" }}</el-form-item>
<el-form-item label="监控超时时间:">{{ form.monitorTimeout > 0 ? form.monitorTimeout + " 毫秒" : "未开启" }}</el-form-item>
<el-form-item label="后续执行时间:">{{ Array.from(nextTimes, x => parseTime(x)).join('; ')}}</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="openView = false"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listJob, getJob, delJob, addJob, updateJob, exportJob, runJob, updateJobStatus, getJobNextTimes } from "@/api/infra/job";
import { InfraJobStatusEnum } from "@/utils/constants";
import Crontab from '@/components/Crontab'
export default {
components: { Crontab },
name: "InfraJob",
data() {
return {
//
loading: true,
//
exportLoading: false,
//
showSearch: true,
//
total: 0,
//
jobList: [],
//
title: "",
//
open: false,
//
openView: false,
// Cron
openCron: false,
//
expression: "",
//
statusOptions: [],
//
queryParams: {
pageNo: 1,
pageSize: 10,
name: undefined,
status: undefined,
handlerName: undefined
},
//
form: {},
//
rules: {
name: [{ required: true, message: "任务名称不能为空", trigger: "blur" }],
handlerName: [{ required: true, message: "处理器的名字不能为空", trigger: "blur" }],
cronExpression: [{ required: true, message: "CRON 表达式不能为空", trigger: "blur" }],
retryCount: [{ required: true, message: "重试次数不能为空", trigger: "blur" }],
retryInterval: [{ required: true, message: "重试间隔不能为空", trigger: "blur" }],
},
nextTimes: [], //
//
InfJobStatusEnum: InfraJobStatusEnum
};
},
created() {
this.getList();
},
methods: {
/** 查询定时任务列表 */
getList() {
this.loading = true;
listJob(this.queryParams).then(response => {
this.jobList = response.data.list;
this.total = response.data.total;
this.loading = false;
});
},
/** 取消按钮 */
cancel() {
this.open = false;
this.reset();
},
/** 表单重置 */
reset() {
this.form = {
id: undefined,
name: undefined,
handlerName: undefined,
handlerParam: undefined,
cronExpression: undefined,
retryCount: undefined,
retryInterval: undefined,
monitorTimeout: undefined,
};
this.nextTimes = [];
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
/** 立即执行一次 **/
handleRun(row) {
this.$modal.confirm('确认要立即执行一次"' + row.name + '"任务吗?').then(function() {
return runJob(row.id);
}).then(() => {
this.$modal.msgSuccess("执行成功");
}).catch(() => {});
},
/** 任务详细信息 */
handleView(row) {
getJob(row.id).then(response => {
this.form = response.data;
this.openView = true;
});
//
getJobNextTimes(row.id).then(response => {
this.nextTimes = response.data;
});
},
/** cron表达式按钮操作 */
handleShowCron() {
this.expression = this.form.cronExpression;
this.openCron = true;
},
/** 确定后回传值 */
crontabFill(value) {
this.form.cronExpression = value;
},
/** 任务日志列表查询 */
handleJobLog(row) {
if (row.id) {
this.$router.push({
path:"/system/job/log",
query:{
jobId: row.id
}
});
} else {
this.$router.push("/system/job/log");
}
},
/** 新增按钮操作 */
handleAdd() {
this.reset();
this.open = true;
this.title = "添加任务";
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
const id = row.id;
getJob(id).then(response => {
this.form = response.data;
this.open = true;
this.title = "修改任务";
});
},
/** 提交按钮 */
submitForm: function() {
this.$refs["form"].validate(valid => {
if (valid) {
if (this.form.id !== undefined) {
updateJob(this.form).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
});
} else {
addJob(this.form).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
});
}
}
});
},
/** 删除按钮操作 */
handleDelete(row) {
const ids = row.id;
this.$modal.confirm('是否确认删除定时任务编号为"' + ids + '"的数据项?').then(function() {
return delJob(ids);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
},
/** 更新状态操作 */
handleChangeStatus(row, open) {
const id = row.id;
let status = open ? InfraJobStatusEnum.NORMAL : InfraJobStatusEnum.STOP;
let statusStr = open ? '开启' : '关闭';
this.$modal.confirm('是否确认' + statusStr + '定时任务编号为"' + id + '"的数据项?').then(function() {
return updateJobStatus(id, status);
}).then(() => {
this.getList();
this.$modal.msgSuccess(statusStr + "成功");
}).catch(() => {});
},
//
handleCommand(command, row) {
switch (command) {
case "handleRun":
this.handleRun(row);
break;
case "handleView":
this.handleView(row);
break;
case "handleJobLog":
this.handleJobLog(row);
break;
default:
break;
}
},
/** 导出按钮操作 */
handleExport() {
const queryParams = this.queryParams;
this.$modal.confirm("是否确认导出所有定时任务数据项?").then(() => {
this.exportLoading = true;
return exportJob(queryParams);
}).then(response => {
this.$download.excel(response, '定时任务.xls');
this.exportLoading = false;
}).catch(() => {});
}
}
};
</script>

178
src/views/infra/job/log.vue Normal file
View File

@ -0,0 +1,178 @@
<template>
<div class="app-container">
<!-- 搜索栏 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="120px">
<el-form-item label="处理器的名字" prop="handlerName">
<el-input v-model="queryParams.handlerName" placeholder="请输入处理器的名字" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="开始执行时间" prop="beginTime">
<el-date-picker clearable v-model="queryParams.beginTime" type="date" value-format="yyyy-MM-dd" placeholder="选择开始执行时间" />
</el-form-item>
<el-form-item label="结束执行时间" prop="endTime">
<el-date-picker clearable v-model="queryParams.endTime" type="date" value-format="yyyy-MM-dd" placeholder="选择结束执行时间" />
</el-form-item>
<el-form-item label="任务状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择任务状态" clearable>
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.INFRA_JOB_LOG_STATUS)"
:key="dict.value" :label="dict.label" :value="dict.value"/>
</el-select>
</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 type="warning" icon="el-icon-download" size="mini" @click="handleExport"
v-hasPermi="['infra:job:export']">导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="list">
<el-table-column label="日志编号" align="center" prop="id" />
<el-table-column label="任务编号" align="center" prop="jobId" />
<el-table-column label="处理器的名字" align="center" prop="handlerName" />
<el-table-column label="处理器的参数" align="center" prop="handlerParam" />
<el-table-column label="第几次执行" align="center" prop="executeIndex" />
<el-table-column label="执行时间" align="center" width="180">
<template v-slot="scope">
<span>{{ parseTime(scope.row.beginTime) + ' ~ ' + parseTime(scope.row.endTime) }}</span>
</template>
</el-table-column>
<el-table-column label="执行时长" align="center" prop="duration">
<template v-slot="scope">
<span>{{ scope.row.duration + ' 毫秒' }}</span>
</template>
</el-table-column>
<el-table-column label="任务状态" align="center" prop="status">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.INFRA_JOB_LOG_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-view" @click="handleView(scope.row)" :loading="exportLoading"
v-hasPermi="['infra:job:query']">详细</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
<!-- 调度日志详细 -->
<el-dialog title="调度日志详细" :visible.sync="open" width="700px" append-to-body>
<el-form ref="form" :model="form" label-width="120px" size="mini">
<el-row>
<el-col :span="12">
<el-form-item label="日志编号:">{{ form.id }}</el-form-item>
<el-form-item label="任务编号:">{{ form.jobId }}</el-form-item>
<el-form-item label="处理器的名字:">{{ form.handlerName }}</el-form-item>
<el-form-item label="处理器的参数:">{{ form.handlerParam }}</el-form-item>
<el-form-item label="第几次执行:">{{ form.executeIndex }}</el-form-item>
<el-form-item label="执行时间:">{{ parseTime(form.beginTime) + ' ~ ' + parseTime(form.endTime) }}</el-form-item>
<el-form-item label="执行时长:">{{ form.duration + ' 毫秒' }}</el-form-item>
<el-form-item label="任务状态:">
<dict-tag :type="DICT_TYPE.INFRA_JOB_LOG_STATUS" :value="form.status" />
</el-form-item>
<el-form-item label="执行结果:">{{ form.result }}</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="open = false"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { getJobLogPage, exportJobLogExcel } from "@/api/infra/jobLog";
export default {
name: "InfraJobLog",
data() {
return {
//
loading: true,
//
exportLoading: false,
//
showSearch: true,
//
total: 0,
//
list: [],
//
open: false,
//
form: {},
//
queryParams: {
pageNo: 1,
pageSize: 10,
handlerName: null,
beginTime: null,
endTime: null,
status: null,
}
};
},
created() {
this.queryParams.jobId = this.$route.query && this.$route.query.jobId;
this.getList();
},
methods: {
/** 查询调度日志列表 */
getList() {
this.loading = true;
getJobLogPage({
...this.queryParams,
beginTime: this.queryParams.beginTime ? this.queryParams.beginTime + ' 00:00:00' : undefined,
endTime: this.queryParams.endTime ? this.queryParams.endTime + ' 23:59:59' : undefined,
}).then(response => {
this.list = response.data.list;
this.total = response.data.total;
this.loading = false;
}
);
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
/** 详细按钮操作 */
handleView(row) {
this.open = true;
this.form = row;
},
/** 导出按钮操作 */
handleExport() {
//
let params = {...this.queryParams,
beginTime: this.queryParams.beginTime ? this.queryParams.beginTime + ' 00:00:00' : undefined,
endTime: this.queryParams.endTime ? this.queryParams.endTime + ' 23:59:59' : undefined,
};
params.pageNo = undefined;
params.pageSize = undefined;
//
this.$modal.confirm('是否确认导出所有定时任务日志数据项?').then(() => {
this.exportLoading = true;
return exportJobLogExcel(params);
}).then(response => {
this.$download.excel(response, '定时任务日志.xls');
this.exportLoading = false;
}).catch(() => {});
}
}
};
</script>

View File

@ -0,0 +1,157 @@
<template>
<div class="app-container">
<doc-alert title="Redis 缓存" url="https://doc.iocoder.cn/redis-cache/" />
<doc-alert title="本地缓存" url="https://doc.iocoder.cn/local-cache/" />
<el-row>
<el-col :span="24" class="card-box">
<el-card>
<div slot="header"><span>基本信息</span></div>
<div class="el-table el-table--enable-row-hover el-table--medium">
<table cellspacing="0" style="width: 100%">
<tbody>
<tr>
<td><div class="cell">Redis版本</div></td>
<td><div class="cell" v-if="cache.info">{{ cache.info.redis_version }}</div></td>
<td><div class="cell">运行模式</div></td>
<td><div class="cell" v-if="cache.info">{{ cache.info.redis_mode === "standalone" ? "单机" : "集群" }}</div></td>
<td><div class="cell">端口</div></td>
<td><div class="cell" v-if="cache.info">{{ cache.info.tcp_port }}</div></td>
<td><div class="cell">客户端数</div></td>
<td><div class="cell" v-if="cache.info">{{ cache.info.connected_clients }}</div></td>
</tr>
<tr>
<td><div class="cell">运行时间()</div></td>
<td><div class="cell" v-if="cache.info">{{ cache.info.uptime_in_days }}</div></td>
<td><div class="cell">使用内存</div></td>
<td><div class="cell" v-if="cache.info">{{ cache.info.used_memory_human }}</div></td>
<td><div class="cell">使用CPU</div></td>
<td><div class="cell" v-if="cache.info">{{ parseFloat(cache.info.used_cpu_user_children).toFixed(2) }}</div></td>
<td><div class="cell">内存配置</div></td>
<td><div class="cell" v-if="cache.info">{{ cache.info.maxmemory_human }}</div></td>
</tr>
<tr>
<td><div class="cell">AOF是否开启</div></td>
<td><div class="cell" v-if="cache.info">{{ cache.info.aof_enabled === "0" ? "" : "" }}</div></td>
<td><div class="cell">RDB是否成功</div></td>
<td><div class="cell" v-if="cache.info">{{ cache.info.rdb_last_bgsave_status }}</div></td>
<td><div class="cell">Key数量</div></td>
<td><div class="cell" v-if="cache.dbSize">{{ cache.dbSize }} </div></td>
<td><div class="cell">网络入口/出口</div></td>
<td><div class="cell" v-if="cache.info">{{ cache.info.instantaneous_input_kbps }}kps/{{cache.info.instantaneous_output_kbps}}kps</div></td>
</tr>
</tbody>
</table>
</div>
</el-card>
</el-col>
<el-col :span="12" class="card-box">
<el-card>
<div slot="header"><span>命令统计</span></div>
<div class="el-table el-table--enable-row-hover el-table--medium">
<div ref="commandstats" style="height: 420px" />
</div>
</el-card>
</el-col>
<el-col :span="12" class="card-box">
<el-card>
<div slot="header">
<span>内存信息</span>
</div>
<div class="el-table el-table--enable-row-hover el-table--medium">
<div ref="usedmemory" style="height: 420px" />
</div>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script>
import { getCache } from "@/api/infra/redis";
import * as echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
export default {
name: "InfraRedis",
data () {
return {
//
commandstats: null,
// 使
usedmemory: null,
// cache
cache: []
};
},
created () {
this.getList();
this.openLoading();
},
methods: {
/** 查缓存询信息 */
getList () {
// Redis
getCache().then((response) => {
this.cache = response.data;
this.$modal.closeLoading();
this.commandstats = echarts.init(this.$refs.commandstats, "macarons");
const commandStats = [];
response.data.commandStats.forEach(row => {
commandStats.push({
name: row.command,
value: row.calls
});
})
this.commandstats.setOption({
tooltip: {
trigger: "item",
formatter: "{a} <br/>{b} : {c} ({d}%)",
},
series: [
{
name: "命令",
type: "pie",
roseType: "radius",
radius: [15, 95],
center: ["50%", "38%"],
data: commandStats,
animationEasing: "cubicInOut",
animationDuration: 1000,
},
],
});
this.usedmemory = echarts.init(this.$refs.usedmemory, "macarons");
this.usedmemory.setOption({
tooltip: {
formatter: "{b} <br/>{a} : " + this.cache.info.used_memory_human,
},
series: [
{
name: "峰值",
type: "gauge",
min: 0,
max: 1000,
detail: {
formatter: this.cache.info.used_memory_human,
},
data: [
{
value: parseFloat(this.cache.info.used_memory_human),
name: "内存消耗",
},
],
},
],
});
});
},
//
openLoading () {
this.$modal.loading("正在加载缓存监控数据,请稍后!");
}
},
};
</script>

View File

@ -0,0 +1,30 @@
<template>
<div>
<doc-alert title="服务监控" url="https://doc.iocoder.cn/server-monitor/" />
<i-frame v-if="!loading" :src="url" />
</div>
</template>
<script>
import iFrame from "@/components/iFrame/index";
import { getConfigKey } from "@/api/infra/config";
export default {
name: "InfraAdminServer",
components: { iFrame },
data() {
return {
url: process.env.VUE_APP_BASE_API + "/admin/applications",
loading: true
};
},
created() {
getConfigKey("url.spring-boot-admin").then(response => {
if (!response.data || response.data.length === 0) {
return
}
this.url = response.data;
}).finally(() => {
this.loading = false;
})
}
};
</script>

View File

@ -0,0 +1,30 @@
<template>
<div>
<doc-alert title="服务监控" url="https://doc.iocoder.cn/server-monitor/" />
<i-frame v-if="!loading" :src="url" />
</div>
</template>
<script>
import iFrame from "@/components/iFrame/index";
import { getConfigKey } from "@/api/infra/config";
export default {
name: "InfraSkyWalking",
components: { iFrame },
data() {
return {
url: "http://skywalking.shop.iocoder.cn",
loading: true
};
},
created() {
getConfigKey("url.skywalking").then(response => {
if (!response.data || response.data.length === 0) {
return
}
this.url = response.data;
}).finally(() => {
this.loading = false;
})
}
};
</script>

View File

@ -0,0 +1,31 @@
<template>
<div>
<doc-alert title="接口文档" url="https://doc.iocoder.cn/api-doc/" />
<i-frame v-if="!loading" :src="url" />
</div>
</template>
<script>
import iFrame from "@/components/iFrame/index";
import { getConfigKey } from "@/api/infra/config";
export default {
name: "InfraSwagger",
components: { iFrame },
data() {
return {
url: process.env.VUE_APP_BASE_API + "/doc.html", // Knife4j UI
// url: process.env.VUE_APP_BASE_API + "/swagger-ui", // Swagger UI
loading: true
};
},
created() {
getConfigKey("url.swagger").then(response => {
if (!response.data || response.data.length === 0) {
return
}
this.url = response.data;
}).finally(() => {
this.loading = false;
})
}
};
</script>

View File

@ -0,0 +1,257 @@
<template>
<div class="app-container">
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="名字" prop="name">
<el-input v-model="queryParams.name" placeholder="请输入名字" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
<el-option label="请选择字典生成" value="" />
</el-select>
</el-form-item>
<el-form-item label="类型" prop="type">
<el-select v-model="queryParams.type" placeholder="请选择类型" clearable>
<el-option label="请选择字典生成" value="" />
</el-select>
</el-form-item>
<el-form-item label="分类" prop="category">
<el-input v-model="queryParams.category" placeholder="请输入分类" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="queryParams.remark" placeholder="请输入备注" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker v-model="queryParams.createTime" style="width: 240px" value-format="yyyy-MM-dd HH:mm:ss" type="daterange"
range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" :default-time="['00:00:00', '23:59:59']" />
</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 type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
v-hasPermi="['infra:test-demo:create']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport" :loading="exportLoading"
v-hasPermi="['infra:test-demo:export']">导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<!-- 列表 -->
<el-table v-loading="loading" :data="list">
<el-table-column label="编号" align="center" prop="id" />
<el-table-column label="名字" align="center" prop="name" />
<el-table-column label="状态" align="center" prop="status" />
<el-table-column label="类型" align="center" prop="type" />
<el-table-column label="分类" align="center" prop="category" />
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['infra:test-demo:update']">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
v-hasPermi="['infra:test-demo:delete']">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
<!-- 对话框(添加 / 修改) -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="名字" prop="name">
<el-input v-model="form.name" placeholder="请输入名字" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio label="1">请选择字典生成</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="类型" prop="type">
<el-select v-model="form.type" placeholder="请选择类型">
<el-option label="请选择字典生成" value="" />
</el-select>
</el-form-item>
<el-form-item label="分类" prop="category">
<el-input v-model="form.category" placeholder="请输入分类" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" placeholder="请输入备注" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { createTestDemo, updateTestDemo, deleteTestDemo, getTestDemo, getTestDemoPage, exportTestDemoExcel } from "@/api/infra/testDemo";
export default {
name: "TestDemo",
components: {
},
data() {
return {
//
loading: true,
//
exportLoading: false,
//
showSearch: true,
//
total: 0,
//
list: [],
//
title: "",
//
open: false,
//
queryParams: {
pageNo: 1,
pageSize: 10,
name: null,
status: null,
type: null,
category: null,
remark: null,
createTime: []
},
//
form: {},
//
rules: {
name: [{ required: true, message: "名字不能为空", trigger: "blur" }],
status: [{ required: true, message: "状态不能为空", trigger: "blur" }],
type: [{ required: true, message: "类型不能为空", trigger: "change" }],
category: [{ required: true, message: "分类不能为空", trigger: "blur" }],
}
};
},
created() {
this.getList();
},
methods: {
/** 查询列表 */
getList() {
this.loading = true;
//
getTestDemoPage(this.queryParams).then(response => {
this.list = response.data.list;
this.total = response.data.total;
this.loading = false;
});
},
/** 取消按钮 */
cancel() {
this.open = false;
this.reset();
},
/** 表单重置 */
reset() {
this.form = {
id: undefined,
name: undefined,
status: undefined,
type: undefined,
category: undefined,
remark: undefined,
};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
/** 新增按钮操作 */
handleAdd() {
this.reset();
this.open = true;
this.title = "添加字典类型";
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
const id = row.id;
getTestDemo(id).then(response => {
this.form = response.data;
this.open = true;
this.title = "修改字典类型";
});
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (!valid) {
return;
}
//
if (this.form.id != null) {
updateTestDemo(this.form).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
});
return;
}
//
createTestDemo(this.form).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
});
});
},
/** 删除按钮操作 */
handleDelete(row) {
const id = row.id;
this.$modal.confirm('是否确认删除字典类型编号为"' + id + '"的数据项?').then(function() {
return deleteTestDemo(id);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
},
/** 导出按钮操作 */
handleExport() {
//
let params = {...this.queryParams};
params.pageNo = undefined;
params.pageSize = undefined;
//
this.$modal.confirm('是否确认导出所有字典类型数据项?').then(() => {
this.exportLoading = true;
return exportTestDemoExcel(params);
}).then(response => {
this.$download.excel(response, '字典类型.xls');
this.exportLoading = false;
}).catch(() => {});
}
}
};
</script>

View File

@ -0,0 +1,92 @@
<template>
<div class="app-container">
<el-form label-width="120px">
<el-row type="flex" :gutter="0">
<el-col :sm="12">
<el-form-item label="WebSocket地址" size="small">
<el-input v-model="url" type="text"/>
</el-form-item>
</el-col>
<el-col :offset="1">
<el-form-item label="" label-width="0px" size="small">
<el-button @click="connect" type="primary" :disabled="ws&&ws.readyState===1">
{{ ws && ws.readyState === 1 ? "已连接" : "连接" }}
</el-button>
<el-button @click="exit" type="danger">断开</el-button>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="发送内容" size="small">
<el-input type="textarea" v-model="message" :rows="5"/>
</el-form-item>
<el-form-item label="" size="small">
<el-button type="success" @click="send">发送消息</el-button>
</el-form-item>
<el-form-item label="接收内容" size="small">
<el-input type="textarea" v-model="content" :rows="12" disabled/>
</el-form-item>
<el-form-item label="" size="small">
<el-button type="info" @click="content=''">清空消息</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import store from "@/store";
import {getNowDateTime} from "@/utils/ruoyi";
export default {
data() {
return {
url: process.env.VUE_APP_BASE_API + "/websocket/message",
message: "",
content: "",
ws: null,
};
},
created() {
this.url = this.url.replace("http", "ws")
},
methods: {
connect() {
if (!'WebSocket' in window) {
this.$modal.msgError("您的浏览器不支持WebSocket");
return;
}
const userId = store.getters.userId;
this.ws = new WebSocket(this.url + "?userId=" + userId);
const self = this;
this.ws.onopen = function (event) {
self.content = self.content + "\n**********************连接开始**********************\n";
};
this.ws.onmessage = function (event) {
self.content = self.content + "接收时间:" + getNowDateTime() + "\n" + event.data + "\n";
};
this.ws.onclose = function (event) {
self.content = self.content + "**********************连接关闭**********************\n";
};
this.ws.error = function (event) {
self.content = self.content + "**********************连接异常**********************\n";
};
},
exit() {
if (this.ws) {
this.ws.close();
this.ws = null;
}
},
send() {
if (!this.ws || this.ws.readyState !== 1) {
this.$modal.msgError("未连接到服务器");
return;
}
if (!this.message) {
this.$modal.msgError("请输入发送内容");
return;
}
this.ws.send(this.message);
}
},
};
</script>

View File

@ -12,7 +12,8 @@
<p class="itemNum">{{ item.checkValue ? (item.checkValue).toFixed(2) : '-' }}</p> <p class="itemNum">{{ item.checkValue ? (item.checkValue).toFixed(2) : '-' }}</p>
<p class="itemDescribe" :title="item.name"> <p class="itemDescribe" :title="item.name">
<img src="./../../../../../assets/images/detectionData.png" alt=""> <img src="./../../../../../assets/images/detectionData.png" alt="">
{{item.name}}</p> {{ item.name }}
</p>
</div> </div>
</div> </div>
</div> </div>
@ -37,7 +38,7 @@ import LineChart from './../../components/lineChart'
import SearchArea from './../../components/searchArea' import SearchArea from './../../components/searchArea'
import moment from 'moment' import moment from 'moment'
export default { export default {
name: 'Voc', name: 'VocManagement',
data() { data() {
return { return {
realtimeList: [], realtimeList: [],
@ -85,34 +86,41 @@ export default {
<style lang='scss' scoped> <style lang='scss' scoped>
.voc { .voc {
background-color: #f2f4f9; background-color: #f2f4f9;
.box1 { .box1 {
height: 172px; height: 172px;
padding: 12px 16px 0; padding: 12px 16px 0;
margin-bottom: 8px; margin-bottom: 8px;
background-color: #fff; background-color: #fff;
border-radius: 9px; border-radius: 9px;
.itemBox { .itemBox {
display: flex; display: flex;
flex-flow: row nowrap; flex-flow: row nowrap;
overflow: auto; overflow: auto;
.itemClass { .itemClass {
width: 198px; width: 198px;
height: 88px; height: 88px;
// border: 1px solid green; // border: 1px solid green;
padding: 12px 0px 12px 18px; padding: 12px 0px 12px 18px;
.itemSub { .itemSub {
width: 176px; width: 176px;
height: 65px; height: 65px;
padding-right: 26px; padding-right: 26px;
border-right: 1px solid #E8E8E8; border-right: 1px solid #E8E8E8;
P { P {
margin: 0; margin: 0;
img { img {
width: 24px; width: 24px;
height: 24px; height: 24px;
vertical-align: middle; vertical-align: middle;
} }
} }
.itemNum { .itemNum {
font-size: 32px; font-size: 32px;
font-weight: 600; font-weight: 600;
@ -120,26 +128,33 @@ export default {
color: #3E6AF7; color: #3E6AF7;
text-align: right; text-align: right;
} }
.itemDescribe { .itemDescribe {
font-size: 16px; font-size: 16px;
text-align: right; text-align: right;
white-space: nowrap; /* 防止换行 */ white-space: nowrap;
overflow: hidden; /* 隐藏超出部分 */ /* 防止换行 */
text-overflow: ellipsis; /* 添加省略号 */ overflow: hidden;
/* 隐藏超出部分 */
text-overflow: ellipsis;
/* 添加省略号 */
} }
} }
} }
} }
.itemClass:last-child>.itemSub { .itemClass:last-child>.itemSub {
border: none !important; border: none !important;
} }
} }
.box2 { .box2 {
background-color: #fff; background-color: #fff;
border-radius: 9px; border-radius: 9px;
padding: 16px; padding: 16px;
height: calc(100vh - 308px); height: calc(100vh - 308px);
} }
.boxTitle { .boxTitle {
display: inline-block; display: inline-block;
font-size: 16px; font-size: 16px;
@ -147,6 +162,7 @@ export default {
color: #000000; color: #000000;
margin: 0 10px 20px 0; margin: 0 10px 20px 0;
} }
.blueTitle { .blueTitle {
content: ''; content: '';
display: inline-block; display: inline-block;